base#
Base procedure classes for implementing experimental workflows in Mesofield.
This module defines a generic Procedure orchestrator that contains
zero device-specific logic. Custom experiment subclasses live outside the
package (typically under experiments/<name>/procedure.py) and are launched
via load_procedure(). A subclass points at its self-contained
experiment.json (params + embedded hardware rig) through the class-level
Procedure.experiment path.
Lifecycle (subclass hooks shown in bold):
initialize_hardware– bring devices upprerun– subclass hook (default: no-op)hardware.arm_all– per-run prep on every deviceconnect
hardware.primary.signals.finished->_cleanup_procedureon_started– subclass hook (default: no-op)hardware.start_allon_finished– subclass hook (default: no-op)save_data+ cleanup
- mesofield.base.processor(*, camera, plot=False, **plot_kwargs)[source]#
Mark a
Proceduremethod as a per-frame compute function.The decorated function is called as
func(self, img, idx, ts)and should return afloat | None. At procedure init the framework builds aFrameProcessorthat wraps it, attaches it to the hardware device whosedevice_idmatchescamera, registers it onDataManager, and (whenplot=True) tells the GUI to add aSerialWidget.plot_kwargsare forwarded straight to the widget — recognized keys:label,value_label,value_units,y_range,value_scale,max_points.Example:
class MyProcedure(Procedure): @processor(camera="meso", plot=True, label="Frame Mean") def frame_mean(self, img, idx, ts): return float(img.mean())
- class mesofield.base.ProcedureSignals[source]#
Bases:
QObjectAll procedure-level signals that a Qt GUI can connect to.
- class mesofield.base.Procedure[source]#
Bases:
objectGeneric orchestrator for a Mesofield experiment.
Subclass this in
experiments/<name>/procedure.pyand override the extension hooks (prerun(),on_started(),on_finished()) and/or the lifecycle methods (run(),save_data(),cleanup()) as needed. The base class never references a specific device type – multi-camera sync is driven by the YAMLprimary: trueflag andHardwareManager.start_all/stop_all.- __init__(config=None, *, hardware=None, experiment_directory=None, **params)[source]#
Build a procedure.
Parameters#
- config:
Path to a self-contained
experiment.json– experiment parameters plus an optional embeddedhardwarerig block. When omitted, the class-levelexperimentpath (if any) is used.- hardware:
Optional rig override: a path to a
hardware.yamlfile, an in-memory rig mapping, or a list of already-constructed device objects (e.g.Procedure(hardware=[LickDetector(port="COM3")])). A device list/mapping replaces any rig embedded in config; a lone device is the primary by default.Nonefalls back to the embedded rig or thedefine_hardware()hook.- experiment_directory:
Where acquisition data is written (
<dir>/data/sub-.../ses-...). Relative paths resolve against the current working directory. Overrides any value fromdefine_config/ JSON.- **params:
Any other experiment parameters (
subject,session,task,duration, …) set straight onto the config. These also overridedefine_config/ JSON.
- load_config(hardware=None, experiment=None)[source]#
Hot-load an experiment JSON and/or hardware YAML into the live config.
The JSON (params + any embedded rig) is applied first; an explicit hardware path then overrides whatever rig the JSON embedded. Callers pass explicit paths (the GUI wizard resolves them from its pickers).
- define_config()[source]#
Subclass hook to declare experiment parameters in Python.
Override to return a
@dataclassinstance or a plain mapping; it is applied toconfigviaExperimentConfig.load_dict(), superseding anyexperiment.json. DefaultNone-> load JSON.- Return type:
- define_hardware()[source]#
Subclass hook to construct hardware devices in Python.
Override to return a list of pre-built device objects (imported and instantiated in the procedure file). They are handed to a fresh
HardwareManager, superseding anyhardware.yaml. DefaultNone-> load YAML. Device classes should be decorated with@DeviceRegistry.register(...)so the setup can later be exported to ahardware.yamlrig file viaHardwareManager.to_yaml.- Return type:
- await_trigger()[source]#
Gate the run after arming and before starting devices.
When
start_on_triggeris set and astart_gatehas been injected (e.g. by the GUI ConfigController), it is invoked here to own the “ready / press to start” interaction; returningFalsecancels the run. Devices are armed but nothing has started yet, so blocking here holds the whole run. Headless runs with no gate do not block.This default contains no device-specific logic. Subclasses may still override it for a fully custom trigger.
- Return type:
None
- on_finished()[source]#
Subclass hook called immediately after the primary device finishes.
- Return type:
None
- property is_running: bool#
True while a run is in progress (started and not yet finished).
Used to refuse destructive actions mid-recording — e.g. a hardware hot-reload, which would tear down devices and abandon their writers.
- run()[source]#
Drive a standard experiment run.
Subclasses may override, but the default body is generic and handles any combination of devices declared in
hardware.yaml.- Return type:
None
- launch(*, splash=True)[source]#
Open the Mesofield GUI for this procedure and block until closed.
The ordinary-Python alternative to
mesofield launch: build the procedure in a script, then callproc.launch(). The acquisition is started from the GUI’s Record button (which callsrun()), so the window comes up ready to configure and record.Returns the Qt application exit code.
splash=Falseskips the ASCII splash screen. The heavy GUI dependencies are imported lazily so headless scripts that never call this keep a light import footprint.
- run_until_finished(timeout=None)[source]#
Run the procedure and block until cleanup completes.
Starts the procedure via
run(), then waits for theprocedure_finished(orprocedure_error) signal. HandlesKeyboardInterruptandtimeoutby invokingcleanup()automatically, so callers (e.g.__main__blocks in experiment scripts) do not need to wire up their own threading events.Parameters#
- timeout:
Optional hard ceiling in seconds. When
None(default), waits indefinitely for the primary device’sfinishedsignal. When provided, forces cleanup if the deadline passes.
Returns#
- bool
Trueif the procedure finished on its own,Falseif cleanup was forced by timeout or interrupt.
- mesofield.base.load_procedure(target, **params)[source]#
Build a
Procedurefor a launch target.target may be a canonical rig name, the literal
dev, or a path to aprocedure.py, anexperiment.json, ahardware.yaml, or a directory containing them.None(or an unresolvable name) opens in a default state for the Configuration Wizard.Directory precedence:
procedure.py(custom subclass) >experiment.json(self-contained config) >hardware.yaml(rig only).