devices#

class mesofield.devices.BaseDevice[source]#

Bases: object

Default lifecycle skeleton for non-Qt hardware devices.

Constructor accepts an optional cfg mapping (the YAML stanza for this device). Common keys are auto-extracted:

  • id / device_id -> self.device_id

  • primary: true -> self.is_primary

A logger is created automatically as f"{module}.{class}[{device_id}]".

__init__(cfg=None, **kwargs)[source]#
Parameters:
Return type:

None

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:

list

class mesofield.devices.BaseDataProducer[source]#

Bases: BaseDevice

Base class for devices that stream samples to the DataQueue.

Subclasses produce data by calling record(), which timestamps, appends to an in-memory buffer, and emits signals.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.

__init__(cfg=None, **kwargs)[source]#
Parameters:
Return type:

None

record(payload, ts=None)[source]#

Buffer payload and emit signals.data.

Returns the timestamp used.

Parameters:
Return type:

float

arm(config)[source]#

Default arm: clear buffer and resolve output_path.

config is expected to expose make_path(name, ext, bids) (see mesofield.config.ExperimentConfig).

Parameters:

config (ExperimentConfig)

Return type:

None

class mesofield.devices.BaseSerialDevice[source]#

Bases: BaseDataProducer

Polling device for line-based serial protocols (Arduino/Teensy/etc.).

Subclasses override parse_line(). Optionally override setup_serial() for post-open initialisation (handshakes, buffer drain, configuring device-side parameters).

Configuration keys read from cfg:

  • port (str, required when development_mode=False)

  • baudrate (int, default 115200)

  • timeout (float, default 0.1) — pyserial readline timeout.

  • dtr (bool | None, default None) — set to False to suppress Arduino auto-reset on connect. None keeps 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_line becomes a no-op; the polling thread idles.

__init__(cfg=None, **kwargs)[source]#
Parameters:
Return type:

None

parse_line(line)[source]#

Decode one raw serial line into (payload, ts) or None.

Parameters:

line (bytes)

Return type:

Tuple[Any, float | None] | None

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) or bytes. newline is appended unless payload already ends with it. In development_mode the bytes are logged and discarded. Returns the bytes that were written (or would have been).

Parameters:
Return type:

bytes

class mesofield.devices.Nidaq[source]#

Bases: object

NIDAQ hardware control device.

This class implements the ControlDevice protocol via duck typing, providing all the necessary methods and attributes without inheritance.

initialize()[source]#

Initialize the device.

Return type:

None

arm(config)[source]#

No per-run prep needed.

Return type:

None

test_connection()[source]#

Pulse the configured DO line high for ~3 s as a connectivity test.

Logs the outcome; does not raise on failure (errors are surfaced via the logger).

reset()[source]#

Stop the worker thread (if running) and reset the NI-DAQ device.

start()[source]#

Begin counting edges on ctr and pulsing lines from a thread.

Configures both a counter-input task (rising-edge counts) and a digital-output task (camera trigger), then launches a worker thread that drives them on the configured poll_interval.

stop()[source]#

Signal the background thread to stop and wait for it.

This will also reset the NIDAQ device.

shutdown()[source]#

Close the device.

Return type:

None

get_data()[source]#

Retrieve a copy of the host-time exposure timestamps.

Return type:

list[float]

__init__(device_name, lines, ctr, io_type, device_id='nidaq', bids_type='', file_type='csv')#
Parameters:
Return type:

None

class mesofield.devices.MMCamera[source]#

Bases: BaseCamera, DataProducer, HardwareDevice

Micro-Manager-backed camera.

Inherits the common camera surface (identity, output paths, manifest metadata, arm / set_sequence defaults, status, calibration) from BaseCamera, and duck-types the DataProducer / HardwareDevice Protocols 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 standard DeviceSignals bundle and constructs a CustomWriter (OME-TIFF) or CV2Writer (MP4) for the output.

__init__(cfg)[source]#
Parameters:

cfg (dict)

set_sequence(build_mda)[source]#

Build the MDA sequence (Micro-Manager backend only).

Parameters:

build_mda (Callable[[DataProducer], Any])

initialize()[source]#

Apply the YAML properties block to the underlying camera.

Each {device_id: {property: value}} pair is forwarded to the backend (core.setROI for ROIs, core.setProperty otherwise) with special handling for the synthetic fps, viewer_type, and auto_contrast keys.

start_led_sequence(pattern)[source]#

Start the LED pattern.

