Tutorial#
Mesofield — getting started#
This is the new-lab adoption path. The goal is to get from a fresh conda env to a working acquisition + analysis on your hardware in under an hour.
1. Install#
Mesofield ships the full pipeline in one package:
The acquisition layer (the
Procedureclass, hardware devices)The ingest layer (
mesofield.datakit, formerly the separatedatakitpackage)The processing layer (
mesofield.processing, wrappers for DLC, mesomap, lab pipelines)The shared schema (
mesokit-schema) is a direct dependency
conda create -n my-rig python=3.12 -y
conda activate my-rig
pip install -e /path/to/mesofield # editable install during development
# pip install mesofield # once it's on PyPI
For analysis-only machines (no Qt, no micromanager), the same install
works. The hardware-side deps (PyQt6, pymmcore-plus, nidaqmx,
tifffile, pyserial) live in the [rig] extra:
pip install -e /path/to/mesofield[rig]
2. Register this machine’s rig (one-time setup)#
A hardware.yaml is rig-specific — COM ports, camera ids, device
indices, Micro-Manager .cfg paths all pin it to one computer. Each machine
keeps a small store of named canonical configs (in the OS config directory):
mesofield rig new my-rig # writes a fill-out template; edit it
mesofield rig list # show registered rigs
# or, if you already have a working hardware.yaml:
mesofield rig add my-rig /path/to/hardware.yaml
Edit the template to declare this machine’s real devices. You only do this once per computer (or whenever the hardware changes).
3. Scaffold an experiment#
mesofield init my-experiment
cd my-experiment
init asks which hardware config to use:
a registered rig — its
hardware.yamlis copied into the experiment,dev— mock devices, runs on any machine with no real hardware,blank— a fill-out template (the default).
Skip the prompt with mesofield init my-experiment --rig my-rig.
You get:
my-experiment/
README.md # this file's smaller sibling, customised to your experiment
experiment.json # subject + protocol + duration
hardware.yaml # copied from the chosen rig (or a template)
procedure.py # your Procedure subclass
devices/
__init__.py
thermal_example.py # annotated custom-device template
The copied hardware.yaml belongs to the experiment — edit it freely for
experiment-specific tweaks without touching the canonical rig file.
Pick dev if you just want the next step to work with no hardware.
4. Run the acquisition#
python procedure.py
This calls MyProcedure(experiment.json).run_until_finished(). After
the duration cap fires (5s by default), mesofield:
Stops every device declared in
hardware.yaml.Calls
save_data()on each producer (CSV for serial devices, OME-TIFF + frame-metadata sidecar for cameras).Writes
data/sub-SUBJ01/ses-01/manifest.json— theAcquisitionManifest, a typed pydantic document declaring exactly what landed on disk: per-produceroutput_path,metadata_path,calibration,time_basis, optionaldataqueue_schema.
Open the manifest and you can see the contract the rest of the pipeline reads. No globbing.
5. Add your real hardware#
Edit hardware.yaml:
wheel:
type: wheel # was mock_wheel
primary: true
port: /dev/ttyUSB0
baudrate: 57600
cpr: 2400
diameter_mm: 80
output:
suffix: wheel
file_type: csv
bids_type: beh
camera:
type: camera # MMCamera (micromanager backend)
name: dev # micromanager device label
backend: micromanager
output:
suffix: meso
file_type: ome.tiff
bids_type: func
Built-in device types: camera, opencv_camera, wheel, encoder
(treadmill), psychopy, nidaq, plus the mock_wheel and
mock_camera examples.
6. Add a lab-specific device#
The scaffold ships devices/thermal_example.py as a working template.
The pattern: one Python file with both halves of the contract — the
producer (what writes data) and the parser (what reads it back).
# devices/thermal_example.py
from mesofield import DeviceRegistry
from mesofield.devices.base import BaseSerialDevice
from mesofield.datakit.sources.register import TimeseriesSource
@DeviceRegistry.register("thermal")
class ThermalSensor(BaseSerialDevice):
file_type = "csv"
bids_type = "beh"
data_type = "thermal"
def parse_line(self, line):
return float(line), None # (payload, timestamp_or_None)
class _ThermalParser(TimeseriesSource):
tag = "thermal"
patterns = ("**/*_thermal.csv",)
def build_timeseries(self, path, *, context=None):
df = pd.read_csv(path)
return df["timestamp"].to_numpy(), df, {"source_file": str(path)}
# Bind parser to producer so manifest-driven dispatch finds it.
ThermalSensor.Parser = _ThermalParser
In procedure.py, import the module to trigger registration:
from devices import thermal_example # noqa: F401
In hardware.yaml, add a stanza:
thermal:
type: thermal
port: /dev/ttyUSB1
baudrate: 115200
output:
suffix: thermal
file_type: csv
bids_type: beh
Re-run python procedure.py. The manifest now includes a third
producer for the thermal sensor; the parser is automatically reached
via ThermalSensor.Parser when ingest runs.
7. Ingest into a dataset#
from mesofield.datakit import Dataset
ds = Dataset.from_directory("./")
ds.save("processed/dataset.h5", format="hdf5")
This walks data/, reads the manifests, and writes
processed/<date>_dataset_mvp.h5 — a pandas DataFrame with a
(Subject, Session, Task) MultiIndex and (Source, Signal) columns.
8. Analyze with databench (optional)#
pip install databench
from databench import Project
proj = Project(dataset="processed/dataset_mvp.h5")
session = proj.session(subject="SUBJ01", session="01", task="demo")
9. Intermediate processing (DLC, mesomap, custom)#
For any file-to-file transformation between acquisition and ingest:
from mesofield.processing import ProcessorRunner
class MyPreprocessor(ProcessorRunner):
tool_name = "my_preprocessor"
tool_version = "0.1.0"
def run(self, inputs, *, threshold=0.5):
# ... read inputs[0], compute, write outputs ...
return [out_path]
# Call it:
outputs, manifest = MyPreprocessor()([tiff_path], threshold=0.7)
The runner hashes inputs, captures parameters, and emits
<tool_name>.process.json next to its outputs. Re-running with
different parameters produces a different manifest hash; the
provenance chain extends past acquisition.
Where things live#
mesofield.base.Procedure— orchestrates a run (lifecycle, manifest)mesofield.devices.base.BaseDataProducer/BaseSerialDevice— start here for new devicesmesofield.datakit.sources.register.TimeseriesSource— start here for new parsersmesofield.processing.ProcessorRunner— start here for intermediate processingmesokit_schema— the manifests themselves; you rarely import these directly
Retrofitting legacy data#
If you have sessions acquired before mesofield wrote manifests:
mesofield process retrofit-manifest /path/to/experiment
This walks the BIDS tree, reads timestamps.csv and configuration.csv,
and synthesizes an AcquisitionManifest for each session. Calibration
constants aren’t recoverable (they weren’t written), but everything
else round-trips. The legacy sessions become contract-compliant.