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.