Components are abstractions that allow tasks to communicate with external objects like levers, lights, or even network events
without details of the specific implementation. To develop a new component, the user creates a subclass of the base Component
class or preexisting component in the py-behav-box-v2/source/Components folder.
Typically, components do not enforce a particular physical implementation but abstract functionality that could be implemented on a variety of hardware. For example, a Toggle, a component that can be set to be on or off, could be used to represent a variety of hardware like lights, motors, or arbitrary digital outputs. Rather than having a separate component class for each of these equivalent cases, the details of interacting with hardware are instead handled by implementation specific Sources.
All components have a type that indicates the types of data components either receive or output. Component types are represented
as an enum in the base Component
class (see the reference section for the full type list).
All subclasses of Component
must override the get_type
method which should return one of the possible indicated types.
Using the example of the Toggle
component which solely outputs a simple digital on or off signal:
def get_type():
return Component.Type.DIGITAL_OUTPUT
All components have states which represent how a component is behaving at any given time. There are no requirements for the data
type of a component's state but all components must override the get_state
method. In the Toggle
component example,
the method just returns a boolean indicating if the toggle is current on or off. However, get_state
could return arbitrarily
complex objects for more flexible components like touch screens or analog inputs.
All components are linked to a particular Source via a component_id
and component_address
. This linkage is mediated
by a Task object task
. When Components are used outside of Tasks (for Sources or GUIs) the Task parameter can be None. These three variables are
all passed to the component constructor along with metadata
and configured using local AddressFiles.
All metadata will be processed to create class attributes corresponding to the entries in the dictionary. The component
constructor can be overridden if necessary to define variables for the component subclass (if necessary, these variables
can be locally configured using metadata
in AddressFiles). Depending on the Component, some metadata may be required or
option (indicated in the package reference below). The example below shows how
the default constructor could be overridden to keep track of a state variable:
def __init__(self, task, component_id, component_address):
super().__init__(task, component_id, component_address)
self.state = False
To query data from the component's source, use the Component
get_state
method. To output to the Source use the Component
write
method with a single input of any type. The type of data returned by get_state
or required by write
is flexible and can depend on the
Source
class. Therefore, we recommend that these methods should be wrapped in component-specific versions like the toggle
method for Toggle
components:
def toggle(self, on):
self.write(on)
self.state = on
Rather than calling write
or get_state
directly, methods like toggle
ensure states are properly updated and conventions for particular
types of components kept to. This is especially helpful when subclassing components when implementation specific features may
be necessary (look at BinaryInput
and OEBinaryInput
for a more in depth example).
When new data is received from a Source, the behavior for updating the state of a Source is handled by the update
method.
This method should return True if the value of the state was changed and False otherwise.
The methods and classes detailed below are contained in the Components
package.
The Component
class in the Component
module is the super class for all components.
__init__(task : Task, component_id : str, component_address : str)
Constructor for a new component connecting to Task task
registered with component_id
at component_address
.
Values for task
, component_id
, component_address
should be provided by local AddressFiles.
Any attributes (metadata) necessary for the component should be defined here (ex. frame rates for videos).
class Type(Enum)
Type is an enum representing the possible types of components to indicate expected data to a source. The possible component types are shown below:
DIGITAL_INPUT = 0 # The Component solely provides digital input
DIGITAL_OUTPUT = 1 # The Component solely receives digital output
ANALOG_INPUT = 2 # The Component solely provides analog input
ANALOG_OUTPUT = 3 # The Component solely receives analog output
INPUT = 4 # Arbitrary input type
OUTPUT = 5 # Arbitrary output type
BOTH = 6 # The Component both inputs and outputs (arbitrary type)
get_type() -> None
Returns the component type as one of the symbolic names described in the Type
class. This method must be overridden by
all subclasses of Component
.
Example override:
def get_type(self):
return Component.Type.DIGITAL_OUTPUT
get_state() -> Any
Returns the state the component currently is in.
write(msg : Any) -> None
Outputs a value via the source. The data type of msg
will depend on the source.
Example usage:
self.write(True)
update(value: Any) -> bool
Process new information from the Source to update the Component's state.
close() -> None
Notifies the source that the component should be closed.
Example usage:
self.close()
class Output(Component)
Type: OUTPUT
General purpose/non-specific output class.
Attributes:
state : Any
Current state value
class Input(Component)
Type: INPUT
General purpose/non-specific input class.
Attributes:
state : Any
Current state value
class Both(Component)
Type: BOTH
General purpose/non-specific class that supports inputs and outputs.
Attributes:
state : Any
Current state value
class Toggle(Output)
Type: DIGITAL_OUTPUT
Toggles typically represent digital outputs like lights and motors and can be set to on or off states.
Methods:
toggle(on : bool) -> None
Set the on/off state of the toggle.
Attributes:
state : bool
Current state value (active/inactive)
class BinaryInput(Input)
Type: DIGITAL_INPUT
BinaryInputs represent inputs that can only be on or off like switches, IR sensors, or levers.
Attributes:
state : bool
Current state value (active/inactive)
class AnalogInput(Input)
Type: ANALOG_INPUT
AnalogInputs represent inputs that can take on a continuous range of values like light, pressure, or temperature sensors.
Methods:
check() -> float
Queries and returns the current value of the component.
Attributes:
state : float
Current state value
class AnalogOutput(Output)
Type: ANALOG_OUTPUT
AnalogOutputs represent outputs that can take on a continuous range of values like intensity, frequency, and duration.
Attributes:
state : float
Current state value
class TimedToggle(Toggle)
Type: DIGITAL_OUTPUT
TimedToggles will only remain active for a set amount of time.
Methods:
toggle(on : Union[float, bool]) -> None
Set the on/off state of the toggle either as an active duration or a steady on/off value.
Attributes:
count: int
Counter for number of instances a timed event has occurred
class OEBinaryInput(BinaryInput)
Type: DIGITAL_INPUT
OEBinaryInputs represent TTL broadcast events originating from OpenEphys.
The class overrides the check
method to handle the JSON data but has identical outputs to the standard BinaryInput.
Optional Metadata:
rising: bool = True
Indicator for whether to respond to rising events
falling: bool = False
Indicator for whether to respond to falling events
class TouchBinaryInput(BinaryInput)
Type: DIGITAL_INPUT
TouchBinaryInputs contain additional information about the location of an input for use with hardware like touchscreens.
Attributes:
definition: Any
Application specific description of the touch object
pos: (int, int)
Tuple indicating the last location touched
class Video(Both)
Type: BOTH
Video components represent the "barebones" information necessary for recording a video file.
Methods:
start() -> None
Starts a video recording
stop() -> None
Stops a video recording
Attributes:
state: bool
Indicates if the video is currently being recorded
name: str
Represents the name for the saved video file. Default implementation uses the current timestamp as the name
class Stimmer(Output)
Type: Output
Stimmer is an abstract class to consolidate the basic functionality required for stimulation.
Methods:
parametrize(pnum : int, outs : list[int], per : int, dur : int, amps : ndarray, durs : list[int]) -> None
Defines a pulse train with ID pnum
. The type of stimulation for each potential channel is indicated by a list of ints outs
.
The stimulation has a period of per
. The pulse train itself is described by a set of stages with amplitudes amps
and durations durs
.
The number of stages corresponds to the number of columns in amps
which is equivalent to the length of durs
. The number of rows in amps
corresponds to the number of channels. This format was inspired by programming for the StimJim.
start(pnum : int, stype: str) -> None
Starts the pulse train with ID pnum
class StimJim(Output)
Type: Output
StimJim encapsulates behavior for a component that would accept a list of instructions to generate a stimulation waveform. The default configuration is readily compatible with the StimJim.
Methods:
trigger(ichan : int, pnum : int, falling : int = 0) -> None
Configures input ichan
on the component to trigger the stimulation
pulse train with ID pnum
. Defaults to trigger off a rising input but can be configured to falling inputs if falling
is changed to 1.
start(pnum : int, stype: str = 'T') -> None
Starts the pulse train with ID pnum
class WaveformStim(Output)
Type: AnalogOutput
WaveformStim defines a stimulation waveform as a matrix given configuration parameters that could be output using a device like a DAQ.
Methods:
parametrize(pnum : int, _, per : int, dur : int, amps : ndarray, durs : list[int]) -> None
Defines a pulse train with ID pnum
.
The stimulation has a period of per
. The pulse train itself is described by a set of stages with amplitudes amps
and durations durs
.
The number of stages corresponds to the number of columns in amps
which is equivalent to the length of durs
. The number of rows in amps
corresponds to the number of channels. This format was inspired by programming for the StimJim.
start(pnum : int, stype: str = None) -> None
Starts the pulse train with ID pnum