Creating Custom Stimulus Behaviour - kirtonBCIlab/bci-essentials-unity GitHub Wiki
While the provided classes can be used in a number of simple cases, BCI-Essentials Unity is set up to let you create your own stimulus behaviour by extending the provided functionality. There are two main classes to target which are listed below along with the virtual methods which should be targeted. Others exist but may result in unexpected behaviour if overridden.
In most cases, the primary targets are:
-
BCIControllerBehavior
:RunStimulusRoutine
/UpdateStimulus
- define stimulus routine -
SPO
events or methods - define stimulus display
Specifies stimulus routines, training, and marker communication to be "driven" by the BCIController
.
-
RunStimulusRoutine
Defines a discrete yielding method to run a single stimulus pass. Not relevant to continual stimulus paradigms.
// P300ControllerBehavior.cs
protected override IEnumerator RunStimulusRoutine()
{
...
RunSingleFlashRoutine()
...
}
private IEnumerator RunSingleFlashRoutine()
{
int totalFlashes = numFlashesPerObjectPerSelection * _selectableSPOs.Count;
int[] stimOrder = ArrayUtilities.GenerateRNRA_FisherYates(totalFlashes, 0, _selectableSPOs.Count - 1);
foreach (int stimIndex in stimOrder)
{
yield return RunSingleFlash(stimIndex);
}
}
protected IEnumerator RunSingleFlash(int activeIndex)
=> RunSingleFlash(activeIndex, _selectableSPOs);
protected IEnumerator RunSingleFlash
(
int activeIndex, List<SPO> stimulusObjects
)
{
SPO flashingObject = stimulusObjects[activeIndex];
flashingObject.StartStimulus();
SendSingleFlashMarker(activeIndex, stimulusObjects.Count);
yield return new WaitForSecondsRealtime(onTime);
flashingObject.StopStimulus();
yield return new WaitForSecondsRealtime(offTime);
}
-
SetUpForStimulusRun
Configure script members and references in preparation for a stimulus run. Called beforeRunStimulusRoutine
when a run is triggered byStartStimulusRun
. -
CleanUpAfterStimulusRun
Reset script member and reference configuration after a stimulus run has completed. Called afterRunStimulusRoutine
when a run is triggered byStartStimulusRun
or interrupted byStopStimulusRun
. -
UpdateObjectListConfiguration
Configure script members based on a newly populated list of selectable objects.
// MIControllerBehavior.cs
protected override void UpdateObjectListConfiguration()
{
if (SPOCount > 2)
{
Debug.LogWarning("Warning: Selecting between more than 2 objects!");
}
}
-
MakeSelection
,MakeSelectionAtEndOfRun
Select a class or object in response to a prediction from BCI-Essentials Python. Defaults to callingSelect
on the relevant object, but this behaviour isn't relevant to certain paradigms such as Motor Imagery. -
Run...TrainingRoutine
Define yielding training routines by type. The base implementation of automated training should be sufficient in most cases, but other types of training need to be more specific to the use case. Used byStartTraining
if the specified type has been implemented.- Automated
- User
- Iterative
- Single
-
UpdateClassifier
Tell BCI-Essentials Python to update the classifier. Unimplemented by default as this behaviour is not relevant to all paradigms. -
WaitForStimulusToComplete
Used in the provided automated training implementation to yield until a single round of training stimulus is complete, whether discrete or for a set period of time, as is the case for continual stimulus paradigms.
It is also possible to write a controller behaviour based on one of the built-in paradigms, such as P300ControllerBehavior
or SSVEPControllerBehavior
. The same methods can be overloaded in the same way to customize the implementation of a stimulus run without having to re-implement the rest of a behaviour class.
Any paradigm using a looping stimulus routine which will continue running until it is stopped, such as SSVEP or Motor Imagery, inherits from ContinualStimulusControllerBehavior
. This class provides a skeleton for training, stimulus, and the relevant markers within this frame.
-
SendTrainingMarker
/SendClassificationMarker
Send a single marker denoting the start of a training or classification epoch.
// SSVEPControllerBehavior.cs
protected override void SendClassificationMarker()
=> MarkerWriter.PushSSVEPClassificationMarker(
SPOCount, epochLength, realFlashingFrequencies
);
-
UpdateStimulus
Update the presentation state of stimulus objects or other prompt, used in frequency stimulus paradigms to update which objects should be 'on' or 'off' based on their assigned frequencies. Called every frame stimulus is running. Can be used as a contextual stand-in forUpdate
.
Flashes, moves, rotates, changed colour, or otherwise displays a change when prompted by a controller behaviour. The example behaviour is to flash 'on' or 'off' between two colours. Arbitrary behaviour can be implemented by extending the base SPO
class, or by using the provided Unity events they invoke. Code-based behaviour is implemented by overriding virtual enumerator methods:
-
RunSelectedRoutine
Provide feedback for the selection of this object -
RunStartStimulusRoutine
Turn stimulus 'on'. A single, instant toggle. The overarching stimulus routine is handled by the controller behaviour. -
RunStopStimulusRoutine
Turn stimulus 'off'. -
RunTargetedRoutine
Display discrete or continuous prompt indicating this object as the one being targeted for training. -
RunUntargetedRoutine
Reset display as altered byOnTrainTarget
Programmatically creates a set of stimulus presenting objects for use by a controller behaviour. A simple grid factory is provided and used in samples, but the base class provides a skeleton to implement whatever creation, placement, or initialization of SPO prefabs is desired.
-
InstantiateConfiguredObjects
Create and configure stimulus objects using assigned prefab. -
InstantiateObject
Internal method: creates and returns an instance of the SPO prefab assigned in the inspector
(tracks created objects for object population and cleanup)
An SPO Factory is a ScriptableObject
which are created, edited, and used as a sort of configuration file. Don't forget to add the following attribute yo your custom factory class to let you create an instance in the project view context menu.
[CreateAssetMenu(menuName = "SPO Setup/My SPO Factory", fileName = "My SPO Factory")]