devices#
- class mesofield.devices.BaseDevice[source]#
Bases:
objectDefault lifecycle skeleton for non-Qt hardware devices.
Constructor accepts an optional
cfgmapping (the YAML stanza for this device). Common keys are auto-extracted:id/device_id->self.device_idprimary: true->self.is_primary
A logger is created automatically as
f"{module}.{class}[{device_id}]".- arm(config)[source]#
Per-run preparation. No-op by default.
- Parameters:
config (ExperimentConfig)
- Return type:
None
- property calibration: Dict[str, Any]#
Device-specific constants worth recording with the data.
Default: everything in cfg that isn’t an orchestration key. Override on a subclass to curate the list explicitly.
- sidecars()[source]#
Auxiliary files this device writes alongside its primary output.
Default: none. Override to declare extra sidecars (masks, regions, derived parameter files) so they ride in the manifest with a role and schema_version instead of being discovered by glob.
The camera classes’ per-frame metadata JSON is the primary sidecar and lives on self.metadata_path – not here. Use this method for the extra files only.
Returns a list of mesokit_schema.SidecarEntry-shaped mappings or instances. The Procedure relativises any absolute paths.
- Return type:
- class mesofield.devices.BaseDataProducer[source]#
Bases:
BaseDeviceBase class for devices that stream samples to the DataQueue.
Subclasses produce data by calling
record(), which timestamps, appends to an in-memory buffer, and emitssignals.data(payload, ts)in one step.The default
save_data()writes the buffer as a two-column CSV (timestamp,payload). Override for binary or domain-specific formats.- queue_payload(payload)[source]#
Filter/reshape what this producer contributes to the dataqueue.
signals.datais the full-fidelity stream that feeds this device’s own buffer (save_data-> CSV) and any live plots. The session-wideDataQueueis a cross-device alignment/event timeline, and a producer may want to contribute only a subset (or a reshaped form) of its samples to it.DataManagercalls this for every sample before pushing it onto the queue. Return:the (possibly transformed) payload to push, or
Noneto drop this sample from the queue entirely.
Default: push the payload unchanged. Override on a subclass to e.g. push only discrete events, or strip a high-rate channel from the queue while leaving the CSV and plots untouched.
- arm(config)[source]#
Default
arm: clear buffer and resolveoutput_path.configis expected to exposemake_path(name, ext, bids)(seemesofield.config.ExperimentConfig).- Parameters:
config (ExperimentConfig)
- Return type:
None
- class mesofield.devices.BaseSerialDevice[source]#
Bases:
BaseDataProducerPolling device for line-based serial protocols (Arduino/Teensy/etc.).
Subclasses override
parse_line(). Optionally overridesetup_serial()for post-open initialisation (handshakes, buffer drain, configuring device-side parameters).Configuration keys read from
cfg:port(str, required whendevelopment_mode=False)baudrate(int, default 115200)timeout(float, default 0.1) — pyserial readline timeout.dtr(bool | None, default None) — set toFalseto suppress Arduino auto-reset on connect.Nonekeeps the OS default.connect_delay(float, default 0.0) — seconds to wait after opening the port before reads begin; common Arduinos need ~2.0. The input buffer is flushed after the delay.development_mode(bool, default False) — skip opening the port so the GUI / Procedure can launch without hardware.send_linebecomes a no-op; the polling thread idles.
- setup_serial()[source]#
Hook called once after the port is opened. Default no-op.
Override to send a handshake, query firmware version, configure device-side parameters, etc.
- Return type:
None
- send_line(payload, *, newline=b'\n')[source]#
Write a command to the device. Thread-safe with the reader.
Accepts
str(UTF-8 encoded) orbytes.newlineis appended unlesspayloadalready ends with it. Indevelopment_modethe bytes are logged and discarded. Returns the bytes that were written (or would have been).
- class mesofield.devices.Nidaq[source]#
Bases:
BaseDataProducerExternal start-trigger + TTL edge counter on an NI-DAQ board.
- class mesofield.devices.MMCamera[source]#
Bases:
BaseCamera,DataProducer,HardwareDeviceMicro-Manager-backed camera.
Inherits the common camera surface (identity, output paths, manifest metadata,
arm/set_sequencedefaults,status,calibration) fromBaseCamera, and duck-types theDataProducer/HardwareDeviceProtocols so existing isinstance() checks keep working. The actual frame flow is driven by pymmcore-plus’s MDA event system; this class wires those events into the standardDeviceSignalsbundle and constructs anOMEWriter(OME-TIFF) orCV2Writer(MP4) for the output.- set_sequence(build_mda)[source]#
Build the MDA sequence (Micro-Manager backend only).
- Parameters:
build_mda (Callable[[DataProducer], Any])
- initialize()[source]#
Apply the YAML
propertiesblock to the underlying camera.Each
{device_id: {property: value}}pair is forwarded to the backend (core.setROIfor ROIs,core.setPropertyotherwise) with special handling for the syntheticfps,viewer_type, andauto_contrastkeys.
- start_led_sequence(pattern)[source]#
Start the LED pattern.
If
led_serialis configured on this camera, sends the configured raw byte sequences via MM’s SerialManager. Otherwise falls back to the originalArduino-Switch.State.loadSequence/startSequencepath.- Return type:
None
- stop_led_sequence()[source]#
Stop the LED pattern (mirror of
start_led_sequence()).- Return type:
None
- start()[source]#
Launch the MDA sequence non-blocking.
- Returns:
Always
True. The sequence runs asynchronously on the camera backend; lifecycle is reported viaself.signals.- Return type:
- stop()[source]#
Stop acquisition for any running Micro-Manager camera (incl. primary).
stopSequenceAcquisitionis non-joining and idempotent: on natural completion the engine already stopped the sequence (no-op here), and on abort/duration-cap it flips the flag the engine loop polls so the run ends and buffered frames flush. Safe from any thread — unlikemda.cancel(), which joins the runner and would self-deadlock when cleanup runs on the MDA worker thread (the primary’sfinishedpath).- Return type:
- start_live()[source]#
Begin continuous (untimed) sequence acquisition for preview.
- Return type:
None
- class mesofield.devices.OpenCVCamera[source]#
Bases:
BaseCamera,QThreadBackground-thread OpenCV camera capturing to MP4.
- Emits via
self.signals(amesofield.signals.DeviceSignals): signals.started/signals.finishedfor lifecycle.signals.data(idx, device_ts)per frame, consumed byDataManager.register_hardware_device().
- Plus Qt live-preview signals (GUI-only, decoupled from DataQueue):
frame_ready(np.ndarray)/image_ready(np.ndarray).
Inherits the common camera surface (identity, output paths, manifest metadata,
arm/set_sequencedefaults) fromBaseCamera, and runs its own capture loop on top ofQThread.- initialize()[source]#
Verify the camera opens and actually delivers a frame.
isOpened()is not proof on Windows: it returns True whileread()fails forever (wrong backend / capture format), which manifests as a camera that “initializes” but shows no live view. So we read a frame here and fail loudly if none arrives.- Return type:
- set_writer(make_path)[source]#
Resolve the output path and build the
CV2Writer.BaseCamera.set_writerresolvesoutput_path, constructs theCV2Writer(the project’s shared MP4 writer), and copies its sidecar path ontometadata_path. The capture loop drives the writer directly viabegin/add_frame/finish.
- start()[source]#
Spawn the capture thread and begin writing frames to MP4.
- Returns:
Trueif the thread started,Falseif it was already running.- Return type:
- Emits via
- class mesofield.devices.SerialWorker[source]#
Bases:
BaseSerialDeviceArduino wheel-encoder device.
- __init__(cfg=None, serial_port=None, baud_rate=None, sample_interval=None, wheel_diameter=None, cpr=None, development_mode=None, **kwargs)[source]#
- Parser#
alias of
WheelEncoder
- class mesofield.devices.EncoderSerialInterface[source]#
Bases:
BaseSerialDeviceTeensy encoder/treadmill device.
Constructor accepts either a cfg dict (
BaseSerialDevice-style) or legacy positional/keyword args(port, baudrate)for backward compatibility withmesofield.hardware.- Parser#
alias of
TreadmillSource
- class mesofield.devices.PsychoPyDevice[source]#
Bases:
SubprocessStimulusDeviceStimulus device that launches a PsychoPy script as a subprocess.
- 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:
- 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
- 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.
detailcarries the child’s last output (seeSubprocessSupervisor.output_tail). Logs by default; a GUI subclass shows a dialog.
- 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:
- class mesofield.devices.MousePortalDevice[source]#
Bases:
SubprocessStimulusDeviceStimulus device that launches MousePortal and feeds it treadmill velocity.
- serves_task(task, config)[source]#
Serve a task iff it matches the MousePortal config block’s
task.Each MousePortal configuration corresponds to a single task ID (a list is honored for the future multi-config case). With no
taskbound, MousePortal serves every task, preserving the single-stimulus behavior.- Return type:
- prepare(config)[source]#
Generate the MousePortal cfg.json and wire treadmill forwarding.
The output CSV path is the ExperimentConfig-authoritative path:
DataManager.setupassignsself.output_pathfromDataPathsbefore arm. We fall back toconfig.make_pathonly for standalone use (no DataManager). MousePortal is handed this exact file path and writes only there – it never constructs a BIDS directory layout.- Return type:
None
- launch_env()[source]#
Environment for the MousePortal subprocess.
Ensures Panda3D can find its
Config.prc(which carriesload-display pandagl). conda-forge’s panda3d on Windows installs the config under<env>\Library\etc\panda3dand the GL plugin under<env>\Library\bin, but Panda3D’s runtime auto-locator does not look there – so no display module loads and ShowBase aborts with “No graphics pipe is available!”. SettingPANDA_PRC_DIRpoints it at the right directory. Respects an existingPANDA_PRC_DIRand is a no-op when the directory cannot be found (e.g. macOS/pip layouts that already auto-locate correctly).
- preflight()[source]#
Return an actionable error string if MousePortal cannot launch.
- Return type:
str | None
- expected_experiment_duration()[source]#
Estimate the MousePortal experiment length in seconds.
Mirrors MousePortal’s per-trial resolution (condition override → global) and sums each trial’s duration plus the inter-trial interval that follows it. DURATION-ended trials are exact; DISTANCE/MANUAL trials are non-deterministic, so their per-trial
trial_duration(or the global default) is used as an estimate – pair this coupling with duration-based trials for a precise camera preallocation.- Return type:
- class mesofield.devices.MockEncoderDevice[source]#
Bases:
BaseDataProducerSynthetic encoder that records random click counts.
- class mesofield.devices.MockFrameProducer[source]#
Bases:
BaseCamera,BaseDataProducerSynthetic camera producing real OME-TIFF + frame metadata JSON.
- arm(config)[source]#
Per-run prep: set up the writer + (optionally) an MDA sequence.
The default body fits both MMCamera (which needs a sequence) and OpenCV/Mock (where
set_sequenceis a no-op). Subclasses override only when they need additional prep.- Parameters:
config (ExperimentConfig)
- Return type:
None
- snap()[source]#
Capture a single frame outside any recording, return it as an ndarray.
Used by the GUI’s snap button. Implementations should NOT alter recording state – snap is preview-only – but they SHOULD call
_save_snap_png()so each snap also lands a*_snap.png.- Return type:
- start_live()[source]#
Begin continuous live preview WITHOUT writing to disk.
Subscribers receive frames via
image_ready(Qt) /signals.data(psygnal). No recording side-effects; pair withstop_live().- Return type:
None
- stop_live()[source]#
End the continuous live preview started by
start_live().- Return type:
None