Markers in Bessy Python

This is a spike on a possible new approach to how markers are handled in BCI Essentials Python.

The idea is to refactor the marker handling logic to enable more efficient handling of the markers by type.

Receiving and handling markers

In Bessy Python, BciController pulls markers and the associated timestamps from a stream of “LSL-Marker-Strings,” using get_markers() from MarkerSource.

There are two broad types of markers: Event Markers and Command Markers.

The step() method of BciController parses the marker string to determine if it is an Event marker. If it is, logic is executed to process that marker. If it is a Command marker, a series of elif statements execute the corresponding logic depending on the command marker label.

To make this more efficient, we could implement a subscriber-based system, with callback methods associated with specific marker types. This would be similar to the implementation in LSLResponseProvider that Twig created for Bessy Unity.

Possible Approach - Subscriber-based system

This is based on Twig’s LSL Framework overhaul on Bessy Unity.

To make the logic for handling markers simpler and more efficient, we could implement a subscriber-based system, with callback methods associated with specific marker types. This would be similar to the implementation in LSLResponseProvider that Twig created for Bessy Unity.

Here is a rough example of how this subscriber system could be implemented in Python:

  1. Create a method somewhere that adds a callback method to a _subscribers dictionary based on marker type.
def _subscribe(self, marker_type: str, callback):
  self._subscribers[marker_type] = callback
  1. Call the _subscribe() method to assign a callback for each marker. For example:
self._subscribe("Training Complete", self.training_complete_response)

Each callback method (such as _training_complete() in the above example) would contain the logic associated with its marker.

  1. In the while loop of the step() method, use the subscriber system to handle the response depending on the current marker.
# Get the callback associated with the current marker
callback = self._subscribers.get(current_step_marker)
	if callback:
		# Invoke the callback method
		callback(...)

So, instead of using several elif statements to identify the Command marker label, we can retrieve the callback method subscribed to the current marker, and call that method.

For Event markers, using the subscriber-callback system would have to be done slightly differently. Instead of looking for an exact match for current_step_marker in _subscribers, one approach is to subscribe to a general “Event Marker” category. Then, if current_step_marker is an event marker, and pass it to a method for processing.

# Subscribe to an "Event Marker" category
self.subscribe("Event Marker", self.event_marker_response)


# In BciController's step() method:
# Check if the current marker is an Event marker - can be done using the existing logic for this
if is_event_marker:
  callback = self._subscribers.get("Event Marker")  
  1. If necessary, we can implement logic that ensures callbacks are still valid before invoking them, along the lines of LSLResponseStream in Bessy Unity.
  2. We could also add an _unsubscribe() method to remove a callback for a specific marker type.

In summary: This approach avoids the hard-coded marker labels within the step() method, and removes the long chain of elif statements currently used to identify the type of marker pulled by get_markers(). It also separates the logic specific to each marker into individual methods.

Also, this approach could be useful if we want Bessy Py to more closely match how LSL responses are now handled in Bessy Unity.

Possible Approach - Enumerated types

Another way to simplify marker handling is to implement enumerated types. This could be adapted alongside the subscribers approach described above, or the enum could be implemented with a dictionary to avoid using the elif statements.

Here’s an idea of what this could look like:

  1. Create an enum for the marker types.
from enum import Enum

class MarkerType(Enum):
    TRIAL_STARTED = "Trial Started"
    TRIAL_ENDS = "Trial Ends"
    TRAINING_COMPLETE = "Training Complete"
    UPDATE_CLASSIFIER = "Update Classifier"
    DONE_RS_COLLECTION = "Done with all RS collection"
  1. Using a dictionary, map each enum to a method based on the marker type.
  2. In step(), find the MarkerType enum of the current_step_marker, then call its corresponding method from the dictionary. Event markers will need to be handled separately.

Summary

Each of these approaches could provide a simpler way to parse marker strings obtained with get_markers() and handle the logic based on the marker type.