What makes an experiment?
In psiexperiment, an experiment is known as a paradigm. A paradigm is not a single large piece of code but rather a collection of functional plug-in modules (some required, some optional) that interact with each other to realize the experimental design.
This modular approach ensures that core features like data storage, hardware communication, and parameter management can be reused across vastly different experimental types. For a detailed look at the underlying plugin architecture, see the Psiexperiment Architecture documentation.
What is a paradigm?
A paradigm is defined using the ParadigmDescription class. It serves as a manifest of all the plugins that should be loaded together to run the experiment.
Here’s an example of how a paradigm is described:
from psi.experiment.api import ParadigmDescription
CORE_PATH = 'psi.paradigms.core.'
MY_PATH = 'my_lab_plugins.'
ParadigmDescription(
# Unique identifier for the paradigm
'tone_pips',
# Human-readable name
'Tone Pip Experiment',
# Experiment type (e.g., 'animal' or 'calibration')
'auditory',
[
# Required core plugins
{'manifest': MY_PATH + 'tone_pip_controller.TonePipManifest'},
# Optional visual monitoring plugin
{'manifest': CORE_PATH + 'signal_mixins.SignalFFTViewManifest',
'selected': True,
'attrs': {
'fft_time_span': 1.0,
'source': 'microphone',
'y_label': 'Level (dB)'
}
},
# Additional hardware drivers or monitoring tools
{'manifest': CORE_PATH + 'video_mixins.PSIVideo', 'selected': False},
],
)
Key Concepts of Paradigm Descriptions:
Unique ID: The first argument (e.g.,
'tone_pips') is how you launch the experiment via the command line:psi tone_pips.Plugin List: The list contains one or more plugin manifests (identified by their full module path).
Attrs: You can override the default values of attributes in a manifest by passing an
attrsdictionary. This is a powerful way to customize a generic plugin (like an FFT view) for a specific experiment without writing new code.Selected vs Required: Plugins can be marked as
selected(loaded by default but can be unchecked in the launcher) orrequired(always loaded).
What is a manifest?
A manifest is the Enaml declaration that defines how a plugin connects to the rest of the psiexperiment ecosystem. Most plugins will subclass ExperimentManifest.
Here is a simplified example of the SignalFFTViewManifest, which contributes a real-time plot to the experiment:
from enaml.workbench.api import Extension
from psi.core.enaml.api import ExperimentManifest
from psi.data.plots import (FFTChannelPlot, FFTContainer, ViewBox)
enamldef SignalFFTViewManifest(ExperimentManifest): manifest:
id = 'signal_fft_view'
name = 'signal_fft_view'
title = 'Signal view (PSD)'
# Attributes that can be overridden via ParadigmDescription 'attrs'
alias fft_time_span: fft_plot.time_span
alias source_name: fft_plot.source_name
Extension:
id = manifest.id + '.plots'
point = 'psi.data.plots'
FFTContainer: fft_container:
label << manifest.title
ViewBox: fft_vb:
y_min = -10
y_max = 100
FFTChannelPlot: fft_plot:
source_name = 'microphone'
time_span = 0.25
In this example, the manifest uses an Extension to contribute a new plot to the psi.data.plots hook. By defining alias properties, it makes internal settings (like time_span) accessible from the top-level manifest, allowing them to be customized in the ParadigmDescription.
Another common use for manifests is defining Actions that respond to experimental events:
enamldef RewardPlugin(ExperimentManifest): manifest:
id = 'reward_dispenser'
attr reward_duration = 0.5
Extension:
id = manifest.id + '.actions'
point = 'psi.controller.actions'
ExperimentAction:
# Respond to a behavioral event
event = 'subject_responded_correctly'
# Invoke a command on a hardware output
command = 'water_valve.fire'
kwargs = {'duration': manifest.reward_duration}
By using this declarative approach, psiexperiment allows you to build complex, reactive systems by simply stating which plugins should be used and how they should be configured.