Input-output manifest
The Input-Output (IO) manifest is the bridge between your experimental logic and your specific hardware setup. It defines the devices (e.g., National Instruments DAQ, LabJack), engines, and channels that psiexperiment will use to generate stimuli and acquire data.
By decoupling hardware configuration into its own file, the same experimental paradigm can be run in different labs by simply swapping the IO manifest.
Structure of an IO File
An IO manifest is written in Enaml and defines an IOManifest class. It typically contains one or more Engines and their associated Channels.
from psi.controller.api import (IOManifest, NIEngine, AnalogInput,
AnalogOutput, DigitalOutput)
enamldef MyLabIOManifest(IOManifest): manifest:
id = 'my_lab_hardware'
NIEngine: engine:
name = 'NI-DAQmx'
# Identifier of the DAQ device in NI Max
dev_name = 'Dev1'
master_clock = True
AnalogOutput: speaker:
name = 'speaker'
channel = 'ao0'
fs = 100000.0
terminal_mode = 'differential'
AnalogInput: microphone:
name = 'microphone'
channel = 'ai0'
fs = 100000.0
terminal_mode = 'differential'
DigitalOutput: water_valve:
name = 'water_valve'
channel = 'port0/line0'
Engines and Syncing
The Engine is responsible for the hardware timing and control loop. You can have multiple engines in a single experiment (e.g., an NI DAQ for high-speed audio and an Arduino for simple digital triggers).
Master Clock: One engine must be designated as the
master_clock. This engine provides the reference timing for the entire experimental loop.Hardware Timed: High-speed channels (like
AnalogInput) are usually hardware-timed by the engine’s clock.Software Timed: Low-speed channels (like digital toggles) can be software-timed.
Examples from Templates
Psiexperiment ships with a set of templates in psi/templates/io/ that demonstrate common hardware configurations.
Simple NI-DAQ Configuration
A basic configuration for a system with one analog output (connected to a speaker) and one analog input (from a microphone) driven by a National Instruments DAQ card.
from enaml.workbench.api import PluginManifest, Extension
from psi.controller.engines.nidaq import (NIDAQEngine,
NIDAQHardwareAIChannel,
NIDAQHardwareAOChannel)
enamldef IOManifest(PluginManifest): manifest:
Extension:
id = 'backend'
point = 'psi.controller.io'
NIDAQEngine:
name = 'NI'
hw_ai_monitor_period = 0.125
NIDAQHardwareAIChannel:
label = 'Microphone 1'
name = 'microphone_1'
channel = 'Dev1/ai0'
start_trigger = '/Dev1/ao/StartTrigger'
fs = 100e3
expected_range = (-10, 10)
dtype = 'float64'
terminal_mode = 'RSE'
#terminal_coupling = 'AC'
unit = 'V'
calibration_user_editable = True
NIDAQHardwareAIChannel:
label = 'Microphone 2'
name = 'microphone_2'
channel = 'Dev1/ai1'
start_trigger = '/Dev1/ao/StartTrigger'
fs = 100e3
expected_range = (-10, 10)
dtype = 'float64'
terminal_mode = 'RSE'
#terminal_coupling = 'AC'
unit = 'V'
calibration_user_editable = True
NIDAQHardwareAOChannel:
label = 'Speaker 1'
name = 'speaker_1'
channel = '/Dev1/ao0'
fs = 100e3
expected_range = (-10, 10)
dtype = 'float64'
terminal_mode = 'RSE'
unit = 'V'
NIDAQHardwareAOChannel:
label = 'Speaker 2'
name = 'speaker_2'
channel = '/Dev1/ao1'
fs = 100e3
expected_range = (-10, 10)
dtype = 'float64'
terminal_mode = 'RSE'
unit = 'V'
Behavioral Setup with Digital Logic
For behavioral experiments, you may need to convert analog signals (e.g., from an infrared beam) into binary events. You can define custom processing chains within the IO manifest to handle this.
from enaml.workbench.api import Extension, PluginManifest
from psi.controller.engines.nidaq import (NIDAQEngine,
NIDAQHardwareAIChannel,
NIDAQHardwareAOChannel,
NIDAQSoftwareDOChannel)
from psi.controller.api import (CalibratedInput, Downsample, Edges, IIRFilter,
Threshold, Trigger, Toggle)
enamldef IRChannel(NIDAQHardwareAIChannel): irc:
unit = 'V'
start_trigger = 'ao/StartTrigger'
fs = 100e3
expected_range = (-10, 10)
dtype = 'float64'
terminal_mode = 'differential'
IIRFilter: iir:
name << irc.name + '_filtered'
f_lowpass = 25
ftype = 'butter'
btype = 'lowpass'
Downsample: ds:
name << irc.name + '_analog'
q = 1000
Threshold: th:
threshold = 2.5
Edges: e:
name << irc.name + '_digital'
debounce = 2
enamldef IOManifest(PluginManifest): manifest:
'''
This defines the hardware connections that are specific to the LBHB Bobcat
computer for the appetitive experiment.
'''
Extension:
id = 'backend'
point = 'psi.controller.io'
NIDAQEngine: engine:
name = 'NI'
master_clock = True
# Since we're using an AnalogThreshold input to detect nose pokes
# and reward contact, we want a fairly short AI monitor period to
# ensure that we detect these events quickly.
hw_ai_monitor_period = 0.025
hw_ao_monitor_period = 1
NIDAQHardwareAOChannel:
label = 'Speaker'
name = 'speaker'
channel = 'Dev1/ao0'
fs = 100e3
expected_range = (-10, 10)
dtype = 'float64'
terminal_mode = 'RSE'
calibration_user_editable = True
NIDAQSoftwareDOChannel:
name = 'food_dispense'
channel = 'Dev1/port0/line0'
Trigger:
# This is a required output for the food dispenser. The
# plugin will look for this output by name. If not present,
# the food dispenser plugin will not work!
label = 'Food dispense'
name = 'food_dispense_trigger'
duration = 0.1
NIDAQSoftwareDOChannel:
name = 'room_light'
channel = 'Dev1/port0/line1'
Toggle:
# This is a required output for the room light. The plugin
# will look for this output by name. If not present, the
# room light plugin will not work!
name = 'room_light_toggle'
label = 'Room light'
IRChannel:
label = 'Nose poke IR'
name = 'nose_poke'
channel = 'Dev1/ai0'
IRChannel:
label = 'Reward IR'
name = 'reward_contact'
channel = 'Dev1/ai1'
How to use an IO Manifest
When launching an experiment, you specify the IO manifest via the --io command-line argument:
psi my_experiment --io my_lab_hardware
Psiexperiment will automatically look for the file in your configured IO_ROOT folder. For more information on setting up your environment, see Installing.