BCI Framework programming and directory guide¶
Peers¶
Every Peer (that is a child of ConfiguredPeer
or Peer
)
which does some useful work is contained in obci.peers
module.
File containing peer class code should be snake_case_named
after the peer it provides. Peer classes should use
CamelCase
with last word being Peer.
Example:
We have Peer which does something really important:
class VeryImportantPeer(Peer):
pass
Which should be placed in appropriate sub-module inside module obci.peers
inside file named:
very_important_peer.py
Additionally such module should export that peer using __all__
mechanism, at the start of file there should be code:
__all__ = ('VeryImportantPeer', )
In order to extend peer functionality you have to do the following:
- Create
Peer
subclass
- Override
__init__()
to perform some basic initialization, without outside communication- Override (async)
_connection_established()
to perform custom initialization, which might take some time ie. communicate with other peers (like inConfiguredPeer
), or initialize resources. After this method finishes, peer is considered to be ready to work. If your additional configuration requires talking to other peers, especialy own launch_dependencies, you should use_dependencies_are_ready()
.- Override (async)
_shutting_down()
to perform long cleaunup task ie. inform other peers about something, send messages.- Override
_cleanup()
free up acquired resources.- Override (async)
_start()
to initializePeer
main task (ie start generating and sending data or save data to file)- Override (async)
_stop()
to stopPeer
main task (ie turn off amplifier, close file on disk)
- Write your message handlers methods
- message handlers should be coroutines
- message handlers for queries must return response messages
- message handlers can use (async)
_send_message()
for immediate message sending (send_message()
puts messages on the queue)
- Register message handlers and subscribe
- You should register message handler for message types you want to receive by using
register_message_handler()
decorator on handler method. After that you will be able to handle sync (your method must return response message) and async messages- In order to receive all async messages of that type messages you should use
subscribe_message_handler()
on handler method- If you only want to receive messages from certain peer you have to use
subscribe_for_specific_msg_subtype()
method in_connections_established()
You can also use peers without subclassing, just instantiate it, wait for connection and use methods:
- register_message_handler()
- subscribe()
- send_message()
- to send messages through pub and sub
- create_task()
- to run background asyncio tasks
See Peer States for more info.
Example:
Simple peer which averages signal across channels:
class AveragingPeer(ConfiguredPeer):
async def _connections_established(self):
await super()._connections_established()
# subscribe to signal going from amplifier peer
# for every SignalMessage self.signal_message_handler function will be called
self.subscribe_for_specific_msg_subtype(SignalMessage, 'amplifier', self.signal_message_handler)
await self.ready()
async def signal_message_handler(self, msg):
input = msg.data # retrieve SamplePacket
# create new SamplePacket averaged across channels
output = SamplePacket(ts=input.ts, samples=numpy.mean(input.samples, axis=1, keepdims=True))
# send created SamplePacket in a SignalMessage
msg = SignalMessage(data=output)
await self._send_message(msg) # every peer subscribed to AveragingPeers SignalMessages will receive this
# new message and could for example display it
# such peer could be running on a different computer.
Amplifier drivers¶
Drivers for amplifiers are split between amplifier classes, which derive from
EEGAmplifier
, and amplifier peers which
utilize those classes derive from AmplifierPeer
.
Drivers should be placed inside obci.drivers.eeg
module and corresponding peers
inside obci.peers.drivers.amplifiers
module.
If your driver implements EEGAmplifier
API and doesn’t need any
additional external parameters it is very likely that code for the peer will very concise:
from obci.peers.drivers.amplifiers.amplifier_peer import AmplifierPeer
from obci.drivers.eeg.my_great_amp import MyGreatAmplifier
__all__ = ('MyGreatAmplifierPeer',)
class MyGreatAmplifierPeer(AmplifierPeer):
AmplifierClass = RandomAmplifier
AmplifierPeer class has its own underlying mechanisms to retrieve samples from EEGAmplifier type classes and send those samples as messages.
BCI Framework driver discovery¶
To make your driver discoverable by BCI Framework driver discovery (i.e. visible in Svarog) you should do following:
- add amplifier class inside
obci.drivers.eeg.driver_discovery.get_amp_classes_defs()
- additionally add path to the amplifier peer
- add path to the template scenario which can run your amplifier
Scenarios¶
Scenarios internally for BCI Framework should abide by these rules:
- Scenarios for amplifiers and peers should be located inside obci/scenarios/
- Scenarios which run only amplifiers in different configurations
(ex to be viewed in Svarog) should be placed inobci/scenarios/amplifier/amp_name/
- Scenarios which save data from your amplifier should be placed in
obci/scenarios/acquisition/amp_name/
- If you want those scenarios to be visible in obci_gui you should edit
obci/control/gui/presets/default.ini
file appropriately.
For example scenario which saves signal from TMSI amplifier connected to USB port:
[peers]
scenario_dir=
;***********************************************
;***********************************************
[peers.config_server]
path=peers/control/config_server.py
;***********************************************
; here peers.SOMETHING - SOMETHING is the peer_id for the loaded peer.
[peers.amplifier]
path=peers/drivers/amplifiers/tmsi_amplifier_peer.py
;***********************************************
[peers.signal_saver]
; path to peers are looked up:
; - first: peer .py files:
; * first in the main obci directory
; * directory relative to scenario location
; * global path (including ~ expansion)
; - next: importable Python 3 path:
; * like this: path=obci.peers.acquisition.signal_saver_peer
path=peers/acquisition/signal_saver_peer.py
[peers.signal_saver.launch_dependencies]
; signal saver has external params depending on signal_source
; signal source could be any peer, in this case it is tmsi_amplifier_peer
signal_source=amplifier