Zapit runs a scanning opto-stimulation system for head-fixed mouse behaviour. A pair of galvo scanners steer a laser onto the exposed skull through a scan lens that also images the sample onto a camera. Zapit registers the scanners to the camera, registers stereotaxic coordinates onto the sample from two clicked reference points, and then plays clocked waveforms that hop the beam between the points of a stimulus condition at 40 Hz with the laser gated off while the mirrors move.
Zapit is written in Python, with a PyQt6 GUI and a ZeroMQ server so Bonsai drives trials. It is the Coen Lab's version of Zapit-Optostim/zapit by Maja Skretowska and Rob Campbell (SWC). Waveforms are locked sample for sample to reference data in the test suite. A rig moving from Zapit 1.x converts its files once with scripts/migrate_rig.py (see docs/settings.md).
| Part | Where | Notes |
|---|---|---|
| Core model | zapit/pointer.py (zapit.Zapit) |
Hardware, both calibrations, stimulus config, trial API, logging |
| Waveforms | zapit/waveforms.py |
One-cycle generation, finite trials, ramp-down, check patterns |
| Stimulus configs | zapit/stim_config.py |
YAML load/save, validation, atlas area names, pattern generator |
| Atlas | zapit/atlas.py, zapit/data/atlas.npz |
Top-down Allen atlas, 43 dorsal areas |
| Hardware | zapit/hardware/ |
nidaq.py (nidaqmx), pylon.py (Basler), simulated camera and DAQ |
| Server | zapit/server.py, zapit/client.py |
ZeroMQ REQ/REP, JSON. Protocol in docs/bonsai.md |
| GUI | zapit/gui/ |
Main window (live camera, calibrations, checks) and the atlas stim config editor |
| Bonsai | bonsai/ |
Example workflow: arm a trial over ZeroMQ, fire the TTL from a Harp board |
| CLI | zapit/cli.py |
zapit gui, zapit server, zapit pattern, zapit check-config |
Requirements: Windows 10/11, Python 3.11+, uv. For hardware: NI-DAQmx driver (any version nidaqmx supports) and the Basler pylon runtime.
git clone https://github.com/Coen-Lab/zapit
cd zapit
uv sync --all-extras
Try it without hardware:
uv run zapit gui --simulated
The simulated camera renders the beam spot where the simulated DAQ points it, with a hidden distortion, so the full scanner calibration, pointing checks and trial flow run and can be tested headless.
- Wire the DAQ: AO0 galvo X, AO1 galvo Y, AO2 laser analog control, AO3 masking LED, PFI0 the trigger input (a Harp digital output in the Bonsai example). AI0 is a photodiode, only needed for the laser power calibration. See
docs/hardware.md. - Moving a rig from Zapit 1.x: run
uv run python scripts/migrate_rig.pyonce. A new rig: start the GUI once withuv run zapit gui; it writes a default settings file to%USERPROFILE%\ZapitSettings\zapitSystemSettings.ymland refuses to stimulate until the laser mW range and both scales are set. Set the NI device name, camera scale (micronsPerPixel), scannervoltsPerPixel, laser min/max control volts and mW, then restart. Zapit never corrects the file: a mistake in it is named on the yellow strip under the camera view, and one in the laser range or either scale stops stimulation until it is fixed and Zapit restarted. Every key is described indocs/settings.md. - In the GUI: set the camera ROI if needed, run the scanner calibration (Calibrate scanners tab), click bregma and the reference point on the live image (Calibrate sample tab), load a stimulus config. Help, Quick start in the GUI walks through these steps, Help, Keyboard and mouse lists what a click does where, and every control's tooltip says what it does.
- Run trials from Bonsai (see
bonsai/README.md) or from Python:
from zapit.client import ZapitClient
z = ZapitClient("tcp://localhost:5555")
z.status()
r = z.send_samples(stim_duration_s=1.5) # random condition, waits for a TTL on PFI0
r["condition"], r["laser_on"]
z.send_samples(condition=2, hardware_triggered=False) # continuous, starts now
z.stop() # ramps the laser downOr drive the model directly in a Python session (zapit.Zapit, see docs/api.md).
Trials are logged automatically when an experiment path is set: a zapit_log_<time>.yml with every condition, the config file and the software version, and a zapit_trials_<time>.csv with one row per accepted trial. Loading a different config starts a new pair, so each trials file maps onto exactly one parameter log.
The ZeroMQ server listens on tcp://127.0.0.1:5555, so only programs on the same PC can drive the laser. To run Bonsai on another machine, set server.bind to tcp://*:5555.
The editor (File, New stim config) places points on a top-down atlas by clicking; zapit pattern writes grids, crosses and lines from the command line; zapit check-config file.yml validates a file against the current settings and prints the brain area under each point.
stimLocations01:
ML: [3.56, -3.56]
AP: [-1.24, -1.24]
Class: stimulate
Type: bilateral_points
Attributes: {laserPowerInMW: 2.0, offRampDownDuration_ms: 250.0}
stimModulationFreqHz: 40.0laserPowerInMW is time-averaged power per point: with two points each lit half the time, the laser runs at twice that while on.
uv run pytest
Everything runs against the simulated hardware. The waveform test compares every sample against tests/data/chan_samples_reference.npz, the reference waveforms for tests/data/uniAndBilateral_5_conditions.yml.
docs/plan.md: the port plan, architecture decisions, and what is not yet verified on hardwaredocs/hardware.md: wiring, DAQ and camera notesdocs/settings.md: every settings keydocs/api.md: the Python model APIdocs/bonsai.md: the ZeroMQ protocol for any clientbonsai/README.md: the example Bonsai workflow
GPL-3.0. The original design, the calibration approach and the waveform scheme are Maja Skretowska's and Rob Campbell's; the Python port is by the Coen Lab (UCL). Inspired by Guo et al. 2014 and Pinto et al. 2019.