If led_serial is configured on this camera, sends the configured raw byte sequences via MM’s SerialManager. Otherwise falls back to the original Arduino-Switch.State.loadSequence/startSequence path.

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 via self.signals.

Return type:

bool

stop()[source]#

Stop acquisition.

Non-primary Micro-Manager cameras must be told explicitly to halt their sequence acquisition — the primary camera’s MDA driver does not stop them.

Return type:

bool

get_data()[source]#

Return the latest captured frame, or None if not active.

snap()[source]#

Capture a single frame via mmcore.snap() and save a snapshot PNG.

start_live()[source]#

Begin continuous (untimed) sequence acquisition for preview.

Return type:

None

stop_live()[source]#

End the continuous sequence acquisition started by start_live.

Return type:

None

shutdown()[source]#

Cancel any in-flight MDA on the Micro-Manager backend.

info()[source]#

Return the verbose multi-line description (module path, MRO, properties).

Return type:

str

class mesofield.devices.OpenCVCamera[source]#

Bases: BaseCamera, QThread

Background-thread OpenCV camera capturing to MP4.

Emits via self.signals (a mesofield.signals.DeviceSignals):
  • signals.started / signals.finished for lifecycle.

  • signals.data(idx, device_ts) per frame, consumed by DataManager.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_sequence defaults) from BaseCamera, and runs its own capture loop on top of QThread.

__init__(cfg)[source]#
Parameters:

cfg (Dict[str, Any])

initialize()[source]#

Verify the camera can be opened. Returns immediately afterwards.

Return type:

bool

set_writer(make_path)[source]#

Resolve the output path and build the CV2Writer.

BaseCamera.set_writer resolves output_path, constructs the CV2Writer (the project’s shared MP4 writer), and copies its sidecar path onto metadata_path. The capture loop drives the writer directly via begin/add_frame/finish.

Parameters:

make_path (Callable[[str, str, str, bool], str])

Return type:

None

start()[source]#

Spawn the capture thread and begin writing frames to MP4.

Returns:

True if the thread started, False if it was already running.

Return type:

bool

stop()[source]#

Signal the capture thread to stop and wait for it to join.

Return type:

bool

shutdown()[source]#

Tear down the capture thread.

stop() joins the capture thread; its run() finally-block releases the CV2Writer and writes the sidecar JSON.

Return type:

None

snap()[source]#

Open the camera, read one frame, return it without recording.

Return type:

ndarray

start_live()[source]#

Start the capture thread WITHOUT a writer (preview-only).

Return type:

None

stop_live()[source]#

End preview capture started by start_live.

Return type:

None

get_data()[source]#

Return the most recent frame index/timestamp pair, or None.

Return type:

Dict[str, Any] | None

save_data(path=None)[source]#

No-op: the MP4 and its _frame_metadata.json sidecar are written by the CV2Writer when the capture loop finishes.

Parameters:

path (str | None)

Return type:

None

run(self)[source]#
Return type:

None

class mesofield.devices.EncoderSerialInterface[source]#

Bases: BaseSerialDevice

Teensy encoder/treadmill device.

Constructor accepts either a cfg dict (BaseSerialDevice-style) or legacy positional/keyword args (port, baudrate) for backward compatibility with mesofield.hardware.

__init__(cfg=None, port=None, baudrate=None, **kwargs)[source]#
Parameters:
Return type:

None

parse_line(line)[source]#

Decode one raw serial line into (payload, ts) or None.

Parameters:

line (bytes)

Return type:

Tuple[Dict[str, Any], float | None] | None

send_command(command)[source]#

Send a single-character firmware command (no newline).

Parameters:

command (str)

Return type:

bytes

Parser#

alias of TreadmillSource

class mesofield.devices.MockEncoderDevice[source]#

Bases: BaseDataProducer

Synthetic encoder that records random click counts.

__init__(cfg=None, **kwargs)[source]#
Parameters:
Return type:

None

class mesofield.devices.MockFrameProducer[source]#

Bases: BaseCamera, BaseDataProducer

Synthetic camera producing real OME-TIFF + frame metadata JSON.

__init__(cfg=None, **kwargs)[source]#
Parameters:
Return type:

None

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_sequence is 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:

ndarray

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 with stop_live().

Return type:

None

stop_live()[source]#

End the continuous live preview started by start_live().

Return type:

None

save_data(path=None)[source]#

Replay buffered frames through the standard mesofield writer.

Parameters:

path (str | None)

Return type:

str | None

property calibration: Dict[str, Any]#

Camera-specific constants worth recording in the AcquisitionManifest.

Subpackages#

Submodules#