psychopy_device#

PsychoPy stimulus device.

Launches a PsychoPy experiment script as a subprocess on the shared SubprocessStimulusDevice engine, so the Procedure drives it through the same arm/start/stop/shutdown lifecycle and stdout readiness handshake as MousePortal and any other external-app stimulus device.

PsychoPy is operator-in-the-loop: unlike MousePortal (which launches silently at arm), it launches at start and overrides the base’s presentation hooks to show a “launching” dialog, surface a startup failure with the script’s output, and gate recording behind a focused “press to start” dialog forced over PsychoPy’s full-screen window. The readiness handshake is mandatory (require_ready = True): if PSYCHOPY_READY never arrives, the run fails rather than recording against a stimulus that never started.

PsychoPy is not a DataProducer: it never emits on signals.data. signals.started fires on the PSYCHOPY_READY handshake; signals.finished fires on stop / subprocess exit.

Config convention (standardized with MousePortal): the script and parameters live in the ExperimentConfig (experiment.json); the hardware.yaml psychopy stanza carries only subprocess plumbing (type / python_exe / ready_timeout). Which script runs is keyed by the selected task: a top-level PsychoPy block maps {task: filename} (scripts declare their task by embedding task-{name} in the filename), and ExperimentConfig.psychopy_path resolves the entry for the current task, falling back to the legacy single psychopy_filename when no map is present. prepare() reads psychopy_path / psychopy_parameters and is unaware of this resolution. Parameters are handed to the script as a base64 JSON argv token so the PsychoPy interpreter needs only the stdlib to decode them (no mesofield import). The matching offline parser is registered under the psychopy tag in mesofield.datakit.sources and bound here as PsychoPyDevice.Parser for the documented dispatch convention.

class mesofield.devices.psychopy_device.PsychoPyDevice[source]#

Bases: SubprocessStimulusDevice

Stimulus device that launches a PsychoPy script as a subprocess.

__init__(cfg)[source]#
Parameters:

cfg (Dict[str, Any])

serves_task(task, config)[source]#

Serve a task iff the task->script map has an entry for it.

With no map (legacy single-script experiments) PsychoPy serves every task, preserving the old behavior.

Return type:

bool

prepare(config)[source]#

Resolve the script path and serialize parameters for the subprocess.

Parameters are sent as base64-encoded JSON (a single safe argv token) so the PsychoPy interpreter decodes them with only the stdlib – the script rebuilds an attribute namespace, e.g. config = types.SimpleNamespace(**json.loads(base64.b64decode(sys.argv[1]))).

Return type:

None

preflight()[source]#

Return an actionable error string to abort launch, or None to proceed.

Return type:

str | None

build_command()[source]#

Return the full argv used to launch the stimulus subprocess.

Return type:

List[str]

present_launching()[source]#

Show a non-blocking ‘waiting for PSYCHOPY_READY’ indicator.

Return type:

None

dismiss_launching()[source]#

Dismiss the indicator shown by present_launching(). No-op default.

Return type:

None

present_failure(message, detail='')[source]#

Surface a launch/handshake failure to the operator.

detail carries the child’s last output (see SubprocessSupervisor.output_tail). Logs by default; a GUI subclass shows a dialog.

Parameters:
Return type:

None

confirm_ready_to_record()[source]#

Focused ‘PsychoPy ready – press to start recording’ gate.

Forced to the foreground over PsychoPy’s full-screen window so a spacebar press lands on this dialog (OK is the default button), not on PsychoPy. Dismissing it returns control to the Procedure, which then starts the recording devices; the operator then presses spacebar in the PsychoPy window to begin the stimulus (cameras lead; timelines are aligned post-hoc). Returns False (Cancel) to abort the run.

Return type:

bool

Parser#

alias of Psychopy