Documentation
Guides for installing, programming and operating Quatm.
From core to complete
Quatm Core is the open-source runtime for experiment control. It
provides the experiment lifecycle, physics models, servers and shared
utilities, and runs without a desktop environment.
Quatm Platform adds the desktop GUI, browser console, driver catalogue,
licensing and support for laboratories that need a complete operating
environment.
Quatm Systems extends the platform into the laboratory itself: real-time
controllers, lasers, high-field magnets, RF sources, integrated ion-trap and
ultracold-atom systems, and complete experimental systems.
Start here
Documentation should make an experiment feel smaller, not larger. These guides follow the way a real experiment comes to life: describe the apparatus, write the sequence, observe what happens, and keep what you learn.
If this is your first visit, begin with Getting started. It asks nothing of your hardware; simulation mode uses the same experiment model as the real apparatus. From there, follow the path that matches your work:
Getting started → Experiment editing → Device drivers → Data and analysis →
Application examples.
Documentation map
| Section | What you will learn |
|---|
| Architecture | Services, processes and the boundaries that keep an experiment legible |
| Getting started | Installation, project layout, device configuration and a first experiment |
| FAQ | Common installation, device, data and upgrade questions |
| Experiment editing | The Experiment lifecycle, parameter types and timing primitives |
| Experiment framework | Widgets, queue, scheduler, preparation station, looper and timeline internals |
| Utilities | Shared utilities and VDS helpers |
| Device drivers | Device modes, driver catalogue and custom driver development |
| Data and analysis | Data streams, archive structure, imaging and fitting |
| GUI components | Desktop applications and their responsibilities |
| Web remote control | Browser console, mobile operation and synchronisation |
| Communication layer | Message hubs, clients, properties and configuration |
| Physics models | Atom database, optical dipole traps, Stark shifts and scattering |
| Monitoring | Environmental and instrument-health monitoring |
| Deployment | Single workstation, split control/analysis machines and remote access |
| Best practices | Safe shutdown, single responsibility and reusable components |
| Examples | Complete MOT, absorption-imaging and ion-trap experiments |
| Contributing | Issues, pull requests and review expectations |
Architecture
Quatm is not a single program. Drivers, data managers, property logging, message routing, web access and the GUI can run as independent services, sharing one experiment model across a workstation or a network.
Read the architecture overview
1 - Architecture
How Quatm services, processes and clients fit together.
Distributed services
Quatm is distributed: drivers, data managers, property logging, message routing,
web access and the GUI run as independent operating-system processes connected
by local IPC or TCP.
flowchart TB
subgraph Clients[Clients]
GUI[Desktop GUI<br/>PySide6]
WEB[Web console<br/>browser / mobile]
end
BUS[(ZeroMQ event bus<br/>data · images · commands · properties)]
subgraph Control[Control]
PM[Process & task supervision]
EXP[Experiment runtime<br/>Experiment · scans · queue · looper]
end
subgraph Services[Services]
DATA[Data & image managers]
PROP[Property logger]
MSG[Message hub / logger]
HEALTH[Device health & leases]
WEBHUB[Web hub]
end
subgraph Hardware[Hardware]
DRV[Device drivers<br/>VISA · serial · TCP · USB · GPIB · DLL]
RT[FPGA / ADwin / real-time controllers]
end
GUI --> BUS
WEB --> BUS
BUS --> PM
BUS --> EXP
EXP --> DATA
EXP --> PROP
PM --> MSG
PM --> HEALTH
DATA --> DRV
EXP --> RT
DRV --> Hardware
WEBHUB --> WEB
MSG --> BUS
HEALTH --> DRVFailure isolation and network transparency
Every major component can run as an independent operating-system process.
Clients and services communicate over local IPC or TCP, so a control PC, a
data-analysis machine and a remote laptop can share one experiment without
sharing one process. The architecture is intentionally fail-isolated and
network-transparent: image processing does not block timing, and a slow
browser cannot stall acquisition.
For the deeper architecture description, see
Communication layer and the repository’s
quatm/docs/ directory.
2 - Getting started
Install Quatm, create a project and run a first experiment in simulation mode.
This guide takes you from a clean machine to a running experiment. You do not need any hardware: Quatm’s simulation mode exercises the same runtime and data path as a real device.
Edition note. The installation commands below set up Quatm Core, the open-source runtime. The desktop GUI, browser console and commercial driver catalogue are provided with Quatm Platform; contact sales@quatm.com for the platform distribution.
What you will do
- Install the framework and create a laboratory project.
- Understand the project directory and
device_db.py. - Write a small
Experiment class with parameters. - Run it from the desktop GUI or the web console.
- Inspect the generated HDF5 data and analysis.
Requirements
- Python 3.9 or newer.
- Windows 10 (1809+) or Windows 11 on x86-64; macOS 13 or newer; Linux x86-64 (Ubuntu 22.04/24.04, Debian 12, or an equivalent environment). ARM64 desktop support is limited; consult the Qt 6.11 supported-platform matrix.
- Optional: a supported FPGA/ADwin controller or VISA/serial/TCP/USB/GPIB instruments for real experiments.
Install
git clone https://github.com/quatm-team/quatm-core.git
cd quatm-core
python -m venv python_venv
source python_venv/bin/activate # Windows: python_venv\Scripts\activate
pip install -e .
pip install -e ".[hardware]" # optional hardware dependencies
Verify
from quatm.experiment import Experiment
class HelloQuatm(Experiment):
def build(self):
print("Quatm ready")
def run(self):
print("Hello, Quatm!")
HelloQuatm().run()
Create or open a project
A Quatm project contains configuration/, experiments/ and Data/. With Quatm Platform, a project normally includes a launcher; run it to open the laboratory environment.
If you start from an empty project, use the platform launcher:
python -m quatm.GUI.entries.start_quatm --root <project>
The first run creates a minimal configuration and opens the Operations Console. Open-source core users can also use the Python API directly and organise configuration/ and experiments/ in their own project.
Next steps
- Basic configuration — connect devices and understand the project files.
- Your first experiment — parameters, sequencing and execution.
- Experiment editing — the full runtime model.
- Device drivers — driver modes and custom instruments.
- FAQ — installation, device, data and upgrade questions.
2.1 - Basic configuration
How a project is discovered, how devices are described, and how services are configured.
Project root
Quatm resolves the project root in this order:
QUATM_PROJECT_ROOT (set by the project launcher);- the nearest parent directory containing
configuration/configfile/configfile.json or a .quatm_project marker; - the legacy repository layout.
A project keeps its own configuration and data; the framework package is installed separately. This lets several laboratories share one installation while keeping their files independent.
Directory layout
<project>/
├── configuration/
│ ├── configfile/configfile.json # services and GUI entries to launch
│ ├── device_db.py # instruments used by experiments
│ └── properties/ # persisted property tree
├── experiments/ # experiment scripts
├── Data/ # HDF5 acquisition files
├── drivers/ # optional project-local drivers
├── monitor/ # optional monitors
└── analysis/ # optional analysis scripts
Device database
configuration/device_db.py maps logical names to drivers. An experiment refers to these names, never to vendor APIs directly.
DEVICE_DB = {
'mot_coils': {
'type': 'attr',
'module': 'quatm.drivers.xxx',
'class': 'XxxDriver',
'address': 'tcp://192.168.1.50:5025',
'arguments': {'channel': 1},
},
'probe_laser': {
'type': 'local',
'module': 'drivers.probe_laser',
'class': 'ProbeLaser',
'arguments': {'port': 'COM4'},
},
}
The three modes are:
| Type | Meaning | Typical use |
|---|
local | Instantiate the driver as a full object | cameras, FPGA controllers, stages |
attr | Map one channel to a Python attribute | DAC/TTL outputs |
generic_attr | Call an arbitrary driver function | DDS tones, multi-channel devices |
A project-local module (for example drivers.probe_laser) takes precedence over a bundled driver with the same suffix. local_only / analysis_local_only can enforce that no bundled fallback is used.
Services
configuration/configfile/configfile.json declares the services and GUI programs for the project. The Operations Console reads this file, starts selected entries and tracks their state. Service processes are ordinary operating-system processes, so a crash is isolated and can be restarted independently.
Verify the setup
from quatm.servers.configreader import PROJECT_ROOT
print(PROJECT_ROOT)
Then open the Device Manager, select a device and use Test connection with simulation enabled. No hardware is required for this step.
2.2 - Your first experiment
Write, parameterise and run an experiment in simulation mode.
A minimal experiment
Put this file under the project’s experiments/ directory. Quatm discovers experiment classes from that tree.
from quatm.experiment import Experiment
class FirstExperiment(Experiment):
def build(self):
self.duration = self.NumberValue(100.0, unit='ms')
self.enabled = self.BoolValue(True)
self.phase = self.StringCombo('A', options=['A', 'B'])
def run(self):
if not self.enabled:
return
self.delay(self.duration)
build() is called once per experiment instance and declares the parameter panel. run() is called once per repetition.
Parameter types
| Type | Purpose |
|---|
NumberValue | numeric value with optional unit, limits and step |
BoolValue | boolean toggle |
StringCombo | selection from a fixed list |
setattr_device | register an instrument for this experiment |
Run it
- Open the Experiment Browser.
- Select
FirstExperiment.py. - Set
duration and enabled in the parameter panel. - Press Run.
- Watch the task in the Task Monitor; the Browser shows progress and results.
In simulation mode the run completes without hardware. Data, parameters and task metadata are archived to HDF5 under Data/.
Scans and repetitions
Use the browser’s scan controls to vary a parameter, set the number of repetitions, or submit a multi-dimensional scan. The framework expands the scan into individual runs and records the scan definition alongside the data.
Where to go next
2.3 - Known issues
Common problems encountered while installing and using Quatm, and their solutions.
Windows
FPGA controller driver
The vendor-provided SDK must be installed, and the FPGA requires the timetable to be defined and flashed according to the actual requirements. No real hardware is needed in simulation mode.
Linux
Qt GUI library dependencies
sudo apt install libxcb-cursor0 libxcb-xinerama0 libegl1-mesa
Camera permissions
Andor / BlackFly cameras require udev rules.
Windows Subsystem for Linux (WSL)
WSL users should make sure to run from a Linux filesystem mount; operating on the Windows filesystem causes unexpected errors.
Upgrading from an older version leaves the quatm.web subpackage missing
When upgrading from an older version, the Web Hub reports an error at startup saying the subpackage is missing: simply re-run pip install -e . (quatm.web is installed together with the quatm core).
Operating the same process from the desktop GUI at the same time
The desktop process manager and the web process page manage the same set of services/GUIs. Before starting an entry, either entry point first terminates the instance managed by the other entry point, so that nothing is launched twice; if a process is restarted unexpectedly, check whether a start action was performed on the other side.
2.4 - Frequently asked questions
Installation, devices, data and upgrade questions.
The questions below are the ones we hear most often — from a first installation to the quiet details that matter once an experiment is running.
Which operating systems are supported?
Quatm uses Qt 6/PySide6; supported platforms follow the Qt 6.11 matrix: Windows 10 (1809+) and Windows 11 on x86-64, macOS 13 or newer, and Linux x86-64 (Ubuntu 22.04/24.04, Debian 12, or equivalent). ARM64 desktop support is limited; see the Qt 6.11 supported-platform matrix. Python 3.9 or newer is required.
What are the minimum requirements?
A machine with at least 8 GB of memory and 4 CPU cores is recommended for the full GUI and data services. Real-time control requires an FPGA-class controller.
Devices and drivers
Do I need specific hardware?
No single vendor is required, but a real-time controller with TTL/DAC outputs is needed for timed sequences. Instruments using standard protocols can be integrated by selecting or writing a driver; the effort depends on the protocol and vendor SDK.
How do I integrate a custom device?
Write a driver class and register it in device_db.py as type: local (or map channels with attr / generic_attr). See Device drivers.
Which cameras are supported?
Andor, FLIR/BlackFly and other scientific cameras are covered by the driver catalogue; custom cameras can be added through project-local drivers.
Data
What format is used?
Experiment data is archived as HDF5, with optional streaming/export services.
How large are the files?
A typical absorption-imaging run is on the order of 10–50 MB, depending on image count and resolution. Plan disk capacity and retention for long campaigns.
Updating
How do I upgrade?
Update the Python environment and reinstall the package, or pull the repository and rerun the bootstrap. Check the changelog for incompatible changes.
Will my experiment scripts break?
Quatm follows semantic versioning for public interfaces; major changes are documented with migration notes. Verify in simulation before a campaign.
3 - Experiment editing
The Experiment lifecycle, parameters, timing and scans.
An experiment is a Python class derived from Experiment. The framework calls a small number of well-defined hooks, and provides the parameter and device model around them.
Lifecycle
| Stage | What happens |
|---|
build() | Declare parameters and devices; called once per instance |
run() | Execute one repetition of the experiment |
_start_run() / _end_run() | Framework bookkeeping around each run, including data markers |
close() | Release devices and clients |
Do not perform hardware work in build(): it may run while the user is editing parameters. Put all timed operations in run().
Parameters
Parameters are declared with typed values such as NumberValue, BoolValue, StringCombo, IntegerValue and ListValue. Each value carries metadata used by the GUI and by the scan engine:
- unit and display multiplier;
- minimum/maximum and step;
- whether the value is persisted;
- the list of allowed values for combos.
The same metadata drives the desktop parameter panel, the web dashboard and scan validation, so a scan cannot silently use an invalid value.
Timing
delay(seconds) advances the sequence clock. realtime() and the DAC/TTL attribute descriptors schedule outputs on the real-time controller. The experiment code describes what happens; the selected backend decides how to realise it on FPGA, ADwin or a simulated controller.
Keep hardware interactions inside run() and avoid Python-level busy loops. The runtime is designed so that a long analysis or GUI operation cannot block the timing path.
Scans and repetitions
A scan is a list of parameter values, optionally multi-dimensional, with an execution order (including randomised and snake orders). The queue turns a scan into individual task descriptions, each with the same parameter schema. Results are stored with the scan definition so the data remains self-describing.
Errors and cleanup
An exception in run() aborts the current run, executes framework cleanup, and marks the task failed. close() is called even after failures. Device leases and property clients are released deterministically; long-running experiments should rely on this cleanup rather than on process exit.
4 - Device drivers
The device model, driver catalogue and custom driver development.
Quatm separates experiment logic from instrument communication. Every instrument is addressed through a logical name in device_db.py; the driver decides whether that name maps to a serial port, a VISA resource, a TCP socket, a USB device or a vendor DLL.
Access modes
| Mode | Behaviour | When to use |
|---|
local | Constructs a driver object | cameras, FPGA controllers, translation stages |
attr | Maps a channel to a Python attribute | DAC/TTL outputs |
generic_attr | Calls a named driver function | DDS tones, multi-channel settings |
All modes support simulation. In simulation the same experiment code runs against a dummy backend, which is essential for developing sequences without occupying the apparatus.
Driver categories
The bundled catalogue covers:
- Real-time control — ADwin, FPGA, timing and trigger distribution.
- Lasers and light — Toptica, Moglabs, Vescent, laser locks and shutters.
- Signal generation and RF — Spectrum AWGs, Rigol, Valon, DDS boards.
- Scientific cameras — Andor, FLIR/BlackFly, PCO and generic video sources.
- Motion and positioning — PI stages, Thorlabs, piezo mirrors.
- DAQ and power supplies — NI, MCC, Keysight, Keithley.
- Environmental monitoring — temperature controllers, magnetometers, wavemeters, vacuum gauges.
The catalogue and per-driver verification status are tracked in the repository (docs/DRIVER_VERIFICATION.md). A driver is considered verified only after it has been exercised on real hardware.
Writing a driver
A driver is a normal Python class with:
__init__ accepting the arguments declared in device_db.py;simulating handling for offline use;- explicit
close() releasing sockets, serial ports and vendor handles; - optional
check() / self-test hooks used by the Device Manager.
Place project-specific drivers under <project>/drivers/. They are imported before bundled drivers with the same suffix, so a laboratory can pin a local version without forking the framework.
Device health and leases
Opening a device acquires a cross-process lease, preventing two experiments from silently driving the same instrument. The Device Manager can probe instruments, verify drivers and report the current owner. Bus-level locking protects adapters that cannot be opened concurrently.
4.1 - Scientific cameras
Scientific camera and image acquisition drivers for 15 brands including Andor, Basler, Hamamatsu and FLIR.
Overview
Quatm provides a unified ICamera interface wrapper for 15 major scientific camera brands worldwide, covering EMCCD, sCMOS, high-speed CMOS and industrial cameras.
Andor series: iXon EMCCD / Zyla sCMOS / Shamrock spectrograph
Overview
Quatm provides two Andor camera drivers that deliver high-performance imaging control through the native SDKs:
| Driver class | SDK | Applicable models | Interface |
|---|
AndorSDK2Camera | atmcd32d.dll (SDK2) | iXon EMCCD, Luca, Clara | PCIe / USB |
AndorSDK3Camera | atcore.dll (SDK3) | Zyla, Neo, Marana (sCMOS) | USB3 / CameraLink |
ShamrockSpectrograph | ShamrockCIF.dll | SR-303i, SR-500i and other spectrographs | USB |
All classes inherit from the io.camera interface and support ROI, binning, exposure control and frame buffer management.
Andor SDK2 (EMCCD) AndorSDK2Camera
iXon EMCCD Andor SDK2 series
| Communication | DLL (atm32cd32) |
| Readout mode | EM/conventional |
| ADC frequency | 1/5/10 MHz |
| Pixel depth | 16-bit |
| Cooling | TEC (≤ -100°C) |
Suited to extremely low light levels (single atom/ion fluorescence), achieving single-photon sensitivity through electron multiplication (EM Gain).
Driver module:
AndorSDK2Camera
Initialization
from quatm.drivers.andor.AndorSDK2 import AndorSDK2Camera
# connect the first camera
cam = AndorSDK2Camera(cam_id=0)
# acquisition parameters
cam.setup_acquisition(
exposure=0.1, # exposure time (s)
em_gain=200, # EM gain (0-1000)
readout_rate="5MHz",# readout rate
pre_amp_gain=2, # pre-amplifier gain
hsspeed=0, # horizontal transfer rate
temperature=-70, # target temperature (°C)
)
# acquire a single frame
cam.start_acquisition(nframes=1)
image = cam.read_multiple_images(nframes=1)
Key parameters
| Parameter | Description | Typical range |
|---|
exposure | Exposure time per frame | 0.001 – 3600 s |
em_gain | Electron multiplication gain | 0 – 1000 (linear or real gain mode) |
readout_rate | ADC readout frequency | 1MHz / 5MHz / 10MHz |
temperature | Target sensor temperature | -60 to -100°C (depends on cooler) |
pre_amp_gain | Preamplifier gain | 1× – 4× |
Andor SDK3 (sCMOS) AndorSDK3Camera
Zyla / Neo sCMOS Andor SDK3 series
| Communication | DLL (atcore) |
| Sensor | sCMOS |
| Interface | USB3 / CameraLink |
| Pixel depth | 12/16-bit |
| Frame rate | up to 100 fps (full resolution) |
Suited to fast sequence imaging and applications requiring a large field of view. Exposure, ROI and triggering are configured through the GenAPI property model.
Driver module:
AndorSDK3Camera
Initialization
from quatm.drivers.andor.AndorSDK3 import AndorSDK3Camera
cam = AndorSDK3Camera(cam_id=0)
cam.setup_acquisition(
exposure=0.05,
pixel_format="Mono16",
trigger_mode="Internal",
)
Andor Shamrock spectrograph ShamrockSpectrograph
Controls grating selection, centre wavelength and slit width:
from quatm.drivers.andor.Shamrock import ShamrockSpectrograph
spec = ShamrockSpectrograph(idx=0)
spec.set_wavelength(780.24) # centre wavelength (nm)
spec.set_grating(2) # select grating
spec.set_slit_width(50, port=1) # entrance slit (µm)
Device configuration
"emccd": {
"type": "local",
"module": "drivers.andor.AndorSDK2",
"class": "AndorSDK2Camera",
"arguments": {"cam_id": 0},
}
Calibration note
EMCCD gain must be calibrated for quantitative fluorescence measurements. Excessive gain introduces clock-induced charge (CIC) noise and non-linearity. We recommend establishing a gain–signal curve using fluorescent beads or a calibrated light source.
FLIR / Teledyne / Blackfly S / Grasshopper3
Overview
Quatm wraps the Spinnaker SDK in the TeledyneSpinnakerCamera class, supporting all GenICam/GenAPI-compatible FLIR/Teledyne USB3/GigE cameras.
Blackfly S / Grasshopper3 FLIR Spinnaker series
| Communication | Spinnaker SDK |
| Interface | USB3 / GigE |
| Pixel formats | Mono8/16, Bayer |
| Trigger | hardware/software |
| Frame rate | depends on model |
For general laboratory imaging: MOT monitoring, alignment beam imaging and equipment status monitoring.
Driver module:
TeledyneSpinnakerCamera
Initialization
from quatm.drivers.teledyne.spinnaker import TeledyneSpinnakerCamera
# list all available cameras
cam_list = TeledyneSpinnakerCamera.list_cameras()
# connect a camera by serial or index
cam = TeledyneSpinnakerCamera(cam_id="serial_xxx")
# or use an index
cam = TeledyneSpinnakerCamera(cam_id=0)
# configure and start acquisition
cam.setup_acquisition(
exposure=0.02, # 20 ms exposure
pixel_format="Mono8",
width=1920, height=1200,
offset_x=0, offset_y=0,
)
Key methods
| Method | Description |
|---|
list_cameras() | Enumerate all Spinnaker cameras in the system |
setup_acquisition(**kwargs) | Configure ROI, exposure, pixel format, etc. |
start_acquisition(nframes) | Start acquisition of the specified number of frames |
read_multiple_images(nframes) | Read the acquired images |
get_device_info() | Get the camera serial number, model and firmware version |
Common property operations
Camera parameters are read and written directly by GenAPI node name:
# Set gain
cam.set_attribute("Gain", 10.0)
# Enable auto exposure
cam.set_attribute("ExposureAuto", "Continuous")
# Get sensor temperature
temp = cam.get_attribute("DeviceTemperature")
Device configuration
"monitor_cam": {
"type": "local",
"module": "drivers.teledyne.spinnaker",
"class": "TeledyneSpinnakerCamera",
"arguments": {"cam_id": 0},
}
Multi-camera synchronization
The Spinnaker SDK supports hardware-triggered synchronization of multiple cameras. Setting TriggerMode = On and TriggerSource = Line0 enables nanosecond-level synchronized exposure through an external TTL signal.stop(): Stop capturing images.
quatm.drivers.blackfly.bfly.Camserver(name, serial)
class quatm.drivers.blackfly.bfly.Camserver(object):
def __init__(self, name, serial):
pass
def generateImageIndex(self):
pass
def run(self):
pass
def sendimage(self, im):
pass
def updateBusInfo(self):
pass
generateImageIndex(): In the current measurement, count the images. If the next run has started (either task, repetition or run changes), reset the counter (imgindex).
run(): Run loop running forever. Sends images from the camera to the imagestream and initiates reconfiguration of the camera in case properties have changed.
sendimage(im): Send Blackfly camera image via the imagestream. The image is converted to numpy array, additional information like binning, timestamp, etc., is added to a dictionary and both are sent to the imagestream.
- Args:
im - Blackfly image object. - Returns:
None
updateBusInfo(): Detect cameras on the bus and update info in properties if new camera has been found.
quatm.drivers.blackfly.bfly.pc2
class quatm.drivers.blackfly.bfly.pc2(object):
pass
- Dummy class in case driver is not installed.
Function:
Thorlabs TSI / uc480 scientific cameras
TLCamera ThorlabsTLCamera
TSI series Thorlabs scientific camera
| Communication | TLCamera SDK |
| Interface | USB3 |
| Sensor | sCMOS / CCD |
| Support | ROI, binning |
For high-frame-rate scientific imaging with SDK-level control.
Driver module:
ThorlabsTLCamera
from quatm.drivers.thorlabs.TLCamera import ThorlabsTLCamera
cam = ThorlabsTLCamera(serial="12345678")
cam.setup_acquisition(exposure=0.01, width=1024, height=768)
Basler — Pylon SDK series
Basler ace / dart Pylon SDK series
| Communication | Pylon SDK (GenICam) |
| Interface | USB3 / GigE |
| Sensor | CMOS |
| Pixel formats | Mono8/16, Bayer |
| Features | ROI, Binning, hardware trigger |
Industrial CMOS cameras, suited to routine laboratory imaging tasks such as MOT fluorescence monitoring and alignment beam imaging.
Driver module:
BaslerPylonCamera
from quatm.drivers.Basler.pylon import BaslerPylonCamera
# Enumerate all cameras
cams = BaslerPylonCamera.list_cameras()
cam = BaslerPylonCamera(cam_id=cams[0])
cam.setup_acquisition(exposure=0.02)
cam.start_acquisition(nframes=1)
Hamamatsu ORCA series sCMOS
ORCA-Fusion / ORCA-Flash4.0 Hamamatsu DCAM
| Communication | DCAM SDK (DLL) |
| Sensor | sCMOS |
| Pixels | 2048×2048 (Fusion) |
| Frame rate | up to 100 fps |
| Readout noise | < 1.6 e⁻ |
Ultra-low-noise sCMOS, suited to quantitative single atom/single ion fluorescence imaging.
Driver module:
DCAMCamera
from quatm.drivers.DCAM.DCAM import DCAMCamera
cam = DCAMCamera(idx=0)
cam.setup_acquisition(exposure=0.1, pixel_format="Mono16")
Photometrics Prime / Evolve series
Prime BSI / Evolve EMCCD Photometrics PVCAM
| Communication | PVCAM SDK (DLL) |
| Sensor | back-illuminated sCMOS / EMCCD |
| Interface | PCIe / USB3 |
| Pixel depth | 16-bit |
Back-illuminated sCMOS offers high quantum efficiency; EMCCD models provide single-photon sensitivity.
Driver module:
PVCAMCamera
from quatm.drivers.Photometrics.pvcam import PVCAMCamera
cam = PVCAMCamera(cam_name="Prime_BSI_95B")
cam.setup_acquisition(exposure=0.05)
Princeton Instruments PIXIS / ProEM
PIXIS / ProEM / IsoPlane Princeton PICam
| Communication | PICam SDK (DLL) |
| Sensor | CCD / EMCCD |
| Cooling | liquid nitrogen / TEC |
| Interface | PCIe / USB |
Spectroscopy-grade CCD/EMCCD cameras, commonly used together with Acton/IsoPlane spectrographs.
Driver module:
PicamCamera
from quatm.drivers.PrincetonInstruments.picam import PicamCamera
cam = PicamCamera(cam_id=0)
cam.setup_acquisition(exposure=1.0) # long-exposure spectrum acquisition
PCO edge / dimax high-speed cameras
edge / dimax / pco.panda PCO SC2
| Communication | SC2 SDK (DLL) |
| Interface | CameraLink HS / USB3 / GigE |
| Frame rate | up to several thousand fps |
| Sensor | sCMOS / high-speed CMOS |
Suited to fast dynamical processes: MOT loading dynamics and real-time observation of ion crystal reordering.
Driver module:
PCOSC2Camera
from quatm.drivers.PCO.SC2 import PCOSC2Camera
cam = PCOSC2Camera(interface="USB 3.0")
cam.setup_acquisition(exposure=0.001) # 1 ms high-speed acquisition
Allied Vision Alvium / Mako
Alvium / Mako / Bonito Allied Vision Vimba X
| Communication | VimbaX SDK (GenICam) |
| Interface | USB3 / GigE / CameraLink |
| Sensor | CMOS |
Compact industrial cameras, suited to space-constrained optical path integration.
Driver module:
AlliedVisionVimbaXCamera
from quatm.drivers.AlliedVision.VimbaX import AlliedVisionVimbaXCamera
cam = AlliedVisionVimbaXCamera(cam_id=0)
IDS uEye+ / Ensenso
uEye+ / Ensenso 3D IDS peak
| Communication | IDS peak SDK |
| Interface | USB3 / GigE |
| Sensor | CMOS |
General-purpose USB/GigE cameras, including the Ensenso 3D stereo camera.
Driver module:
GenericIDSPeakCamera
from quatm.drivers.IDS.peak import GenericIDSPeakCamera
cam = GenericIDSPeakCamera(cam_id=0)
Generic configuration pattern
All cameras are registered in device_db.py with type: "local":
"imaging_cam": {
"type": "local",
"module": "drivers.Basler.pylon",
"class": "BaslerPylonCamera",
"arguments": {"cam_id": 0},
}
Frame grabbers
For cameras with a Camera Link interface, Quatm also ships drivers for NI IMAQ, BitFlow and Silicon Software frame grabbers. They are used in the same way as camera drivers: all frame grabbers likewise inherit the io.camera interface.4.2 - Signal sources and RF
Drivers for frequency synthesizers and RF instruments: Valon 5009, DDS AD9959, Agilent, Rigol.
Overview
Quatm supports a broad range of RF instruments, from general-purpose function generators to vector network analyzers, covering frequency synthesis, arbitrary waveforms and S-parameter measurement.
Valon 5009 dual-channel microwave frequency synthesizer
Overview
The Valon 5009 is a dual-channel frequency synthesizer (23.5 MHz – 6 GHz) controlled over a serial port. Quatm wraps it in valon, which sets the frequency and power of the two outputs independently.
Valon 5009 Dual RF Synthesizer
| Communication | serial (RS232) |
| Frequency range | 23.5 MHz – 6 GHz |
| Channels | 2 |
| Output power | -30 ~ +10 dBm |
| Frequency resolution | 0.1 Hz |
Widely used in ion trap experiments to drive microwave transitions (such as the Ba⁺ 9.9 GHz hyperfine transition) or as an AOM drive source.
Driver module:
valon
Initialization
from quatm.drivers.valon.valon_quatm import valon
# Connect to Valon (default /dev/ttyUSB0)
valon = valon(port="/dev/ttyUSB0")
# Set channel 1 frequency to 500 MHz
valon.set_frequency(freq=500e6, source=1)
# Set channel 2 frequency to 9.925 GHz (external frequency multiplier required)
valon.set_frequency(freq=9.925e9, source=2)
# Send raw command
valon.sendRaw("LOCK 1 ON\r\n")
Key methods
| Method | Description |
|---|
set_frequency(freq, source) | Set the output frequency of channel 1 or 2 (Hz) |
sendCommand(source, command, value) | Send a formatted SCPI command |
sendRaw(cmd) | Send a raw string command |
Serial communication
Underneath, the Valon driver communicates through VSerialPort (a serial.Serial subclass). Typical parameters:
# serial parameters (driver defaults)
port = "/dev/ttyUSB0"
baudrate = 115200
timeout = 0.1
Use the Valon 5009 to drive the 9.925 GHz hyperfine transition and measure Rabi oscillations by scanning the frequency.
Key parameters
| Start frequency | 9.924 GHz |
| Stop frequency | 9.926 GHz |
| Step | 10 kHz |
| Repetitions per point | 100 |
# experiment snippet: frequency scan
import numpy as np
frequencies = np.arange(9.924e9, 9.926e9, 10e3)
for f in frequencies:
valon.set_frequency(freq=f, source=2)
# ... run the probe sequence ...
Reference: See ion trap experiments → quantum state detection
Frequency accuracy
The internal reference stability of the Valon 5009 is limited. For high-precision spectroscopy or quantum control experiments we recommend:
- an external 10 MHz rubidium clock reference
- or real-time monitoring of the output frequency with a HighFinesse wavemeter / spectrum analyzer
class quatm.drivers.valon.valon_prog.Valon_Standalone(name='Valon')
Bases: object
Standalone driver for the Valon frequency generator. Commands can be sent via the Commandhub and are processed in parallel to the experiment, thereby the experiment is not delayed by the Valon.
function quatm.drivers.valon.valon_prog.run()
DDS AD9959 four-channel direct digital synthesis
Overview
The AD9959 driver wraps the AD9959 evaluation board and controls four independent DDS channels over the SPI bus, providing precise frequency, amplitude and phase control.
AD9959 evaluation board 4-channel DDS
| Communication | SPI |
| Channels | 4 |
| Frequency resolution | 32-bit (0.12 Hz @ 500 MHz clock) |
| Phase resolution | 14-bit |
| Amplitude control | 10-bit DAC |
Widely used in cold atom/ion experiments for AOM driving, sideband generation and coherent manipulation.
Driver module:
AD9959
Initialization
from quatm.drivers.dds import AD9959
dds = AD9959()
# Configure channel 0 — cooling-light AOM
dds.set_frequency(0, 80e6) # 80 MHz
dds.set_amplitude(0, 0.5) # 50% amplitude
dds.set_phase(0, 0) # 0° phase
# configure channel 1 — repump AOM (+0.1 MHz offset)
dds.set_frequency(1, 80.1e6)
dds.set_amplitude(1, 0.3)
Key methods
| Method | Description |
|---|
set_frequency(ch, freq_hz) | Set the channel frequency (Hz) |
set_amplitude(ch, amp) | Set the channel amplitude (0–1 fraction) |
set_phase(ch, phase_deg) | Set the channel phase (°) |
update_all() | Update all channels at once |
Synchronized update
The AD9959 supports a synchronized update mode: first call the set_* methods for the four channels individually (buffered), then call update_all() to write everything to the hardware at once. This guarantees phase consistency between channels, which is essential for experiments such as sideband cooling that rely on precise phase relations.Device configuration
"aom_dds": {
"type": "local",
"module": "drivers.DDS",
"class": "AD9959",
}
Typical applications
| Application | Configuration |
|---|
| AOM drive | Single channel, fixed frequency |
| Raman transitions | Two channels, difference-frequency locking |
| Trap drive | Multi-channel phase control |
| Sideband cooling | Frequency modulation |
Alternative driver
An alternative AD9959 board driver with a serial (AT command) interface is also available:
from quatm.drivers.dds.alibaba import AD9959Board
Rigol DG1020Z two-channel function generator
DG1020Z
DG1020Z Rigol function generator
| Communication | VISA (USB/LAN) |
| Channels | 2 |
| Frequency range | 1 µHz – 25 MHz |
| Amplitude | 1 mVpp – 10 Vpp |
| Sampling rate | 200 MSa/s |
Used to generate AOM/EOM drive signals, modulation waveforms and custom arbitrary waveforms.
Driver module:
RigolDG1020Z
from quatm.drivers.AWG.specific import RigolDG1020Z
fg = RigolDG1020Z(addr="USB0::...")
fg.set_frequency(80e6)
fg.set_amplitude(1.0) # 1.0 Vpp
fg.enable_output(channel=1, enable=True)
fg.set_function(channel=1, function="SIN")
Other Rigol devices
DP1116A programmable DC power supply
DP1116A Rigol DC power supply
| Communication | VISA |
| Output | 16V/10A or 32V/5A |
| Channels | 1 |
| Resolution | 1 mV / 1 mA |
Used for coil current control, laser diode driving and circuit board supply.
Driver module:
DP1116A
from quatm.drivers.rigol.power_supply import DP1116A
ps = DP1116A(addr="USB0::0x1AB1::0x0E11::DP1A123456789::INSTR")
ps.set_voltage(12.0) # 12V
dc.set_current(0.5) # 500 mA current limit
ps.enable_output(True)
Oscilloscopes
Rigol DS series oscilloscopes are compatible with the Tektronix SCPI command set and are controlled through the ITektronixScope driver:
from quatm.drivers.tektronix.base import ITektronixScope
scope = ITektronixScope(addr="USB0::0x1AB1::0x04CE::DS123456789::INSTR", nchannels=4)
scope.set_timebase(scale=1e-6) # 1 µs/div
scope.set_vertical(channel=1, scale=0.5) # 0.5 V/div
waveform = scope.acquire(channel=1)
33500B / 33220A / 33250A Agilent/Keysight
| Communication | VISA (USB/LAN/GPIB) |
| Channels | 1–2 |
| Frequency | up to 120 MHz (33500B) |
| Resolution | 14-bit |
| Sampling rate | 250 MSa/s |
| Waveforms | sine/square/ramp/pulse/noise/ARB |
A laboratory standard signal source for AOM driving, modulation signals and reference clock generation.
Driver module:
Agilent33500 / Agilent33220A
from quatm.drivers.awg.specific import Agilent33500
awg = Agilent33500(addr="USB0::0x0957::0x2C07::MY52801234::INSTR")
# Configure an 80 MHz sine wave
awg.set_function(channel=1, function="SIN")
awg.set_frequency(80e6)
awg.set_amplitude(1.0) # 1 Vpp
awg.enable_output(channel=1, enable=True)
# Burst mode — generate N cycles, then stop automatically
awg.enable_burst(channel=1, ncycles=100)
Supported models
| Driver class | Applicable models |
|---|
Agilent33500 | 33500B, 33510B, 33520B, 33522B |
Agilent33220A | 33220A, 33250A |
RigolDG1000 | DG1022, DG1032 |
RigolDG1020Z | DG1022Z, DG1032Z |
Instek AFG2000 / AFG2225 dual channel
AFG-2225 / AFG-2100 Instek
| Communication | VISA |
| Channels | 1 (AFG2000) / 2 (AFG2225) |
| Frequency | up to 25 MHz |
| Sampling rate | 200 MSa/s |
A cost-effective two-channel arbitrary waveform generator.
Driver module:
InstekAFG2225 / InstekAFG2000
from quatm.drivers.awg.specific import InstekAFG2225
awg = InstekAFG2225(addr="USB0::...")
awg.set_frequency(10e6)
awg.set_amplitude(2.0)
awg.enable_output(channel=1, enable=True)
awg.enable_output(channel=2, enable=True)
AFG1022 / AFG1062 Tektronix
| Communication | VISA |
| Channels | 2 |
| Frequency | up to 60 MHz (AFG1062) |
| Sampling rate | 300 MSa/s |
| Arbitrary waveform | 64 MB memory |
A high-performance two-channel AWG with support for large custom waveforms.
Driver module:
TektronixAFG1000
from quatm.drivers.awg.specific import TektronixAFG1000
awg = TektronixAFG1000(addr="USB0::...")
awg.set_function(channel=1, function="ARB")
# Load a custom waveform...
Tektronix DPO/MSO series oscilloscopes
DPO2000 / MSO4000 series Tektronix oscilloscope
| Communication | VISA |
| Bandwidth | 100 MHz – 1 GHz |
| Channels | 2–4 |
| Sampling rate | up to 5 GS/s |
Automates waveform acquisition and parameter measurement over SCPI. Frequently used for experiment debugging and pulse sequence verification.
Driver module:
ITektronixScope
from quatm.drivers.tektronix.base import ITektronixScope
scope = ITektronixScope(
addr="USB0::0x0699::0x0401::C012345::INSTR",
nchannels=4
)
# Configure trigger
scope.set_trigger(source=1, level=0.5, slope="POS")
scope.set_timebase(scale=1e-6) # 1 µs/div
scope.set_vertical(channel=1, scale=0.1) # 0.1 V/div
# Acquire waveform
waveform = scope.acquire(channel=1)
Agilent E5071C vector network analyzer
E5071C Agilent VNA
| Communication | VISA |
| Frequency | 9 kHz – 20 GHz |
| Ports | 2–4 |
| Functions | S-parameters, impedance, Smith chart |
Used for RF component characterization: S-parameter measurement of filters, amplifiers, antennas and transmission lines.
Driver module:
AgilentE5071C
from quatm.drivers.Agilent.vna import AgilentE5071C
vna = AgilentE5071C(addr="TCPIP::192.168.1.100::INSTR")
vna.set_frequency_range(start=1e6, stop=1e9, points=1001)
vna.set_if_bandwidth(100) # 100 Hz IFBW
vna.set_averaging(count=10)
# Read S21 parameters
freq, s21 = vna.read_S_parameters(parameter="S21")
PicoVNA portable vector network analyzer
PicoVNA Pico Technology
| Communication | TCP/SCPI (port 5025) |
| Frequency | 300 kHz – 8.5 GHz |
| Ports | 2 |
| Dynamic range | 100 dB
|
A compact USB-controlled VNA, well suited to desktop RF debugging in the laboratory.
Driver module:
PicoVNA
from quatm.drivers.PicoTech.vna_scpi import PicoVNA
vna = PicoVNA(addr="localhost") # or IP address
vna.set_frequency_range(start=10e6, stop=500e6)
s_params = vna.read_S_parameters(parameter="S21")
Generic SCPI device configuration
"function_gen": {
"type": "local",
"module": "drivers.awg.specific",
"class": "Agilent33500",
"arguments": {"addr": "USB0::..."},
}
4.3 - Lasers and optics
Drivers for lasers and optical measurement: Cobolt, Toptica, Sirah Matisse, M² Solstis, HighFinesse wavemeters.
Overview
Quatm supports diode lasers, pump lasers, tunable ring lasers and optical measurement instruments. Laser-type devices are controlled over serial ports; SCPI instruments inherit the standard interface.
Hubner Cobolt MLD / DPL single-frequency lasers
Cobolt 06-01 series Hubner Cobolt
| Communication | serial (115200 baud) |
| Wavelength | 405/445/488/515/532/561/594/633/660/780/1064 nm |
| Power | 25–300 mW |
| Modulation | analog + digital TTL (up to 5 MHz) |
Compact DPSS/diode lasers for ion/atom cooling light, repump light and fluorescence excitation.
Driver module:
Cobolt
from quatm.drivers.Hubner.Cobolt import Cobolt
laser = Cobolt(conn="/dev/ttyUSB0")
# Turn on the laser
laser.set_autostart(True)
laser.set_power(50e-3) # 50 mW
# Digital modulation (TTL)
laser.set_digital_modulation(True)
laser.set_modulation_power(100e-3) # High power during modulation
# Read operating parameters
hours = laser.get_operating_hours()
temp = laser.get_base_temperature()
Toptica iBeam smart diode lasers
iBeam series Toptica
| Communication | serial (115200 baud) |
| Wavelength | 375–1550 nm |
Smart laser heads with a built-in controller, supporting automatic current/power calibration and status diagnostics.
Driver module:
TopticaIBeam
from quatm.drivers.Toptica.ibeam import TopticaIBeam
laser = TopticaIBeam(conn="/dev/ttyUSB0")
laser.set_power(channel=0, power=10e-3)
laser.enable(channel=0, enable=True)
Laser Quantum Finesse pump laser
Finesse Laser Quantum
| Communication | serial (19200 baud) |
| Power | 4–16 W (532 nm) |
| Noise | < 0.1% RMS |
| Application | Ti:sapphire/dye laser pumping |
An ultra-low-noise continuous-wave DPSS laser used to pump tunable ring lasers such as the Sirah Matisse.
Driver module:
Finesse
from quatm.drivers.LaserQuantum.base import Finesse
pump = Finesse(conn="/dev/ttyUSB0")
pump.set_power(10.0) # 10 W
pump.enable(True)
actual_power = pump.get_power()
Lighthouse Photonics Sprout G
Sprout G Lighthouse Photonics
| Communication | serial (19200 baud) |
| Wavelength | 532 nm |
| Power | 6–18 W |
| Noise | < 0.2% RMS |
A high-power 532 nm solid-state pump laser for Ti:sapphire lasers and optical dipole traps.
Driver module:
SproutG
from quatm.drivers.LighthousePhotonics.base import SproutG
sprout = SproutG(conn="/dev/ttyUSB0")
sprout.set_power(15.0)
sprout.enable(True)
Sirah Matisse tunable ring laser
Matisse Sirah Lasertechnik
| Communication | VISA or TCP/IP |
| Wavelength range | visible–near-infrared (depends on dye/Ti:sapphire) |
| Linewidth | < 50 kHz (locked) |
| Tuning elements | birefringent filter + thin/thick etalon + piezo |
A precision tunable ring laser system.
MatisseTuner coordinates the laser with a HighFinesse wavemeter to provide automatic wavelength tuning and stitched scans.
Driver module:
SirahMatisse / MatisseTuner
from quatm.drivers.Sirah.Matisse import SirahMatisse
matisse = SirahMatisse(addr="TCPIP::192.168.1.10::INSTR")
# Set wavelength
matisse.set_birefringent_filter(wavelength=780.24) # nm
matisse.set_thin_etalon(wavelength=780.24)
matisse.set_thick_etalon(wavelength=780.24)
# Piezo scan
matisse.set_piezo_voltage(50.0) # 0–100 V
# Automatic tuning (requires a wavemeter)
from quatm.drivers.Sirah.tuner import MatisseTuner
tuner = MatisseTuner(laser=matisse, wavemeter=wlm)
tuner.tune_to(wavelength=780.24) # Auto-search and lock
tuner.scan(start=780.20, stop=780.30, speed=0.01) # nm/s
M² Solstis narrow-linewidth tunable laser
Solstis M² Lasers
| Communication | TCP/IP JSON + WebSocket |
| Wavelength range | 700–1000 nm (Ti:sapphire) |
| Linewidth | < 50 kHz |
| Functions | etalon lock, cavity reference lock, wavemeter feedback |
A fully digital tunable laser system, completely remotely controllable through a TCP/IP JSON interface. It supports a WebSocket real-time status stream.
Driver module:
Solstis (ICE Bloc)
from quatm.drivers.M2.solstis import Solstis
solstis = Solstis(addr="192.168.1.20", port=8080)
# Wavelength tuning
solstis.set_wavelength(780.24)
# Etalon lock
solstis.lock_etalon()
# Terahertz scan (with the EMM module)
solstis.set_terahertz_scan(start_freq=0, stop_freq=100e9)
Optical power meters & wavemeters
Ophir Vega laser power/energy meter
Vega Ophir
| Communication | serial |
| Heads | thermopile/photodiode/pyroelectric |
| Power | µW – kW |
| Energy | µJ – J |
A versatile laser measurement instrument supporting continuous-wave power and pulsed energy measurement.
Driver module:
VegaPowerMeter
from quatm.drivers.Ophir.base import VegaPowerMeter
pm = VegaPowerMeter(conn="/dev/ttyUSB0")
power = pm.get_power()
HighFinesse WS6/WS7 precision wavemeters
WS6 / WS7 HighFinesse
| Communication | native wlmData.dll |
| Accuracy | 2–200 MHz (depends on model) |
| Channels | 1–8 |
| Measurement rate | up to 500 Hz |
| Wavelength range | 350–1120 nm |
Fizeau interferometer wavemeters for precision laser frequency measurement and feedback control. Multi-channel models can monitor the frequencies of several lasers simultaneously.
Driver module:
WLM
from quatm.drivers.HighFinesse.wlm import WLM
wlm = WLM(version=7, dll_path="C:\\wlm\\wlmData.dll")
# Read the current frequency of channel 1
freq = wlm.get_frequency(channel=1) # THz
wavelength = wlm.get_wavelength(channel=1) # nm
# Exposure control (auto-adapts to light intensity)
wlm.set_exposure(channel=1, mode="auto")
Matisse automatic tuning
The combination of a HighFinesse wavemeter and a Sirah Matisse is the archetypal laser frequency feedback control system in Quatm. The MatisseTuner driver coordinates the two to provide closed-loop automatic tuning and wide-range mode-hop-free scanning.
Thorlabs optical power meters PM100 / PM400
PM100 / PM400 series Thorlabs power meter
| Communication | VISA (USB) |
| Supported heads | photodiode/thermopile/pyroelectric |
| Wavelength range | depends on head |
| Power range | nW – W level |
Communicates with Thorlabs power meter heads over SCPI commands and supports a variety of sensor head types.
Driver module:
GenericPM
from quatm.drivers.Thorlabs.misc import GenericPM
pm = GenericPM(addr="USB0::0x1313::0x8078::P1234567::INSTR")
pm.set_wavelength(780.0) # 780 nm calibration
power = pm.get_power() # Current power (W)
4.4 - Motion and positioning
Drivers for precision motion and positioning: PI piezo nanopositioners, Attocube, Newport Picomotor, Thorlabs APT.
Overview
Quatm supports a wide range of motion platforms, from piezo nanopositioning to long-travel stepper motors, behind a unified IMultiaxisStage interface.
Physik Instrumente (PI) piezo nanopositioning
Overview
GenericPIController wraps serial communication with PI general-purpose controllers and controls a variety of PI piezo positioning platforms through the ASCII command set.
PI piezo controller E-xxx series
| Communication | serial (RS232) |
| Protocol | PI ASCII command set |
| Axes | depends on controller model |
| Accuracy | sub-nanometre |
Typical applications include fine focusing of high-NA objectives, automatic fibre coupling alignment, interferometer beam path adjustment and control of the beam waist position of optical dipole traps.
Driver module:
GenericPIController
Initialization
from quatm.drivers.PhysikInstrumente.base import GenericPIController
# serial connection
stage = GenericPIController(conn="/dev/ttyUSB0")
# home
stage.home(axis=0)
# absolute positioning (µm)
stage.move_to(axis=0, position=50.0)
# Relative move
stage.move_by(axis=1, delta=10.0)
# query current position
pos = stage.get_position(axis=0)
Key methods
| Method | Description |
|---|
home(axis) | Home the specified axis |
move_to(axis, position) | Absolute positioning (µm) |
move_by(axis, delta) | Relative move (µm) |
get_position(axis) | Query the current position |
stop(axis) | Emergency stop |
Device configuration
"objective_z": {
"type": "local",
"module": "drivers.PhysikInstrumente.base",
"class": "GenericPIController",
"arguments": {"conn": "/dev/ttyUSB0"},
}
Usage example
# In the Experiment class
self.pi_stage = self.get_device("pi_stage")
# Move to the target position (µm)
self.pi_stage.move_absolute(x=50.0, y=25.0, z=10.0)
# Relative move
self.pi_stage.move_relative(dx=1.0, dy=0.0, dz=0.0)
Note: piezo translation stages have a limited travel range, and exceeding it triggers hardware protection. Consult the device manual to confirm the maximum travel before use.
Thorlabs APT/Kinesis translation stages
KDC101 / K10CR1 series
KDC101 / K10CR1 / filter wheel Thorlabs APT/Kinesis
| Communication | serial (FTDI) |
| Protocol | APT (binary) |
| Applicable devices | DC servo motors, stepper motors, filter wheels, shutters |
A unified wrapper for Thorlabs APT protocol devices.
Driver module:
BasicKinesisDevice
from quatm.drivers.Thorlabs.kinesis import BasicKinesisDevice
stage = BasicKinesisDevice(conn="27200001")
stage.home()
stage.move_to(position=25.0) # mm
Elliptec piezoelectric resonant motors
ELLx series Thorlabs Elliptec
| Communication | serial (9600 baud) |
| Travel | depends on slider |
| Accuracy | µm level |
Compact piezoelectric resonant motors, suited to space-constrained optical mounts.
Driver module:
ElliptecMotor
from quatm.drivers.Thorlabs.elliptec import ElliptecMotor
motor = ElliptecMotor(conn="/dev/ttyUSB0", addrs=[0])
motor.move_to(axis=0, position=10.0)
Motorized filter wheels FW102/FW212
from quatm.drivers.Thorlabs.serial import FW
fw = FW(conn="/dev/ttyUSB0")
fw.set_position(position=2) # switch to filter position 2
KDC101 motorized translation stage
Control Thorlabs KDC101/T-Cube series motorized translation stages through the .NET SDK.
from quatm.drivers.thorlabs.kdc101 import KDC101
stage = KDC101("26001234") # Serial number
stage.home() # Home
stage.move_absolute(10.0) # Move to 10.0 mm
pos = stage.get_position() # Read current position
| Function | Description |
|---|
| Homing | Automatically seek the limit switch |
| Absolute move | Move to the specified position (mm) |
| Relative move | Move relative to the current position |
| Position readout | Read the current position in real time |
Attocube ANC300 / ANC350 nanopositioning
ANC300 Attocube
| Communication | TCP/IP (port 7230) |
| Axes | up to 7 |
| Travel | mm range |
| Accuracy | nm range |
| Suitable for | cryogenic / vacuum |
A modular nanopositioning controller, remotely controlled over TCP/IP. It is widely used for precise positioning of objectives and samples inside cryostats.
Driver module:
ANC300
from quatm.drivers.Attocube.anc300 import ANC300
anc = ANC300(conn=("192.168.1.30", 7230))
# home
anc.home(axis=0)
# Nanometer-level step
anc.move_by(axis=0, delta=0.1) # 100 nm
# Continuous scan mode
anc.set_continuous_mode(axis=1, enable=True)
anc.set_continuous_speed(axis=1, speed=100.0) # nm/s
ANC350 Attocube (USB)
| Communication | USB HID (PyUSB) |
| Axes | up to 3 |
| Control | direct USB connection |
USB variant, suited to desktop applications.
Driver module:
ANC350
from quatm.drivers.Attocube.anc350 import ANC350
anc = ANC350(conn=0) # First USB device
anc.move_to(axis=0, position=50.0)
Newport Picomotor 8742 4-axis controller
Picomotor 8742 Newport
| Communication | USB (PyUSB) or TCP/IP Telnet (port 23) |
| Axes | 4 |
| Step size | < 30 nm |
| Vacuum compatible | optional |
A piezoelectric inertial motor controller supporting direct USB or network control. Vacuum-compatible models can be used directly for adjusting optical elements inside a vacuum chamber.
Driver module:
Picomotor8742
from quatm.drivers.Newport.picomotor import Picomotor8742
# USB connection
pico = Picomotor8742(conn=0)
# Or TCP/IP
pico = Picomotor8742(conn="192.168.1.40", multiaddr=(0, 1))
pico.move_to(axis=0, position=5.0) # mm
pico.home(axis=0)
SmarAct MCS2 / SCU3D high-precision positioning
MCS2 SmarAct
| Communication | native MCS2 SDK |
| Axes | up to 18 |
| Accuracy | nm range |
| Speed | up to 20 mm/s |
| Vacuum/cryogenic | optional modules |
A modular high-precision positioning system supporting coordinated multi-axis motion and complex trajectories.
Driver module:
MCS2
from quatm.drivers.SmarAct.MCS2 import MCS2
mcs = MCS2(locator="usb:sn:12345678")
mcs.move_to(axis=0, position=10.0) # mm
mcs.get_position(axis=0)
SCU3D SmarAct 3-axis
| Communication | SCU3DControl SDK |
| Axes | 3 |
An integrated 3-axis controller for confined spaces.
Driver module:
SCU3D
from quatm.drivers.SmarAct.scu3d import SCU3D
scu = SCU3D(idx=0)
scu.move_to(axis=1, position=25.0)
Standa 8SMC stepper motor controllers
8SMC4 / 8SMC5 Standa
| Communication | serial (115200 baud) |
| Axes | 1–4 |
| Step resolution | depends on motor/lead screw |
| Application | long-travel translation stages, rotation stages |
An economical stepper motor controller, suited to long-travel coarse positioning on optical tables.
Driver module:
Standa8SMC
from quatm.drivers.standa.base import Standa8SMC
stage = Standa8SMC(conn="/dev/ttyUSB0")
stage.move_to(position=50.0) # mm
stage.home()
Performax Arcus Technology
| Communication | USB (DLL) or RS485 serial |
| Axes | up to 4 |
| Features | acceleration/deceleration profiles, limit detection |
A high-performance stepper motor controller providing smooth S-curve acceleration and deceleration.
Driver module:
GenericPerformaxStage
from quatm.drivers.arcus.performax import GenericPerformaxStage
stage = GenericPerformaxStage(idx=0, conn="USB")
stage.move_to(axis=0, position=100.0)
stage.set_acceleration(axis=0, accel=50.0) # mm/s²
Trinamic TMCM-1110 integrated stepper motor
TMCM-1110 Trinamic
| Communication | serial (9600 baud, TMCL protocol) |
| Features | StallGuard stall detection, CoolStep energy saving |
An integrated stepper motor module with a built-in controller and driver. Suited to simple rotation/translation tasks and beam path shutters.
Driver module:
TMCM1110
from quatm.drivers.trinamic.base import TMCM1110
motor = TMCM1110(conn="/dev/ttyUSB0")
motor.move_to(position=180.0) # Rotation (°)
motor.set_velocity(500.0) # °/s
"objective_z": {
"type": "local",
"module": "drivers.Attocube.anc300",
"class": "ANC300",
"arguments": {"conn": ("192.168.1.30", 7230)},
}
Multi-axis coordination
All io.multiaxisstage drivers support independent axis mapping. The muxaxis tool can combine physical axes into a logical coordinate system, enabling advanced features such as tilt correction and coordinate transformation.4.5 - DAQ and power supplies
Data acquisition and electrical measurement drivers: NI DAQ, Rigol/EA power supplies, Keithley/Voltcraft multimeters, oscilloscopes.
Overview
Data acquisition and power equipment form the infrastructure of experiment automation. Quatm provides a unified NI-DAQmx wrapper together with SCPI/Modbus instrument drivers.
Rigol DP1116A programmable DC power supply
DP1116A Rigol DC power supply
| Communication | VISA |
| Output | 16V/10A or 32V/5A |
| Channels | 1 |
| Resolution | 1 mV / 1 mA |
Used for coil current control, laser diode driving and circuit board supply.
Driver module:
DP1116A
from quatm.drivers.rigol.power_supply import DP1116A
ps = DP1116A(addr="USB0::0x1AB1::0x0E11::DP1A123456789::INSTR")
ps.set_voltage(12.0) # 12V
dc.set_current(0.5) # 500 mA current limit
ps.enable_output(True)
Rigol DP932 programmable DC power supply
Provides programmable DC power for a wide range of laboratory equipment.
from quatm.drivers.rigol.dp932 import RigolDP932
psu = RigolDP932("USB0::...")
psu.set_voltage(1, 5.0) # Channel 1: 5.0 V
psu.set_current(1, 1.0) # Channel 1: 1.0 A current limit
psu.output_on(1)
NI-SWITCH matrix/multiplexer switches
PXI-25xx / SCXI-11xx series NI-SWITCH
| Communication | NI-SWITCH API |
| Topology | matrix/multiplexer/general-purpose |
| Channels | depends on module |
| Switching speed | ms range |
| Maximum voltage | 300 V (depends on module) |
NI switch modules are controlled through the
niswitch Python API. They are suited to signal routing, multiplexed calibration and coil polarity reversal in experiments.
Driver module:
ni_switch
from quatm.drivers.NI.ni_switch import NISwitch
sw = NISwitch(resource_name="PXI1Slot2")
sw.connect(channel1="ch0", channel2="com0")
sw.disconnect(channel1="ch0", channel2="com0")
all_relays = sw.get_relay_count()
PXIe-657x / PXIe-655x series NI-Digital
| Communication | NI-Digital Pattern API |
| Channels | 32–128 |
| Rate | up to 200 MHz |
| Voltage | 1.2–5.5 V (programmable) |
| Features | pattern generation/acquisition, PMU parametric measurement |
NI digital waveform instruments are controlled through the
nidigital Python API. They generate and acquire high-speed digital waveforms for custom TTL timing protocols and chip-level digital testing.
Driver module:
ni_digital
from quatm.drivers.NI.ni_digital import NIDigital
dig = NIDigital(resource_name="PXI1Slot3")
dig.configure_voltage_levels(voltage=3.3)
pattern = [0b10101010] * 100 # 100-cycle digital waveform
dig.burst_pattern(pattern, frequency=10e6)
NI-DAQmx multifunction data acquisition
PCIe-63xx / USB-63xx / PXI series NI DAQ
| Communication | NI-DAQmx |
| Analog input | 16-bit, up to 2 MS/s |
| Analog output | 16-bit |
| Digital I/O | TTL, up to 10 MHz |
| Counters | 2–4 channels |
General-purpose multifunction DAQ cards for analog signal recording, timed digital I/O and photon counting.
Driver module:
NIDAQ
from quatm.drivers.NI.daq import NIDAQ
daq = NIDAQ(
dev_name="Dev1",
rate=100_000, # 100 kS/s
buffer_size=10_000,
)
# Analog input (continuous acquisition)
daq.configure_ai(channels=["ai0", "ai1"], voltage_range=(-10, 10))
daq.start_ai()
data = daq.read_ai(samples=1000)
# Analog output
daq.configure_ao(channels=["ao0"], voltage_range=(0, 5))
daq.write_ao(channel="ao0", voltage=3.3)
# Digital I/O
daq.configure_di(lines=["port0/line0"])
daq.configure_do(lines=["port0/line1"])
daq.write_do(line="port0/line1", value=True)
# Counters (photon counting / frequency measurement)
daq.configure_counter(
counter="ctr0",
mode="edge_count",
source="PFI0",
)
counts = daq.read_counter("ctr0")
Conrad relay board controller
Relay Board Conrad
| Communication | serial (19200 baud, binary protocol) |
| Channels | 8 relays per board, daisy-chainable |
| Contacts | AC/DC switching |
An economical multi-channel relay controller suited to coarse switching: magnetic trap coil polarity reversal, device power switching and laser shutter control.
Driver module:
RelayBoard
from quatm.drivers.Conrad.base import RelayBoard
# Single board
rb = RelayBoard(conn="/dev/ttyUSB0", start_addr=0)
rb.set_relay(relay=0, state=True) # Close relay 1
rb.set_relay(relay=3, state=False) # Open relay 4
# Byte write (controls 8 channels at once)
rb.write_byte(0b10101010)
Elektro Automatik PS2000B high-power supply
PS2000B series Elektro Automatik
| Communication | serial (115200 baud) |
| Power | 640 W – 15 kW (depends on model) |
| Voltage | up to 1500 V |
| Current | up to 510 A |
| Modes | constant voltage/current/power |
High-power programmable DC supply for driving large currents in magnetic trap coils and for Feshbach resonance field coils.
Driver module:
PS2000B
from quatm.drivers.ElektroAutomatik.base import PS2000B
ps = PS2000B(conn="/dev/ttyUSB0", remote_mode=True)
# Constant-current mode — magnetic field coil
ps.set_current(50.0) # 50 A
ps.set_voltage_limit(20.0) # 20 V voltage limit
ps.enable_output(True)
# Read actual values
actual_v = ps.get_voltage()
actual_i = ps.get_current()
Keithley 2110 benchtop digital multimeter
2110 Keithley
| Communication | VISA (USB/LAN) |
| Functions | DCV/ACV/DCI/ACI/resistance/capacitance/frequency/temperature |
| Accuracy | 5½ digits |
| Sampling rate | up to 50 kS/s |
A precision benchtop multimeter for electrical characterization and sensor calibration.
Driver module:
Keithley2110
from quatm.drivers.Keithley.multimeter import Keithley2110
dmm = Keithley2110(addr="USB0::0x05E6::0x2110::1234567::INSTR")
# DC voltage measurement
voltage = dmm.measure_voltage(range="10V")
# 4-wire resistance measurement
resistance = dmm.measure_resistance_4wire(range="100Ω")
# Temperature (thermocouple)
temp = dmm.measure_temperature(tc_type="K")
Voltcraft VC7055 two-channel benchtop multimeter
VC7055 Voltcraft
| Communication | serial (115200 baud, SCPI) |
| Channels | 2 |
| Functions | DCV/ACV/DCI/ACI/resistance/frequency/capacitance |
| Bluetooth | optional (VC7055BT) |
A two-channel benchtop multimeter; the BT variant with built-in Bluetooth supports wireless data acquisition.
Driver module:
VC7055
from quatm.drivers.Voltcraft.multimeter import VC7055
dmm = VC7055(addr="/dev/ttyUSB0")
# Simultaneous two-channel measurement
v1 = dmm.measure_voltage(channel=1)
v2 = dmm.measure_voltage(channel=2)
Device configuration
"daq": {
"type": "local",
"module": "drivers.NI.daq",
"class": "NIDAQ",
"arguments": {"dev_name": "Dev1", "rate": 100_000},
},
"coil_ps": {
"type": "local",
"module": "drivers.ElektroAutomatik.base",
"class": "PS2000B",
"arguments": {"conn": "/dev/ttyUSB0", "remote_mode": True},
},
"dmm": {
"type": "local",
"module": "drivers.Keithley.multimeter",
"class": "Keithley2110",
"arguments": {"addr": "USB0::..."},
},
4.6 - Environmental monitoring
Drivers for temperature, vacuum and cryogenic monitoring: Lakeshore, Cryocon, Pfeiffer, Leybold.
Overview
Laboratory environmental monitoring underpins the stability of quantum experiments. Quatm ships drivers for temperature, vacuum and cryogenic level monitoring with real-time data logging and anomaly alerts.
Lakeshore 218 temperature monitor
218 Lakeshore
| Communication | serial (9600 baud, 7E1) |
| Channels | 8 |
| Sensors | diode (Si/GaAs), platinum resistance (Pt100/1000), Cernox, RuO₂ |
| Temperature | 1.4 K – 1500 K (depends on sensor) |
The standard temperature monitor for cryogenic physics experiments. The eight channels are configured independently and support diode and resistance thermometers as well as Cernox thin-film resistors.
Driver module:
Lakeshore218
from quatm.drivers.Lakeshore.base import Lakeshore218
ls = Lakeshore218(conn="/dev/ttyUSB0")
# Configure channel 1 as a silicon diode
ls.set_sensor_type(channel=1, sensor_type="DT-670")
# Read temperature
temp = ls.get_temperature(channel=1) # Kelvin
all_temps = ls.get_all_temperatures() # All 8 channels
# Read raw sensor values (voltage/resistance)
raw = ls.get_reading(channel=1)
Cryocon 12C / 14C / 18C temperature controllers
12C / 14C / 18C Cryocon
| Communication | serial (9600 baud) |
| Channels | 2–4 (depends on model) |
| Supported sensors | diode/Pt/Cernox |
Cryogenic temperature controllers with PID heater output for precise temperature stabilization.
Driver module:
Cryocon1x
from quatm.drivers.Cryocon.base import Cryocon1x
cryo = Cryocon1x(conn="/dev/ttyUSB0", nchannels=4)
# Read channel 1 temperature
temp = cryo.get_temperature(channel=1)
# PID setpoint
cryo.set_setpoint(channel=1, temperature=4.2) # 4.2 K
cryo.set_heater_range(channel=1, range="low")
cryo.enable_heater(channel=1, enable=True)
Lumel RE72 · Omron E5_C temperature controllers
RE72 / E5_C Lumel / Omron
| Communication | RS485 Modbus RTU |
| Features | PID autotuning, ramp/soak programs |
| Application | optical table temperature stabilization, laser crystal temperature control |
Industrial-grade PID temperature controllers communicating over the Modbus RTU protocol. Commonly used for laser crystal temperature control and optical table environmental temperature control.
Driver module:
LumelRE72Controller / OmronE5xCController
from quatm.drivers.Lumel.base import LumelRE72Controller
from quatm.drivers.Omron.base import OmronE5xCController
# Lumel RE72
lumel = LumelRE72Controller(conn="/dev/ttyUSB1", daddr=1)
temp = lumel.get_temperature()
lumel.set_setpoint(25.0)
# Omron E5_C
omron = OmronE5xCController(conn="/dev/ttyUSB1", daddr=2)
omron.set_setpoint(30.0)
omron.enable_heater(True)
Pfeiffer TPG2xx high-vacuum gauges
TPG2xx series Pfeiffer Vacuum
| Communication | serial (9600 baud) |
| Channels | 2 |
| Supported gauges | Pirani / Cold Cathode / combination gauge |
| Range | atmospheric pressure – 10⁻¹⁰ mbar |
A compact two-channel vacuum gauge controller supporting both rough vacuum (Pirani) and ultra-high vacuum (cold cathode) gauge heads.
Driver module:
TPG2xx
from quatm.drivers.Pfeiffer.base import TPG2xx
tpg = TPG2xx(conn="/dev/ttyUSB0")
# Read both pressure channels
p1 = tpg.get_pressure(channel=1) # mbar
p2 = tpg.get_pressure(channel=2)
# Gauge type identification
gauge_type = tpg.get_gauge_type(channel=1)
Leybold ITR wide-range vacuum gauges
ITR series Leybold
| Communication | serial (9600 baud, binary protocol) |
| Range | atmospheric pressure – 5×10⁻¹⁰ mbar |
| Output | continuous pressure data stream |
Wide-range Pirani/cold cathode combination vacuum gauges that actively push pressure data.
Driver module:
GenericITR
from quatm.drivers.Leybold.base import GenericITR
itr = GenericITR(conn="/dev/ttyUSB0")
pressure = itr.get_pressure() # mbar
Agilent XGS-600 multi-channel vacuum controller
XGS-600 Agilent
| Communication | serial (RS485) |
| Channels | up to 6 gauge heads |
| Gauge types | Hot Filament / Cold Cathode / Pirani / Convection |
A multi-gauge controller for large vacuum systems, addressed over RS485 in a daisy chain.
Driver module:
XGS600
from quatm.drivers.Agilent.pressure import XGS600
xgs = XGS600(conn="/dev/ttyUSB0", addr=1)
# Read all channel pressures
pressures = xgs.get_all_pressures()
p1 = xgs.get_pressure(channel=1)
Cryogenic level monitoring
Cryomagnetics LM500 liquid helium/nitrogen level meter
LM500 / LM510 Cryomagnetics
| Communication | serial (9600 baud) |
| Functions | He/N₂ level, temperature, sample chamber temperature |
| Channels | 1–2 level + 4 temperature |
The standard instrument for monitoring levels in superconducting magnet dewars. It monitors liquid helium/nitrogen levels and the sample chamber temperature simultaneously.
Driver module:
LM500
from quatm.drivers.Cryomagnetics.base import LM500
lm = LM500(conn="/dev/ttyUSB0")
helium_level = lm.get_helium_level() # %
nitrogen_level = lm.get_nitrogen_level() # %
sample_temp = lm.get_temperature()
American Magnetics 1700 level monitor
1700 American Magnetics
| Communication | serial or TCP/IP (port 7180) |
| Functions | He / N₂ level, relay control |
A level monitor supporting both serial and network interfaces.
Driver module:
AM1700
from quatm.drivers.AmericanMagnetics.base import AM1700
am = AM1700(conn="192.168.1.50") # TCP/IP
# Or serial port: am = AM1700(conn="/dev/ttyUSB0")
level = am.get_helium_level()
Generic environmental monitoring configuration
"chamber_pressure": {
"type": "local",
"module": "drivers.Pfeiffer.base",
"class": "TPG2xx",
"arguments": {"conn": "/dev/ttyUSB0"},
},
"cryo_temp": {
"type": "local",
"module": "drivers.Lakeshore.base",
"class": "Lakeshore218",
"arguments": {"conn": "/dev/ttyUSB1"},
},
Automated monitoring
Together with Quatm’s DataStreamMonitor and UpdatingPlot components, vacuum, temperature and level data can be visualized in real time, and threshold alerts can be configured to pause the experiment automatically.4.7 - Experiment control and others
Drivers for FPGA controllers and auxiliary devices such as ADwin, NI-FPGA and Red Pitaya, covering the full spectrum of experiment control requirements from nanosecond real-time timing to general-purpose switching logic.
Overview
FPGA controllers and auxiliary devices are an essential part of experiment automation: from nanosecond real-time timing to relay switching, Quatm provides a unified control interface. ADwin, NI-FPGA, Red Pitaya and several other brands are currently supported.
ADwin
Overview
ADwin is a family of real-time controllers from Jaeger Computergesteuerte Messtechnik, based on an FPGA/DSP architecture, that executes real-time tasks independently of the PC operating system.
ADwin Gold II / Pro II
| Communication | PCI / USB |
| Processor | DSP + FPGA |
| Timing precision | nanosecond level |
| Analog input | 16-bit |
| Digital I/O | TTL (5V) |
| Memory | 32 MB DRAM |
It runs real-time processes compiled from ADbasic and communicates with Quatm through shared memory or register reads and writes. Typical applications include laser cooling timing, MOT magnetic field switching and photon counting acquisition.
Driver module:
ADwin
How it works
PC (Quatm) ADwin (real-time)
┌──────────┐ PCI/USB ┌──────────────┐
│ experiment│ ←──────────→ │ ADbasic proc │
│ .py │ Par/Data │ ns timing+acq│
└──────────┘ └──────────────┘
Quatm experiment scripts read and write parameters and data arrays through the ADwin driver, interacting with the ADbasic process running on the ADwin. The timing-critical parts execute on the ADwin hardware, while the Python side handles parameter setting and result readout.
Initialization
from quatm.drivers.fpga import ADwin
# Connect to ADwin (PCI bus)
adw = ADwin(device_number=1)
# Load and start the ADbasic process
adw.load_process("experiment_sequence.TB1")
adw.start_process()
# Set experiment parameters
adw.set_par(1, 100_000) # Par_1 = 100 µs cooling time
adw.set_par(2, 50) # Par_2 = 50 cycles
adw.set_par(3, 10_000) # Par_3 = 10 ms detection time
# Start one experiment sequence (triggered by an event in ADbasic)
adw.trigger()
# Read acquired data
photons = adw.get_data(1, data_length=1000) # Read photon counts from Data_1
Key methods
| Method | Description |
|---|
load_process(binary_path) | Load an ADbasic-compiled binary process |
start_process() | Start the loaded process |
stop_process() | Stop the running process |
set_par(index, value) | Write an integer parameter (1–80) |
set_fpar(index, value) | Write a floating-point parameter (1–80) |
get_par(index) | Read an integer parameter |
get_data(index, data_length) | Read a data array (1–200) |
set_data(index, values) | Write a data array |
trigger() | Send a software trigger event |
Typical ADbasic process structure
' ADbasic code snippet — experiment timing
Init:
Par_1 = 100000 ' Cooling time (µs)
Par_2 = 50 ' Number of cycles
Event:
For i = 1 To Par_2
P2_DAC(1, 5000) ' Set AOM power
P2_DAC(2, 800) ' Set magnetic field
' ... wait, acquire, switch ...
Next i
Device configuration
"fpga": {
"type": "local",
"module": "quatm.drivers.fpga",
"class": "ADwin",
"arguments": {"device_number": 1},
}
Development and debugging
During development, experiment logic can be tested with the FPGADummy simulator (no hardware required). The simulator exposes the same API but performs no actual hardware operations, which makes it suitable for developing and validating experiment scripts on an office computer.Exception:
quatm.drivers.fpga.client module
Class:
quatm.drivers.fpga.units module
NI-FPGA LabVIEW FPGA modular instruments
PXIe-78xxR / PCIe-78xxR series NI-FPGA
| Communication | NI-FPGA Interface Python API |
| FPGA | Xilinx Kintex/Virtex |
| Digital I/O | up to 1 Gb/s |
| Analog I/O | up to 3.2 GS/s |
| Programming | LabVIEW FPGA + Python runtime control |
Controls LabVIEW FPGA bitstreams at runtime through the
nifpga Python API. Suited to custom digital signal processing requiring very high throughput and deterministic timing.
Driver module:
ni_fpga
from quatm.drivers.NI.ni_fpga import NIFPGA
fpga = NIFPGA(resource_name="RIO0", bitfile="custom_logic.lvbitx")
fpga.run()
fpga.write_control("pulse_width", 100) # ns
fpga.write_control("trigger_delay", 50)
fpga.read_indicator("photon_count")
Comparison with ADwin
| Feature | ADwin | NI-FPGA |
|---|
| Programming language | ADbasic (BASIC-like) | LabVIEW FPGA (graphical) |
| Python control | Parameter/data array read-write | Control/indicator read-write |
| Suitable for | Nanosecond timing, photon counting | High-speed DSP, custom protocols |
| Hardware ecosystem | Standalone controller | Integrated in the NI PXI platform |
Conrad relay board
Relay Board Conrad
| Communication | serial (19200 baud) |
| Channels | 8 relays per board |
| Support | daisy-chain expansion |
An economical multi-channel relay controller, suited to magnetic trap coil polarity switching, device power switching and laser shutter control.
Driver module:
RelayBoard
from quatm.drivers.Conrad.base import RelayBoard
rb = RelayBoard(conn="/dev/ttyUSB0", start_addr=0)
rb.set_relay(relay=0, state=True)
rb.set_relay(relay=3, state=False)
rb.write_byte(0b10101010)
Development and debugging
During development, experiment logic can be tested with the FPGADummy simulator, without connecting real hardware.5 - Data and analysis
Data streams, HDF5 acquisition, imaging and fitting.
Data path
Experiment runs publish typed data and image streams over ZeroMQ. Data clients subscribe to the channels they need; the Data Manager accumulates runs, writes HDF5 files and exposes live series to the GUI and web console. The message and property Hubs provide the same data to every client, so a plot, a notebook and a remote browser see one coherent experiment.
HDF5 structure
A run is archived with:
- the experiment and task identifiers;
- parameter values and scan definition;
- DAC/TTL settings and sequence metadata;
- scalar data series and image data;
- save-acknowledgement status and error information.
The file is self-describing: a run can be reconstructed and compared without the original GUI session.
Analysis modules
Quatm includes reusable analyses for common quantum-physics measurements:
- absorption imaging and optical density;
- ion / bright-spot detection;
- 1D and 2D Gaussian fits;
- skewed-Gaussian and multi-peak fits;
- ROI and line-profile extraction;
- averaging and histogram utilities.
Analysis can run from the Data Manager, from Python scripts, or on the analysis machine while acquisition continues. The same modules are used in the desktop GUI and in the web console.
Live monitoring
The Stream Monitor and image viewers follow data as it arrives. The history viewer, timeline and live plots are read-only clients: they cannot alter the experiment state. This makes them safe to open during operation and on remote machines.
Export
Data and fit results can be exported to CSV, images or publication-quality PDF. Export runs off the GUI thread, so large datasets do not freeze the interface.
5.1 - 2D Gaussian fit
Two-dimensional Gaussian fit within the ROI of each frame of an image stream: outputs the fit parameters and 1/e and 1/e² contour masks.
Overview
quatm/analysis/2D_gauss_fit.py provides 2D Gaussian fit analysis (TwoDGaussFit): it subscribes to an image stream and, on receiving an image, performs a 2D Gaussian fit inside the ROI (astropy Gaussian2D + Const2D, Levenberg-Marquardt iterations), publishes the fit parameters to the data stream and sends 1/e and 1/e² contour masks back to the image stream.
A fast variant, quatm/analysis/2D_gauss_fit_fast.py, is also available: the core fit is implemented in a Cython extension, and the interface and functionality are identical.
Output parameters: center x0 / y0, widths sigma_x / sigma_y, amplitude and background.
Usage
from quatm.analysis.2D_gauss_fit import TwoDGaussFit
fitter = TwoDGaussFit(name="gauss2d") # subscribe to the imagestreams image stream
fitter.run()
The analysis process is started from the Analysis Manager (AnalysisManager), where it is selected with a checkbox.
5.2 - 1D Gaussian fit
Fits a Gaussian to each frame of 1D data in a data stream and publishes the fit parameters and fitted curve.
Overview
quatm/analysis/gaussfit.py provides 1D Gaussian fit analysis (Gaussfit): as a standalone analysis process it subscribes to data streams, fits a Gaussian to each frame of 1D data and publishes the results to the data stream.
The fit function has the form:
f(x) = A0 / (sqrt(2*pi) * sigma) * exp(-(x - pos)^2 / (2*sigma^2)) + offset
The output parameters are A0 (area), sigma (width), pos (center), offset (background) and height (peak height = A0/(sqrt(2π)·σ)).
Configuration properties
| Property | Description |
|---|
datastreams | List of input data streams (each frame may be a 1D array, or a 2D array with two rows x/y) |
Usage
from quatm.analysis.gaussfit import Gaussfit
gf = Gaussfit(name="gauss") # name serves as the property/data-stream namespace
gf.run()
The analysis process is started from the Analysis Manager (AnalysisManager), where it is selected with a checkbox; input streams and parameters are configured in the property tree.
5.3 - Data management and analysis module
Data aggregation and storage management: DataSummary collects per-run data and writes HDF5, with a companion h5 management interface.
Overview
The data management and analysis module aggregates the data streams produced by an experiment into a complete record for each run and persists them. DataSummary in quatm/servers/datamgr.py is the aggregation core: it subscribes to a number of data channels plus experiment start/end events, organizes the data of each run into a complete dictionary that can be written to HDF5, read back and cleared. quatm/GUI/h5storage/ provides the data-management interface built on it (subscription, periodic saving and plotting).
Core components
| Component | Module | Description |
|---|
DataSummary | servers/datamgr.py | Aggregates multi-channel data streams into a complete dictionary for each run |
H5StorageDataMgr | GUI/h5storage/data_manager.py | Host for the aggregate/single-stream/image data managers |
H5StorageGui | GUI/h5storage/main_window.py | Data management main window (data/plot/event tabs) |
H5DataPlotter / EventPlotter | GUI/h5storage/plotters.py | Measurement data / event data plotting |
Key methods (DataSummary)
| Method | Description |
|---|
initDataq | (Re)subscribes to the channels configured in properties and to the experiment start/end events |
recvData / processIncomingData | Receives data and merges it into the most recent run record |
savetoFile | Writes the collected run dictionaries to a structured HDF5 dataset |
loadFromFile | Reads a set of stored run dictionaries back from HDF5 |
clear | Clears all collected data |
run_forever | Blocking loop that keeps receiving data |
Usage
from quatm.servers.datamgr import DataSummary
summ = DataSummary(name="sum")
summ.initDataq() # subscribe to the channels configured in the properties
summ.run_forever() # blocking acquisition; savetoFile then writes the run dict to the .h5
Input channels and the save path are configured in the property tree; for the GUI side see Data storage.
5.4 - Data stream subscription management
Main scrolling data-view window: ScrollPlot subscribes to data streams and scrolls them; PlotDataEditor selects the data source.
Overview
quatm/GUI/entries/scrollwindow.py provides data stream subscription management (ScrollWindow): a multi-channel scrolling data-view main window that lays out ScrollPlot subplots by count (two per row) and uniformly distributes data blocks to the corresponding subplots and refreshes them; PlotDataEditor provides drop-down selection of data/image streams.
Components
| Component | Description |
|---|
ScrollWindow | Main window: creates subplots by count, distributes data and refreshes |
ScrollPlot | Scrolling subplot: subscribes to data streams, ring buffer + periodic refresh |
PlotDataEditor | Data-source selection panel: drop-down candidates written back to the property ydata |
Key methods
| Method | Description |
|---|
initPlots / feedData / updatePlot (ScrollWindow) | Create subplots / feed data / batch refresh |
setnewData (ScrollPlot) | Periodically pulls data and refreshes the curve |
dataSelectDialog / subscribe_window / configureWindow (ScrollPlot) | Select channels / change subscription / configure this plot |
updateDatachannel (PlotDataEditor) | Writes the selected stream into the property ydata when the drop-down changes |
Usage
from quatm.GUI.entries.scrollwindow import ScrollWindow
win = ScrollWindow(name="PMT scroll monitor", parent=parent, n_plots=2)
Data-stream subscription, channel selection and configuration are performed on each subplot through its context menu; for a more complete description see Scrolling waveform window.
5.5 - Graphical ROI manager
Remotely controllable ROIs whose position, size and color are synchronized through the central property store.
Overview
quatm/GUI/widgets/ROI.py provides ROI: a subclass of ROI that communicates its color, position and size through the central property store under a unique name. It is suited to displaying and managing multiple regions of interest in a GUI and letting remote clients view and adjust them in sync.
Methods
| Method | Description |
|---|
update_from_properties | Refreshes the ROI pen color and geometry from the color/position/size in properties |
Usage
from quatm.GUI.widgets.ROI import ROI
roi = ROI(name="analysis_roi", parent=view) # name must be unique; duplicate names share properties
roi.update_from_properties()
For a more complete description, see Remote ROI.
5.6 - Live updating plot
Real-time plotting that subscribes to data streams and keeps updating: the UpdatingPlot and LivePlot containers.
Overview
quatm/GUI/entries/updating_plot.py provides the live updating plot: UpdatingPlot is a subclass of PlotItem that subscribes to data streams and periodically pulls new data to refresh the curve; LivePlot, by contrast, is a widget container that embeds an UpdatingPlot and can carry an image-stream name, which makes it convenient to embed as a dock panel in other windows.
Components
| Component | Description |
|---|
UpdatingPlot | Live plot item: subscribes to data streams and keeps updating the curve |
LivePlot | Widget container: embeds one UpdatingPlot and can carry an image stream |
Key methods (UpdatingPlot)
| Method | Description |
|---|
setnewData | Periodically pulls new data; for 1D arrays it automatically adds an x axis and then updates the curve |
updateConfiguration | Cancels the old subscriptions, resubscribes according to the stream list configured in properties and rebuilds the curves |
subscribe_window | Opens the subscription editor to select data streams and reapply the configuration |
configureWindow | Opens the property-edit dialog to adjust display settings |
Usage
from quatm.GUI.entries.updating_plot import UpdatingPlot, LivePlot
plot = UpdatingPlot(name="PMT signal") # used directly as a plot item
host = LivePlot(name="PMT signal", parent=parent) # or embedded as a container in the UI
For a more complete description, see Live plotting.
5.7 - Subscription editor
Manages the program’s subscriptions to data/image streams: display, add, delete and hot property updates.
Overview
quatm/GUI/editors/subscription_editor.py provides the subscription editor (SubscriptionEditor): it manages the program’s subscriptions to data or image streams. The interface shows the current subscription list, offers a drop-down to select a new stream and add it, and allows subscriptions to be deleted; changes are synchronized immediately to the property store and a signal is broadcast, so that plotting/monitoring components can update on the fly.
Key methods
| Method | Description |
|---|
loadSubscriptions | Reads the saved subscriptions from the property store and loads them into the list |
handle_property_changes | Listens for property changes: when this subscription key changes, refreshes the list and broadcasts |
update_streamlist | Rebuilds the drop-down candidates from the currently available data-stream list |
add | Adds the stream selected in the drop-down to the subscriptions and synchronizes properties |
deletemarked | Removes the subscriptions marked for deletion and synchronizes properties |
Usage
from quatm.GUI.editors.subscription_editor import SubscriptionEditor
editor = SubscriptionEditor(props=props, category="Data", parent=parent)
Constructor parameters: category is Image / Data / List; propprefix, streamkey and fullList (List mode) can be used to adjust the property keys and candidate streams. For a more complete description see Subscription editor.
5.8 - Table widget
Displays and edits parameter types holding NumPy record arrays in tabular form.
Overview
quatm/GUI/widgets/table_parameter.py provides the table widget (table_parameter): it extends the parameter tree so that parameters holding NumPy record arrays can be displayed and edited in tabular form, and can be registered as a reusable custom parameter type via registerParameterType.
Components
| Component | Description |
|---|
table_parameterItem | Table-widget parameter item for editing record arrays (a WidgetParameterItem subclass) |
table_parameter | Table parameter type (a Parameter subclass), used together with the item above |
Key methods
| Method | Description |
|---|
makeWidget (table_parameterItem) | Creates a QTableWidget editor from the recArray dtype |
value / setValue (table_parameterItem) | Reads back cell by cell from the table / writes the array cell by cell |
valueChanged (table_parameterItem) | Refreshes the table and label when the parameter value changes |
setValue / valueIsDefault (table_parameter) | Sets the parameter value / tests whether it is the default value |
Usage
from quatm.GUI.widgets.table_parameter import table_parameter
param = table_parameter(name="records", value=recarray) # insert into the parameter tree
For a more complete description see Table parameter.
5.9 - Data, image and camera editor
Display and editing of data-stream, image and camera properties: these capabilities are now provided by parameter boxes, the image monitor and the property editor.
Overview
An earlier unified interface provided a set of widgets, ParameterBox / ImageDataBox / CamPropsBox / DataBox, for displaying a single data-stream value, image data and camera properties as compact boxes, with right-click subscription and data selection. As the GUI was split up and evolved, these capabilities have been taken over by the current components:
| Legacy component | Function | Current implementation |
|---|
ParameterBox | Compact display of a single data-stream value | Parameter box widgets (widgets/arg_boxes etc.) |
ImageDataBox | Compact box for image data display | Image monitor (entries/image_monitor.ImageDisplay) |
CamPropsBox | Display and editing of camera properties | Property tree editor (PropEdit) + subscription |
DataBox | Simple label-value box | GUI base frames (BFrame / BWidget) |
Related pages
5.10 - Image monitor
Automatically updating image display window: subscribes to image/mask streams, with noise reduction, bright-spot detection and saving.
Overview
quatm/GUI/entries/image_monitor.py provides the image monitor (ImageDisplay): an automatically updating image display window that periodically pulls the latest frame from the image streams subscribed in properties, transforms it according to resolution and offset, refreshes the display and synchronously overlays mask streams. It has built-in noise reduction, bright-spot detection, saving of the current frame and property configuration (display levels, ROI, etc.).
Methods
| Method | Description |
|---|
update_image / update_mask | Periodically pulls the image/mask stream and refreshes the display and overlay |
noise_filter | Bilateral filtering + median filtering noise reduction using property parameters |
spot_detector | Binarizes the image by threshold to detect bright spots |
saveCurrentImage | Saves the currently displayed frame as PNG (named by time) |
subscribe_window / subscribe_mask | Opens the subscription dialog to select image/mask streams and resubscribes |
configureWindow | Opens the property-edit dialog to adjust display levels, ROI, etc. |
Usage
from quatm.GUI.entries.image_monitor import ImageDisplay
view = ImageDisplay(imagestreams=["camera/image"], parent=parent)
view.configureWindow() # adjust display levels, ROI, etc.
For a more complete description, see Image monitor.
5.11 - Image viewer
Main scientific image-viewing window: a central image display with row/column line-profile and histogram color-scale panels.
Overview
quatm/GUI/entries/image_group.py provides the image viewer window (ImageWindow): with a central ImageDisplay image display as the main widget and docked panels for row/column line profiles and the histogram color scale around it, it is used for live monitoring and analysis in imaging experiments.
Layout structure
| Dock area | Widget | Purpose |
|---|
| Center | ImageDisplay | Subscribes to image streams and refreshes automatically; masks can be overlaid |
| Top | LivePlot (row profile) | Intensity distribution for image rows |
| Right | LivePlot (column profile) | Intensity distribution for image columns |
| Left | HistogramLUTWidget | Histogram color scale: level range and false-color mapping |
Display parameters (in the Viewer/ namespace: min/max levels, gradient, tick_position) are restored from properties on construction and saved on close.
Usage
from quatm.GUI.entries.image_group import ImageWindow
win = ImageWindow(name="absorption_imaging") # standalone top-level window
win.show()
For a more complete description, see Image viewer.
5.12 - Live data monitor
Real-time plotting of measurement data: data sources / multiple y axes, filtering/binning/fitting and export.
Overview
quatm/GUI/alignment/plotter.py provides the live data monitor (DataPlotterWidget): it plots the data acquired during a measurement. It supports switching the data source and provides an x-axis selector and several y-axis selectors; each y axis is processed through a configurable data chain (scale factor → filter → binning/fitting) before being drawn in the plot area, and the result can be exported to PDF.
Components
| Component | Description |
|---|
DataPlotterWidget | Main plot: data-source drop-down, x-axis selector, several y-axis selectors (right-click toggles the configuration panel) |
YAxisWidgetHelper | y-axis data chain: scale factor, filter, binning and fitting |
Key methods
| Method | Description |
|---|
setDataSource | Sets or changes the data source |
checkForNewData / updateDatasources | Checks for new data / new data sources and refreshes |
plotData | Adds a set of x/y data to the plot area as a new curve |
toggleConfig | Toggles display of the data-source and y-axis configuration panel |
exportpdf | Exports the current plot as an archival-quality PDF |
clear | Clears the data summary and all curves in the plot area |
Y-axis chain (YAxisWidgetHelper): updateYscale sets the scale factor; the context menu offers addBinning for binning and addFitting for fitting (e.g. Gaussian/linear), and reevaluateData re-runs the whole data chain after changes such as filters.
Usage
from quatm.GUI.alignment.plotter import DataPlotterWidget
plotter = DataPlotterWidget(
axis_name="myaxis",
prop_root="...",
datastream_names=[".../data"],
n_yaxis=1,
parent=parent,
)
plotter.toggleConfig() # expand the data-source / x, y axis selection config
For a more complete description, see Auxiliary plotting.
5.13 - Property editor
A tool for viewing and editing the property tree, based on ParameterTree.
Overview
quatm/GUI/entries/property_editor.py provides the property editor: it presents the key/value tree of the property store as an expandable, editable parameter tree, and modifications are written back immediately. It supports browsing by subtree, searching, saving/loading and deleting the selected nodes.
Components
| Component | Description |
|---|
TreeEdit | Extracts information from Properties and organizes it into a tree-view structure |
PropEdit | Property editing main window (ParameterTree): subtree browsing, search, save/delete |
ScalableGroup | Special parameter group: automatically generates two mutually reciprocal child parameters |
PropSelector | Key-list selector: checks keys from the property tree and maintains a key list |
Usage
from quatm.GUI.entries.property_editor import PropEdit
editor = PropEdit(name="device_status", props=props, subtree="/", parent=parent)
Use subtree to restrict the display to properties under a given path. For a more complete description, see Property editor.
5.14 - Property update manager
Property timestamp watchdog: checks whether properties keep updating and raises an alarm in Deadman fashion after a timeout.
Overview
quatm/analysis/props_check.py provides the property update manager (PropsCheck): a watchdog that runs as a standalone analysis process and periodically reads the timestamp property specified by the property path _prop_path_abs; if it has not been updated for longer than _deadman_delay_sec, an alarm is raised through send_error (Mattermost can be configured).
Key methods
| Method | Description |
|---|
run | Periodically checks whether the timestamp property is fresh and raises an alarm in Deadman fashion on timeout |
Usage
from quatm.analysis.props_check import PropsCheck
pc = PropsCheck(name="props_watchdog") # property namespace
pc.run()
On the GUI side there is also a property checker (PropertiesChecker) for the same purpose, which verifies that the property center is really distributing updates (it sets a flag and reads it back; if the update has not propagated, it shows a dialog and raises an alarm), see Property checker.
5.15 - Skewed Gaussian fit
Fits a skewed Gaussian (skew-normal) distribution to 1D data in a data stream, additionally reporting skewness and peak position.
Overview
quatm/analysis/gaussfit_skewed.py provides skewed Gaussian fit analysis (GaussfitSkewed): it fits a skew-normal distribution to each frame of 1D data in a data stream and publishes the results to the data stream. Compared with an ordinary Gaussian fit, the skewed Gaussian describes asymmetric (tailed) peak shapes better, and the results additionally include the skewness parameter and the peak position.
Usage
from quatm.analysis.gaussfit_skewed import GaussfitSkewed
gs = GaussfitSkewed(name="skewed") # name serves as the property/data-stream namespace
gs.run()
The input data streams are configured in the property datastreams; the analysis process is started from the Analysis Manager (AnalysisManager), where it is selected with a checkbox.
5.16 - Data and image stream monitor
Real-time scrolling plot window: subscribes to data/image streams and scrolls them, with running mean and right-click configuration.
Overview
quatm/GUI/entries/live_plot.py provides data-stream/image-stream scrolling monitoring (ScrollPlot): inside the multi-subplot scrolling window ScrollWindow, each ScrollPlot subscribes to several channels of one data stream, maintains a ring buffer and scrolls scatter points and a moving-average curve; PlotDataEditor provides a data-source selection drop-down.
Components
| Component | Description |
|---|
ScrollWindow | Multi-subplot scrolling main window: restorePlots restores / addPlot appends subplots |
ScrollPlot | Scrolling subplot: ring buffer, scatter and moving average, context-menu operations |
PlotDataEditor | Data-source selection panel: drop-down candidates written back to properties/the parent plot |
| Method | Description |
|---|
feedData / setnewData | Pushes a new data block into the buffer / periodically pulls and refreshes |
updatePlot / clearPlot | Refreshes the curve / clears and rebuilds this plot |
running_mean | Efficiently computes the N-point moving average using cumulative sums and differences |
dataSelectDialog / subscribe_window / configureWindow | Select channels / change subscription / configure |
deletePlot / updateTitle | Removes this plot / synchronizes the title and data source |
Usage
from quatm.GUI.entries.live_plot import ScrollWindow
host = ScrollWindow(name="PMT scroll monitor", parent=parent, n_plots=2)
For a more complete description, see Live scrolling plot.
5.17 - Absorption imaging
Computes the optical density (column density) from with-atom, without-atom and background frames using the Beer-Lambert law.
Overview
quatm/analysis/imagedivider.py provides absorption imaging analysis (ImageDivider): it collects with-atom (atom), without-atom (no-atom) and background (background) images by imgindex, combines them with physical parameters such as the scattering cross-section, and computes and publishes the optical density (two-dimensional atomic column density) according to the Beer-Lambert law.
The intensity relation is I = I₀·exp(−n·σ), so the column density is n = ln(I₀/I)/σ; physical parameters such as detuning and linewidth are updated through event messages when the experiment starts.
Configuration properties
| Property | Description |
|---|
_atomindex / _noatomindex / _backgroundindex | imgindex of the with-atom / without-atom / background frames |
| Detuning / linewidth, etc. | Imaging laser and transition parameters (updated by experiment events) |
Usage
from quatm.analysis.imagedivider import ImageDivider
div = ImageDivider(name="od") # subscribe to the corresponding image streams
div.run()
The analysis process is started from the Analysis Manager (AnalysisManager), where it is selected with a checkbox.
5.18 - Ion detection series
Bright-spot/single-ion detection and ion-state discrimination: the BrightSpots, SingleIonPresent and IonState analysis processes.
Module overview
| Module | Class | Detection method | Purpose |
|---|
ion_blob | BrightSpots | Threshold segmentation + connected-component counting | Counts the number of ions in each frame |
bright_spots | BrightSpots | skimage blob_dog | Detects bright spots and outputs a position mask and bloblist |
single_ion_present | SingleIonPresent | Counting + multiple Gaussian-fit-parameter criteria | Decides whether a single ion is present |
ion_state | IonState | Multi-image sequences by imgindex | Discriminates the ion product state |
Discrimination workflow
IonState collects the check / survival / hot / shelved image series by imgindex, detects whether an ion is present in each image, and aggregates the results into the product state:
group frames by imgindex (check / survival / hot / shelved)
↓ detect the ion in each frame by blob analysis
aggregate → publish the product-state result
Usage
from quatm.analysis.ion_state import IonState
det = IonState(name="ionstate") # subscribe to the image streams and group by imgindex
det.run()
Each process is started from the Analysis Manager (AnalysisManager), where it is selected with a checkbox; image streams, thresholds, imgindex, etc. are configured in the property tree.
5.19 - Image processing tools
Image processing analysis processes for hot-pixel filtering/finding, rotation, slicing, frame selection and background subtraction.
Module list
The following are all standalone analysis processes that subscribe to image streams, located in quatm/analysis/:
| Module | Class | Description |
|---|
hot_pixel_filter | HotFilter | Filters/replaces hot pixels according to the coordinate list in the property hotpixels and sends the result back |
hot_pixel_finder | HotFilter | Finds hot-pixel coordinates statistically and writes them to properties (for use by the filtering script) |
rotate_image | ImageRotate | Rotates the image by an angle and rotates the coordinate offsets accordingly |
imageslice | ImageSlice | Computes row/column projections (sum/clip/integrate) over the ROI and sub-region images |
imageselector | ImageSlice | Selects the frame with the specified imgindex from the image stream and forwards it |
substract_image | ImageSubstract | Subtracts a fixed background image (automatically adapting for transposition) and outputs the difference |
Usage
from quatm.analysis.hot_pixel_filter import HotFilter
f = HotFilter(name="hotfix") # subscribe to the image stream and filter with the hot-pixel list from the properties
f.run()
Each process is started from the Analysis Manager (AnalysisManager), where it is selected with a checkbox; input streams and parameters are configured in the property tree.
6 - GUI components
Desktop applications and their responsibilities.
The desktop suite is built with PySide6 and shares a common visual language, theme and lifecycle model.
Architecture overview
The Experiment Browser is the main window: FileSelector opens experiment scripts, the central QMdiArea hosts one or more ExperimentWindow panes, and the top dock area manages the experiment queue, preparation station and looper. ExperimentManager is the execution engine that consumes tasks and drives each run through its lifecycle.
flowchart TB
subgraph Browser[Experiment Browser - main window]
FS[FileSelector<br/>experiment file tree]
MDI[QMdiArea<br/>experiment pane container]
EW[ExperimentWindow<br/>single-experiment control]
DOCKS[Top dock area<br/>queue / preparation / looper]
FS --> MDI
MDI --> EW
DOCKS --> MDI
end
EM[ExperimentManager<br/>execution engine]
QUEUE[Experiment queue / scans]
PROC[Process Manager]
EW --> EM
DOCKS --> EM
EM --> QUEUE
EM --> PROCExperimentWindow owns the parameter panel, submission and scan entry points. ExperimentManager does not draw the UI; it consumes tasks from the queue and tracks their run state. The Process Manager starts and supervises the independent GUI, driver and service processes.
| Application | Responsibility |
|---|
| Operations Console | Services, tasks/processes/ports and device health in one window |
| Experiment Browser | Experiment selection, parameters, queue, scans, preparation and looper |
| Dashboard | Instrument defaults and parameter tuning by device group |
| Data Manager | HDF5 browsing, fits, image analysis and export |
| Stream Monitor | Live data and message inspection |
| Image / Live / Scroll viewers | Camera, ROI and real-time curve displays |
| Timeline | FPGA/ADwin sequence visualisation |
| Device Manager | Device database editing, driver catalogue and health checks |
| Property / Subscription / Auto-save editors | Shared property tree and persistence policies |
| Settings Hub | Notifications, licence and low-frequency configuration |
| Task & Process Monitor | Read-only task, process, endpoint and health observability |
Design conventions
- Every window can be opened independently or through the launcher.
- Closing the launcher’s last window prompts for full application exit and stops all services.
- Windows that create background work release it in
closeEvent; hidden windows pause refresh timers rather than stopping data clients. - Help and About pages are per-window and bilingual.
- The interface follows the operating system’s font scaling and light/dark appearance.
Extending the GUI
Reusable building blocks live under quatm/GUI/widgets and quatm/GUI/infra. Project-specific windows belong in the project’s GUI/ directory and are registered in configfile.json. The application identity table controls window names, icons and desktop launchers.
6.1 - Experiment browser
The Quatm main window: experiment browsing, queue management and a multi-window MDI workspace.
Overview
quatm/GUI/browser/main_window.py provides the experiment browser (Browser): Quatm’s main application window, built on QMainWindow + QMdiArea as a multiple-document interface. The file selector on the left browses experiment scripts, the experiment queue, preparation station and looper are docked at the top, and each experiment occupies its own sub-window in the central MDI area.
Layout
┌───────────────┬───────────────────────────────┐
│ FileSelector │ QMdiArea (subwindow area) │
│ (file tree) │ ┌─────────────────────┐ │
│ │ │ ExperimentWindow │ │
├───────────────┤ └─────────────────────┘ │
│ Queue / Prep │ │
│ / Looper dock │ │
└───────────────┴───────────────────────────────┘
Core components
| Component | Module | Description |
|---|
FileSelector | browser/file_selector.py | Experiment directory tree; double-clicking opens the experiment or simulation window |
ExperimentWindow | browser/experiment_window.py | Control panel for a single experiment inside the central MDI area |
ExperimentQueue | experiment/experiment_queue.py | Tabular task queue |
ExperimentManager | experiment/experiment_manager.py | Takes tasks from the queue and executes them |
PrepStation | experiment/prepstation.py | Staging and editing of tasks before they are sent to the queue |
Looper | looper/ui.py | Conditionally repeats a group of tasks based on measurement results |
Details of each component are given on the corresponding page in this section.
Lifecycle
init_ui / create_dock_widgets: adapts the default geometry to the screen and creates the MDI canvas together with the two dock areas (the file selector on the left; the queue, preparation station and looper at the top).open_experiment: opens an experiment window; if it is already open, restores it from minimised state and raises it.closeEvent: first saves the main-window geometry and the state of all experiment sub-windows, then stops the queue/looper worker threads, and finally hands over to the base class to complete the close.moveEvent / resizeEvent: debounced geometry saving, so that the window position and size are restored on the next start.
Usage
from quatm.GUI.browser.main_window import Browser
browser = Browser()
browser.show()
The experiment browser is the interface through which every experiment other than the helpers is run; it is normally started as the main process by the process manager.
6.2 - Experiment manager
The core experiment execution engine: takes tasks from the experiment queue and schedules them by measurement/sequence/run hierarchy.
Overview
quatm/GUI/experiment/experiment_manager.py provides the experiment manager (ExperimentManager): the engine that takes tasks from the experiment queue (ExperimentQueue) and executes them. Its queue polling runs in a worker thread, and tasks are scheduled in the hierarchy measurement → repetition → sequence/scan → run.
Execution flow
queue_fn (worker thread polls the queue continuously)
└─→ run (task wrapper: cleanup is guaranteed however the task ends)
├─→ start_measurement (measurement start hook)
├─→ start_sequence (sequence/scan start hook)
│ └─→ … several run calls with varying parameters …
├─→ end_sequence (sequence end hook)
└─→ end_measurement (measurement end hook; clears the task table)
Key methods
| Method | Description |
|---|
queue_fn | Continuously polls the queue for task dictionaries: selects tasks by priority, task number and scheduled time |
run | Task run wrapper: finalises cleanly whether the task ends as Done/Terminated/Failed or is deleted |
pause | Pauses the current experiment; calling it again resumes it |
terminate_experiment | Graceful termination: lets the current run finish naturally before stopping |
start_measurement, etc. | Invokes the experiment’s start/end hooks for each stage |
set_dict | Writes a single parameter to an attribute of the experiment instance |
set_run_nr / set_rep_nr | Synchronises the run/repetition index into the task dictionary and refreshes the table |
due_check | Determines whether a task has reached its scheduled execution time |
start_queue / shutdown | Starts / stops the resident queue worker |
update_table | Notifies the GUI to refresh the displayed task table |
Usage
from quatm.GUI.experiment.experiment_manager import ExperimentManager
manager = ExperimentManager(browser) # browser supplies the queue and the UI
manager.start_queue() # start the resident queue worker
Tasks execute in a dedicated worker thread and do not block the GUI; when the browser closes it calls shutdown to stop the queue worker so that the process exits cleanly.
6.3 - Experiment queue
The task queue table panel: maintains priority ordering, status tracking and lifecycle management (pause/terminate/sleep/delete) of experiment tasks.
Overview
ExperimentQ (quatm/GUI/experiment/experiment_queue.py) is the “experiment queue” panel of the experiment browser (Browser) main window: a task table driven by the queue table model ScheduleModel (module quatm/GUI/widgets/models.py). Task dictionaries are stored with the task number as primary key; each task is one record containing everything needed to run it, and the table sorts by priority priority (lower value first) and then by task number.
Task status
The experiment manager (ExperimentManager) and the panel jointly maintain the status of each task:
| Status | Meaning |
|---|
Queued | Enqueued and waiting for execution |
Waiting | A due time has been set; not executed until that time is reached |
Sleeping | Asleep; the manager ignores it for the moment (click again to wake it) |
Running | A single run is in progress |
Scanning | A sequence/scan is in progress |
Paused | Paused; can be resumed |
Terminating / Termination Pending | Graceful termination in progress (lets the current run finish) |
Done | Execution completed |
Failed | Execution failed |
Operations and shortcuts
- Button bar (created by
create_buttons): icon buttons for pause/resume, terminate selected tasks, terminate all, and so on. - Context menu and single-key shortcuts (
init_table_actions): e edits parameters, v opens a read-only preview, and delete, sleep/wake and pause are also available. - Deleting a running task: automatically becomes a graceful termination (
delete_clicked). - Terminate (
terminate_clicked / terminate_all): stops gracefully, letting the run currently in progress finish before ending.
Main methods
| Method | Function |
|---|
submit(taskDict) | The only entry point into the queue (runs on the GUI thread): assigns task numbers centrally; the looper (Looper) also reaches it through the submitItem signal |
pause() | Pauses/resumes the experiment currently running |
set_sleeping() | Marks the selected tasks as sleeping or wakes them |
delete_item(k) / delete_clicked() | Deletes by task number, or deletes the selected rows (a running task is terminated gracefully) |
terminate_clicked() / terminate_all() | Gracefully terminates the selected tasks / all tasks |
update_item(k, v) | Updates a model entry by task number and resets its lock |
open_editor() / open_viewer() | Edits / previews the parameters of the selected task read-only (shortcuts e / v) |
Data model
# task dictionary structure (key = task number)
expDict = {
1: {
"expName": "Rabi scan",
"filepath": "experiments/ion/rabi.py",
"status": "Queued",
"priority": 1,
"args": {},
"due": "",
},
}
Sorting rule: priority (lower value first) → task number (first in, first out).
Relation to the looper
Task items of the looper (Looper) are also enqueued through ExperimentQ.submit(), so the desktop interface and loop scheduling share the same “central task-number assignment” entry channel.
6.4 - Preparation station
The staging and editing area for experiment parameters: preview, reorder and fine-tune tasks before they are pushed into the queue.
Overview
PrepStation is a tab in a top dock of the experiment browser (Browser) that acts as the
“staging area” of the experiment queue: tasks that are not yet ready are kept here and are
pushed into ExperimentQ for execution once confirmed. The task list is persisted in
configuration/browser/prepfile.json and can be viewed and manipulated from the experiment
console on the browser side (Web remote control).
Functions
| Operation | Description |
|---|
| Stage | Clicking the Prep button in the experiment window stages the current parameters (including the scan definition) in the task list |
| Push | Copies the selected task into ExperimentQ (the queue retains the complete task number, priority and scan parameters) |
| Add to loop | Adds the selected task to a Looper loop group (push_to_looper) |
| Reorder | Moves tasks up/down to change their order |
| Sleep | Marks a task as Sleeping so that it takes no part in pushes for the time being |
| Edit/view | Opens the task parameter editor or a read-only view |
| Save/load | Saves a parameter set as JSON (experiment_params/<experiment>/<date>/) |
Task data structure
Every staged task has the same structure as a queue task (generated by the experiment window’s setup_task_dict()):
task = {
'task': 12, # task number
'expName': 'Absorption', # experiment name
'label': 'scan: freq 2:1', # label (includes the scan prefix)
'priority': 0,
'args': {'count': 5}, # experiment parameters
'nRuns': 21, # number of scan points
'scanpars': ['freq'], # scan parameters
'scanvals': [[...]], # scan values
'scansequence': [...], # scan order (after randomisation)
'nReps': 1,
'status': 'Queued', # Queued / Sleeping / Waiting
'filepath': '.../absorption.py',
}
Persistence and sharing
- The task list is kept in
configuration/browser/prepfile.json and is restored automatically when the browser restarts; - The desktop GUI and the Web remote control platform share the same file,
so tasks staged, pushed, reordered or slept on one side are immediately visible on the other.
6.5 - Looper
The conditional loop executor: decides automatically, from measurement results, whether to repeat, jump or end an experiment loop.
Overview
Looper is a tab in the top dock of the experiment browser (Browser)
(quatm/GUI/looper/). It manages groups of experiment tasks, executes the items of a group
in order, and decides whether to jump or to stop based on the measurement results. Loop
definitions are persisted in configuration/browser/looper/loopfile.json, and the same file
is shared with the experiment console on the browser side
(Web remote control).
Core logic
LoopManager.run_loop(): loop from the start group forever until terminate
└─→ run the items of the group in order
├─ Task: submit to the experiment queue and advance after it finishes
│ (max count / max consecutive count can be set; reaching either ends the loop)
└─ Conditional: read a data-stream field → compare (> >= == != =< <)
├─ true → jump to the given item / end of group / end of loop
└─ false → jump to the else item
Components
| Component | Description |
|---|
LoopGroup | A loop group (one tab), containing the item list and the loop builder |
LoopItem | Item base class (delete/up/down/run counter/indicator LED) |
TaskItem | Task item: points to a staged task, supports max count/max consecutive/skip |
ListItem | Task list item: expands a task list and runs the tasks one by one |
ConditionalItem | Data condition item: compares a field and then jumps (if/else) |
ConditionalReachedItem | Reached-index condition item: jumps once the current time reaches the N-th cycle’s h5 save time |
ConditionalDateTimeItem | Time condition item |
CallerItem / GroupItem | Inter-group jump and end-of-group items |
DataManager | Subscribes to data streams and accumulates the latest measurement values |
LoopManager | The loop executor (run_loop() / terminate(), running in a background thread) |
AutoSaveEditor | Configuration of the automatic HDF5 save schedule |
How to use
Operate interactively through the Looper tab in the experiment browser (Browser):
- Add a group: create a new tab (
New Tab #N), which can be renamed; - Add items: each group can contain Task / List / Conditional / ConditionalDateTime
/ ConditionalReached / Caller and other items;
- Configure items: for Task, select the staged task number and set the maximum count; for
Conditional, select the data field, comparison operator and threshold, and configure the
if/else jump targets (item index /
end box / end loop); - Run: select the starting group and click run;
LoopManager executes in a background thread and
the terminate button stops it at any time.
# core classes (used internally by the GUI)
from quatm.GUI.looper import Looper, LoopManager
looper = Looper(parent=browser)
looper.loopManager.run_loop() # start looping from the start group
looper.loopManager.terminate() # terminate the loop
Persistence and sharing
- Loop definitions are saved automatically to
configuration/browser/looper/loopfile.json
({group name: [item dictionaries, ...]}) and restored automatically when the browser restarts; - The desktop GUI and the web remote-control platform share the same file, so changes made on
either side are immediately visible on the other; the browser side likewise supports group
management, Task/Conditional item editing and starting/stopping loops.
6.6 - Experiment window
The per-experiment control window: dynamic parameter grid, submission/scanning and parameter persistence.
Overview
quatm/GUI/browser/experiment_window.py provides the experiment window (ExperimentWindow):
each experiment script corresponds to a control window carried by an ExperimentSubWindow in
the browser’s MDI area. It generates a parameter grid automatically from the parameter objects
declared by the experiment, and is responsible for submitting to the queue/preparer, building
scans and loading/saving JSON parameters.
Components
| Component | Description |
|---|
ExperimentSubWindow | Graphical container: stores the window geometry and clamps the window inside the visible area |
ExperimentWindow | Single-experiment control panel: parameter grid + submit/scan/save controls |
SequenceEditor | Scan sequence dialog: sets start, stop and step count per dimension to generate an nD scan |
Key methods (ExperimentWindow)
| Method | Description |
|---|
submit_to_queue | Submits the current experiment to the experiment queue for execution (the unified entry point for all submission buttons) |
submit_next | Submits with the highest priority, ahead of all existing tasks |
submit_to_prepper | Submits to the preparation station (PrepStation) for staging |
setup_scan | Builds the scan sequence (parameter values and randomised run order) from the scan definition |
setup_task_dict | Builds the task dictionary stored in the queue/preparation station |
build_argument_dict | Collects values from the parameter grid controls into a complete parameter dictionary |
save_params / load_params | Saves parameters to JSON (named by date/time, optionally backed up) and loads them back |
adaptive_columns | Re-flows the parameter grid columns to the window width (roughly one extra column per 200 px) |
edit_sequence | Opens the scan sequence editor and applies the new sequence |
Usage
from quatm.GUI.browser.experiment_window import ExperimentWindow
# normally opened by Browser.open_experiment; may also be constructed directly
win = ExperimentWindow(filepath=..., props=props, parent=subwindow, browser=browser)
The parameter objects declared by the experiment script (such as NumberValue / BoolValue /
StringCombo) automatically produce editing controls with a label, unit and range; parameter
sets can be saved as json and loaded again later.
6.7 - Parameter system
The BasicManager parameter-manager system: maps experiment properties to numeric, boolean, combo and frequency controls.
Overview
quatm/GUI/entries/dashboard.py provides a family of “parameter manager” controls: each
manager maps one experiment parameter (stored in the property database, always in SI units
internally) to a labelled input control, and synchronises bidirectionally between the
interface and the property database.
Control class hierarchy
BasicManager (base class of numeric parameter entries)
├── IntManager → integer spin box
├── FloatManager → float spin box (optional unit/step/decimals)
├── BoolManager → check box
├── ComboManager → drop-down box (string list)
└── FrequencyManager → DDS frequency spin box (stores Hz, shows MHz)
Usage (schematic)
from quatm.GUI.entries.dashboard import IntManager, FloatManager, BoolManager
# each manager binds the property store props and the experiment exp, reading/writing by parameter name
mgr = IntManager(props=props, exp=exp, parname="my_int")
fmgr = FloatManager(props=props, exp=exp, parName="cooling_time", unit="µs")
bmgr = BoolManager(props=props, exp=exp, parName="enable")
Changes made in the interface are converted to SI and written back to the property database;
changes in the property database are likewise synchronised to the displayed values periodically
by each control (updateValue / updateSpin).
See also “Experiment parameter panel”: on this basis the Dashboard main panel arranges the
parameters of a whole device into an adjustable control panel.
6.8 - Property editor
A ParameterTree-based tool for viewing and editing properties.
Overview
quatm/GUI/entries/property_editor.py provides a property editing tool based on
parametertree: it presents the key/value tree of the property database as an expandable,
editable parameter tree with search filtering, and writes changes back immediately.
Components
| Component | Description |
|---|
TreeEdit | Extracts information from Properties and arranges it into a tree-view structure |
PropEdit | Main property editing window (ParameterTree): subtree browsing, search, saving/deleting the selected node |
Usage
from quatm.GUI.entries.property_editor import PropEdit
editor = PropEdit(name="device_status", props=props, subtree="/", parent=parent)
Note: subtree restricts the view to the properties under a given path; TreeEdit is responsible for converting the property dictionary into a tree of Parameter nodes.
6.9 - Live plotting
A live plot item that subscribes to data streams and keeps updating, together with its widget container.
Overview
quatm/GUI/entries/updating_plot.py provides live plotting: UpdatingPlot is a
PlotItem subclass that subscribes to a data stream, periodically pulls new data and
refreshes the curve; LivePlot is a widget container that embeds one UpdatingPlot, which
makes it easy to dock into other windows as a panel.
Components
| Component | Description |
|---|
UpdatingPlot | Live plot item: subscribes to a data stream and keeps the curve updated |
LivePlot | Widget container: embeds one UpdatingPlot and may carry an image stream name |
Key methods (UpdatingPlot)
| Method | Description |
|---|
setnewData | Periodically pulls new data; a one-dimensional array gets an x axis automatically, then the curve is updated according to the rotation flag |
updateConfiguration | Cancels the old subscriptions, then resubscribes and rebuilds the curves from the stream list configured in the properties |
subscribe_window | Opens the subscription editor to select data streams and reapplies the subscription configuration on close |
configureWindow | Opens the property editor dialog to adjust settings and reapply the display configuration |
Usage
from quatm.GUI.entries.updating_plot import UpdatingPlot, LivePlot
plot = UpdatingPlot(name="PMT signal") # use directly as a plot item
host = LivePlot(name="PMT signal", parent=parent) # or embed as a container in the UI
Subscriptions are made in the context menu of the plot item; LivePlot is also used as the
container for the row/column profile plots in the image viewer.
6.10 - LED indicator
A status LED: green/red/cyan color schemes, radial gradients and lit/unlit appearances.
Overview
LedIndicator (quatm/GUI/widgets/qled.py) is an LED indicator button whose lit/unlit
appearance can be toggled: a two-layer circular shell with a gradient centre, using highlighted
colors when checked (lit) and dark colors when unchecked (dark). It is commonly used to show
device connection status, experiment run status and the execution feedback of loop items.
Color modes
| Method | Description |
|---|
set_green() | Green scheme: bright green when checked, dark green when unchecked; usually indicates normal/ready |
set_red() | Red scheme: bright red when checked, dark red when unchecked; usually indicates an error/warning |
set_cyan() | Cyan scheme: used by looper condition items while they wait for data |
The lit/unlit (checked) state is controlled with setChecked(True/False); the onColor1/onColor2
and offColor1/offColor2 properties customise the primary/secondary colors of the on and off
states.
Usage
from quatm.GUI.widgets.qled import LedIndicator
led = LedIndicator(parent=parent)
led.set_green() # device ready
led.setChecked(True) # switch on
# ...
led.set_red() # device fault
6.11 - GUI base classes
BWidget, BMainWindow and BFrame: Qt base classes with geometry persistence and property communication.
Overview
quatm/GUI/widgets/quatmQt.py defines the base classes and common controls of the Quatm GUI
components: it provides window geometry persistence, the Properties property namespace, and a
combo box with completion.
Class hierarchy
QWidget → BWidget
QFrame → BFrame
QMainWindow → BMainWindow
Base classes and mixins
| Component | Description |
|---|
GeometrySaveMixin | Persists window geometry to QSettings (keyed by window name); saves on close, move and resize (debounced) |
BWidget | Quatm widget base class: carries the Properties namespace and geometry persistence |
BFrame | Base class corresponding to QFrame, with the same behaviour as BWidget |
BMainWindow | Main-window base class with geometry persistence and a property handle |
from quatm.GUI.widgets.quatmQt import BWidget, BMainWindow
widget = BWidget(name="my_widget", parent=None)
window = BMainWindow(name="MainWindow", parent=None)
Search and completion controls
| Component | Description |
|---|
CustomCompleter | Completer with match modes (contains = substring anywhere / begins = prefix) |
SearchComboBox | Combo box with autocompletion and type-ahead search; add_if_new() / find_or_add() maintain entries conveniently |
SearchLineEdit | Line edit for search boxes: selects all on focus, records on focus-out/Enter, restores on Escape |
from quatm.GUI.widgets.quatmQt import SearchComboBox
box = SearchComboBox(parent=parent)
box.add_if_new("my_stream")
6.12 - Image monitor
A self-updating image display control: image/mask stream subscription, denoising and spot detection, level and ROI configuration.
Overview
quatm/GUI/entries/image_monitor.py provides the automatically updating image display window
(ImageDisplay): it periodically pulls the latest frame from the image stream subscribed in the
properties, transforms it by resolution and offset and refreshes the display, optionally
overlaying a mask stream in sync. It has built-in denoising, spot detection, saving of the
current frame and property configuration (display levels, ROI, etc.).
Methods
| Method | Description |
|---|
update_image | Periodically pulls the image stream and refreshes the display frame by frame after resolution/offset transforms |
update_mask | Pulls the mask stream and updates the overlay geometry from the mask resolution/offset |
noise_filter | Denoises with a bilateral filter plus a median filter according to the property parameters |
spot_detector | Binarises the image by threshold to detect bright spots |
saveCurrentImage | Saves the currently displayed frame as PNG (named by time) |
subscribe_window | Opens the subscription dialog to select image streams and resubscribes |
subscribe_mask | Opens the subscription dialog to select mask streams and resubscribes |
configureWindow | Opens the property editing dialog to adjust display levels, ROI and other parameters |
Usage
from quatm.GUI.entries.image_monitor import ImageDisplay
view = ImageDisplay(imagestreams=["camera/image"], parent=parent)
view.configureWindow() # adjust display levels, ROI, etc.
Note: ImageDisplay is both the central display of ImageWindow and can be embedded on its
own in other interfaces. The image and mask stream lists are stored in the properties, and the
context menu provides entries for subscription, mask subscription and configuration.
6.13 - Analysis manager
A tree-view panel: manages the start, stop, status monitoring and automatic restart of analysis scripts.
Overview
quatm/GUI/entries/analysismanager.py provides the analysis manager: it lists the analysis
scripts in a checkable tree table (name / script / category / input stream) and is responsible
for starting and stopping subprocesses, monitoring and automatic restart, as well as adding,
deleting and configuring analysis entries.
Components
| Component | Description |
|---|
AnalysisManager | Main panel: loads and organises the analysis entries, and provides delete/configure entry points |
TreeViewWidget | Analysis script management tree: checking starts and stops, monitoring restarts automatically, add/delete/edit |
addAnalysisWidget | Toolbar for adding an analysis entry (name / category / script / input stream) |
Functions
| Operation | Description |
|---|
| Check to start | Checking an entry starts the corresponding analysis subprocess; unchecking stops it |
| Stop | endProcess: terminates first and waits briefly for exit, then forces the end on timeout |
| Status refresh | updateStatus: periodically checks whether the process is still running |
| Clean up stray processes | killVagabonding: terminates leftover processes of the same script from the previous round |
| Add / configure / delete | The toolbar registers new entries; a selected row can be configured or deleted |
Usage
from quatm.GUI.entries.analysismanager import AnalysisManager
mgr = AnalysisManager(name="AnalysisManager", parent=parent)
Analysis scripts are independent processes: once an entry is checked, the manager launches the
script with the current interpreter and injects the project environment variables into the child
process, so analysis code and the main program are isolated from each other and a script crash
cannot affect experiments.
6.14 - Stream monitor
Stream monitoring: multi-tab data/image/command/message monitoring and single-stream panels.
Overview
quatm/GUI/streammonitor/ provides the stream monitor: MainWindow uses tabs to hold
monitors for data streams (Data), image streams (Image), commands (Command), message logs
(Message) and history (History), among others; each monitor tab is a StreamMonitor that
subscribes to a single data stream and displays it in real time.
Components
| Component | Description |
|---|
MainWindow | Monitoring main window: multi-tab container + menu |
StreamMonitor | Single-stream monitoring panel: subscribes to one data stream and displays it in real time |
MessageTableModel | Message log table model (timestamp/source/level/content, colored by level) |
ImageTableModel | Image stream metadata table (fixed time/source columns + dynamic key columns) |
AutoSortProxyModel | Auto-sorting proxy model ordered by descending timestamp |
Monitoring types
| Tab | Content |
|---|
| Data | Real-time curves/tables of numeric data streams |
| Image | Scientific image streams and their metadata |
| Command | Command / instruction streams |
| Message | Message log (timestamp/source/level/content) |
| History | Historical records |
Usage
from quatm.GUI.streammonitor import MainWindow, StreamMonitor
win = MainWindow(parent=parent) # multi-tab monitoring main window
panel = StreamMonitor(name="PMT", stream_type="Data", parent=parent) # single-stream panel
This monitoring interface is normally started as a standalone process; the single-stream
StreamMonitor panel can also be embedded in other windows.
6.15 - Sequence editor
Experiment code editing and sequence simulation: syntax highlighting, parsing and output-channel timing visualisation.
Overview
quatm/GUI/editors/editor_sequencer.py provides the edit and simulate tool (formerly Combi /
“open simulation window”): the CodeEditorParser composite main window combines the CodeEditor
on the left with the sequence visualisation on the right: after editing an experiment script you
can simulate a run with a single click, drawing the timing of each output channel as a step plot,
which makes experiment sequences easier to debug.
Components
| Component | Description |
|---|
CodeEditor | Simple code editor: line numbers, Python highlighting, automatic indentation and completion |
PythonHighlighter | Lightweight Python syntax highlighter |
LineNumberArea | Line-number margin widget on the left of the editor |
ExperimentParser | Parses an experiment: buildExperiment / simulateExperiment return the command stack |
ExperimentSequencer | Draws a sequence as a step plot of each output channel against time |
CodeEditorParser | Composite main window: Save / Simulate / Clear toolbar + dock layout |
Key methods
| Method | Description |
|---|
saveFile (CodeEditor) | Saves the current text to a file (returns True on success) |
simulateExperiment (CodeEditorParser) | Simulates the current version of the experiment and hands the command stack to the sequence visualisation |
buildExperiment / simulateExperiment (ExperimentParser) | Builds / simulates the experiment and returns the command stack |
plotSequence (ExperimentSequencer) | Draws the sequence dictionary as a per-channel timing step plot |
Usage
from quatm.GUI.editors.editor_sequencer import CodeEditorParser
win = CodeEditorParser(parent=parent, filename="experiments/my_experiment.py")
Note: parameter scans (nD scans, randomisation, etc.) are edited through the Edit button of
the experiment window (SequenceEditor, see Experiment window); the scan
definition is stored in the experiment property tree and shared with the remote-control platform.
6.16 - Process manager
A distributed process panel: starts and stops background processes grouped as Servers/Driver/GUI/Viewer/Monitor.
Overview
quatm/GUI/entries/processmanager.py provides the process manager: it reads the configuration by category (Servers / Driver / GUI / Viewer / Monitor) and generates a SingleProcess start/stop button panel for each script entry, so the whole experiment software suite is managed from one place. Processes are launched with the current interpreter, either as a script or as a module (python -m), carrying the project root and the parent-process watchdog environment.
Process categories
| Category | Description | Typical processes |
|---|
| Servers | Communication / property / data services | Data centre, property service, etc. |
| Driver | Hardware driver processes | FPGA and other device drivers |
| GUI | Graphical interface processes | Experiment browser, data manager, etc. |
| Viewer | Data viewing processes | Live plotting, scroll window, image viewer, etc. |
| Monitor | Monitoring / checking processes | Property checker, message monitor, etc. |
Components
| Component | Description |
|---|
ProcessManager | Category panel: reads the configuration and generates start/stop buttons for each entry |
SingleProcess | Single-entry panel: start / asynchronous termination / status polling with coloring |
resolve_script | Resolves the script entry from the configuration into a launch target (file path or module) |
Key methods (SingleProcess)
| Method | Description |
|---|
startProcess | Stops the old instance and then starts the subprocess with the current interpreter |
stopProcess | Terminates the subprocess asynchronously without blocking the GUI thread |
updateStatus | Periodically polls the exit status and colors it (running / exited normally / crashed) |
Usage
from quatm.GUI.entries.processmanager import ProcessManager
pm = ProcessManager(parent=parent)
pm.show()
The entries to manage are defined by the scripts in the configuration file; each entry is started
and stopped through its own button panel.
6.17 - Device manager
PyVISA device discovery and driver binding: enumerate, query and select instruments and associate driver files.
Overview
quatm/GUI/entries/device_manager.py provides the device manager (VISADeviceLister): it scans
the VISA instruments on the system through PyVISA, queries the device identity (*IDN?) and
selects a driver file to bind, then adds the device to the selected list as a DeviceInfo record
for use in device configuration.
Components
| Component | Description |
|---|
VISADeviceLister | VISA device list tool: two columns (enumeration/query + selected devices/driver files) |
DeviceInfo | Device description: name, type, driver file and additional parameters |
Workflow
1. refresh_devices() → scan the VISA bus and fill the device list
2. query_device_info() → send an *IDN? query to the selected device
3. select_driver_file() → choose and bind a driver file
4. add_device_to_selected() → add to the selected device list (de-duplicated)
Usage
from quatm.GUI.entries.device_manager import VISADeviceLister
dl = VISADeviceLister(parent=parent)
dl.refresh_devices() # list the connected USB/GPIB/LAN and other VISA devices
6.18 - Data storage
HDF5 data management: subscription, scheduled/manual saving and history read-back, with integrated plotting panels.
Overview
quatm/GUI/h5storage/ provides the HDF5 data storage GUI (a package split out of the former
single file, with its public symbols re-exported at package level): three kinds of data manager —
aggregate, single-stream and image — subscribe to data and save it automatically at a scheduled
time, periodically, or on a Looper command; the current snapshot can also be written to an .h5
file in the background, and historical measurements read back from a file.
Components
| Component | Module | Description |
|---|
H5StorageGui | main_window.py | Main window: data tabs, save/scheduled-save controls and plot management |
H5StorageDataMgr | data_manager.py | Host for the three data managers: subscription management, save/read-back, clearing |
H5SaveWorker | workers.py | Worker thread that writes the .h5 file, keeping disk I/O off the Qt event loop |
H5DataPlotter / EventPlotter | plotters.py | Plotting tabs for measurement data / event data |
XAxisWidget / YAxisWidget | axes.py | Data selection and filter-chain configuration for the x axis and each y axis |
BinningWidget / FilterWidget / FittingWidget | widgets.py / fitting.py | binning, filtering and ODR fitting processing chain |
Saving modes (H5StorageDataMgr)
| Method | Description |
|---|
toggle_scheduled_save | Toggles saving at a scheduled time (saves automatically and notifies when the time arrives) |
toggle_regular_save | Toggles periodic automatic saving every N hours |
scheduled_save_by_looper | Responds to save commands from the Looper (final / intermittent) |
savetoFile | Writes the current data snapshot to an .h5 file in the background |
loadFromFile | Loads historical measurements from an existing .h5 file and refreshes the interface |
selectDatadir | Chooses the target h5 file manually (to avoid falling back to the daily default name) |
clear | Clears the entire cache of the three data managers |
Usage
from quatm.GUI.h5storage import H5StorageGui # package-level re-export
win = H5StorageGui(parent=parent)
win.show()
Data saving is performed by the background H5SaveWorker: the GUI thread copies the data snapshot
first and then hands it to the worker, so writing to HDF5 on disk never blocks the interface.
6.19 - Live scroll plot
Live scrolling plots: ring buffers, running averages and data-stream subscription.
Overview
quatm/GUI/entries/live_plot.py provides live scrolling plots: ScrollWindow is a
multi-subplot main window in which each ScrollPlot subplot subscribes to several channels of
one data stream, maintains a ring buffer and scrolls scatter/curve data and a running average;
PlotDataEditor provides the data-source drop-down panel.
Components
| Component | Description |
|---|
ScrollWindow | Multi-subplot scrolling main window: restores or creates subplots from the properties; the toolbar can append subplots |
ScrollPlot | Scrolling subplot: ring buffer, scatter and running-average curves, context-menu operations |
PlotDataEditor | Data-source selection panel: drop-down candidates written back to the properties/parent plot |
| Method | Description |
|---|
feedData | Pushes a new data block into the buffer array |
setnewData | Periodically pulls new data into the buffer and refreshes the curves |
updatePlot | Flushes the valid data in the buffer to the scatter and running-average curves |
running_mean | Computes an N-point running average efficiently from cumulative sums and differences |
dataSelectDialog | Selects the data channels displayed in this plot |
subscribe_window | Opens the subscription editor to resubscribe the data stream |
configureWindow | Pauses refreshing and edits this plot’s properties inline |
deletePlot | Removes this plot from the parent window and clears its properties |
Usage
from quatm.GUI.entries.live_plot import ScrollWindow
host = ScrollWindow(name="PMT scroll monitor", parent=parent)
host.addPlot() # append a scrolling subplot
Afterwards, subscribe to data streams, select channels and adjust the display from the context
menu of a subplot. Configurations such as the buffer length, the running-average switch and the
window size are stored in the properties and restored automatically after a restart.
6.20 - Auto-save editor
Editor for the list of automatic save times: configures a set of periodic save instants (checkbox + date-time).
Overview
AutoSaveEditor (quatm/GUI/editors/auto_save_editor.py) is a pop-up editor used to maintain
the “time list” of periodic h5 automatic saves: each row = one (checkbox + date-time), and a
checked row triggers one automatic save at the corresponding time. It is invoked by the
data-storage panels.
Components
| Component | Description |
|---|
AutoSaveEditor | Dialog for editing the list: loads/writes back the properties, adds, deletes and edits rows, and confirms (Accept) |
DateTimeWidget | Editing row for a single automatic save time: index + checkbox + date-time picker |
Main methods
| Method | Function |
|---|
load_AutoSaveEditor_props(props, prop_name) | Reads the saved date-times and check marks from the properties, padding them up to the maximum number of items |
update_datetime(idx, datetime_save) | Saves a new date-time for a row |
update_ticked(idx, ticked) | Saves the checked state of a row |
accept() | Confirms: writes the list back to the properties and closes |
Usage
from quatm.GUI.editors.auto_save_editor import AutoSaveEditor
editor = AutoSaveEditor(props=props, max_items=24, parent=parent)
editor.load_AutoSaveEditor_props(props, "H5Storage/auto_save_times")
6.21 - Experiment helpers
Optical-tweezer alignment and stray-field compensation: a toolkit for automated calibration and optimisation.
Overview
Quatm wraps routine experiment calibration tasks as alignment/helper tools. The modules live in
quatm/GUI/alignment/, together with a number of standalone entry-point widgets, and provide
one-click runs and automatic optimisation.
ODT alignment helper — OdtAlignmentHelper
quatm/GUI/alignment/odt_helper.py — builds a single-axis control block for each piezo mirror
axis of xODT/cODT, and provides overall run/terminate together with compensation voltage and BA
laser parameter settings. The complete alignment sequence for each axis is: full-range scan with
the laser off → fine scan near the ion → publish the fit result → move to the target position.
from quatm.GUI.alignment.odt_helper import OdtAlignmentHelper
helper = OdtAlignmentHelper(parent=parent)
helper.run_all() # run the full alignment sequence (thread pool keeps axes sequential)
helper.terminate_all() # safely stop all axes
Stray-field compensation — CompensationHelper
quatm/GUI/alignment/comp_helper.py — obtains the compensation voltage from a voltage scan along
each trap axis as the intersection of two fitted lines, minimising the stray field automatically.
| Component | Description |
|---|
PlotWidget | Plot widget with one tab per trap axis |
ControlWidget | Control area for each axis plus a shared parameter area |
SingleAxisWidget | Scan parameters and “find compensation voltage” (intersection of the two fit lines) |
CompVoltageScan | Performs the voltage scan at the high and low rf confinement |
from quatm.GUI.alignment.comp_helper import CompensationHelper
comp = CompensationHelper(parent=parent)
Piezo mirror control — PiezoMirrorControl
quatm/GUI/entries/piezo_mirror_controler.py — a simple panel that steers the direction of the
mirror driven by a linear actuator using arrow keys/buttons; W/A/S/D correspond to up/left/down/right.
from quatm.GUI.entries.piezo_mirror_controler import PiezoMirrorControl
mirror = PiezoMirrorControl() # standalone window; control with WASD or the arrow keys
mirror.show()
6.22 - Data models
DictSyncModel/ListSyncModel: maps Python dictionaries/lists onto Qt tables automatically.
Overview
quatm/GUI/widgets/models.py provides the base classes that map Python dictionaries / lists onto Qt table models (derived from ARTIQ’s models). The table interfaces of the experiment queue, preparation station and looper are all built on them.
Core classes
| Class | Description |
|---|
_SyncSubstruct | Helper wrapping dictionary operations on the underlying container: triggers the refresh callback after append/insert/pop |
DictSyncModel | Dictionary→table model base class: maps backing_store through the headers/dataNames columns |
ListSyncModel | List→table model base class (the backing store is a list) |
ScheduleModel | Model for the experiment queue (ExperimentQueue): tasks sorted by priority and then by task number |
PrepModel | Model for the preparation station / looper: the backing store is a list whose order can be changed freely |
Usage
from quatm.GUI.widgets.models import DictSyncModel
model = DictSyncModel(
headers=["Name", "Value", "Unit"],
dataNames=["name", "value", "unit"],
init={"name": "param1", "value": 100, "unit": "µs"},
)
Concrete business models customise their behaviour by implementing three hooks in a subclass: convert turns a key/value/column into cell content, sort_key returns the sort key, and flags decides per-cell flags such as whether a cell is editable.
6.23 - Collapsible components
CollapsibleDialog: a collapsible/expandable tree dialog.
Overview
quatm/GUI/widgets/theexpandables.py provides the collapsible/expandable tree dialog
CollapsibleDialog: each “section” consists of a tree item with an expand/collapse button and a
content widget, which suits configuration interfaces and advanced parameter settings.
Components
| Component | Description |
|---|
SectionExpandButton | Button that expands/collapses a section |
CollapsibleDialog | Dialog container; subclasses override define_sections() to define all panels |
CollapsibleDialog provides add_section(title, widget) to add a panel directly; subclasses may
also override define_sections() to organise the tree structure in detail with add_button /
add_widget.
Usage
from quatm.GUI.widgets.theexpandables import CollapsibleDialog
class SetupDialog(CollapsibleDialog):
def define_sections(self):
self.add_section("Camera settings", some_widget)
self.add_section("Trigger settings", trigger_widget)
6.24 - Parameter boxes
Input controls bound to experiment parameters: float, boolean and combo parameter boxes that write changes back immediately.
Overview
quatm/GUI/widgets/arg_boxes.py provides widgets that bind experiment parameter classes to Qt
input controls: when the user changes a value it is written back to the experiment parameter
(stored internally in SI units, converted for display as needed). They are usually created
automatically by the “experiment parameter panel / parameter system” according to the parameter
type.
Controls
| Control | Parameter type | Description |
|---|
FloatBox | Numeric parameter | Floating-point input; converts units via unit and display_multiplier before writing back |
BoolBox | Boolean parameter | Checkbox; the value is written back as a boolean |
ComboBox | List of strings | Combo box; takes the value from the string list by the selected index |
Usage (schematic)
from quatm.GUI.widgets.arg_boxes import FloatBox, BoolBox, ComboBox
# numeric parameter: shown in kHz in the UI, stored in SI (Hz) underneath
fbox = FloatBox(argument=param_freq, unit="kHz", display_multiplier=1e3)
bcheck = BoolBox(parName="enable", value=param_enable)
cbox = ComboBox(parName="source", argument=param_source) # the parameter class holds a stringlist
These controls are normally generated automatically by the parameter system from the experiment’s
declarations, so there is no need to instantiate them by hand.
6.25 - Experiment parameter panel
DefaultExp + Dashboard: a whole-device parameter control panel built automatically from the device database.
Overview
The Dashboard (quatm/GUI/entries/dashboard.py) is the “control panel” main widget driven by the
default experiment (DefaultExp): DefaultExp assembles the parameters of the whole machine
from the device database automatically, and Dashboard then arranges these parameters in sections
by integer/float/boolean and as a grouped grid, forming an adjustable parameter control interface.
Class hierarchy
DefaultExp (experiment base class: auto-assembles device properties and groups them)
└─ Dashboard (control panel: sections + grouped grid + adaptive column count)
└─ manager widgets (IntManager / FloatManager / BoolManager / ComboManager …)
Key features
- Automatic assembly:
DefaultExp.build() walks the device library and creates a parameter
object for every attr/generic_attr device, classifying it by its group. - Sections by type: the interface is split into integer / float / boolean sections, and the
parameters are grouped within each section.
- Adaptive column count:
adaptive_columns() reflows according to the window width (starting at
4 columns, adding one column per ~200 px), so that narrow windows are not squeezed. - Immediate effect: changing a spin box writes back to the properties and straight through to the
hardware (the run body
run() is empty, which suits purely manual tuning).
Usage (schematic)
from quatm.GUI.entries.dashboard import Dashboard, DefaultExp
# DefaultExp auto-assembles the device parameters; Dashboard handles layout and grouping
page = Dashboard(parent=parent)
The “Parameter system” page describes the manager control classes behind it.
6.26 - FPGA DAC control
A simple manual control interface for the FPGA: 16 DAC voltages and 32 TTL bitmask settings.
Overview
quatm/GUI/entries/fpga_controller.py provides simple manual FPGA control for debugging and
calibration: each DAC voltage is set with a labelled -10 … +10 V spin box, and the 32 TTL outputs
are set with a grid of checkboxes.
Components
| Component | Description |
|---|
dacv(v) | Converts -10..+10 V into the 16-bit raw DAC code used by the FPGA |
DACsingle | A single DAC channel: a labelled -10..+10 V spin box, effective as soon as it is written |
DACvalues | All 16 DAC channels stacked vertically |
TTLQW | The 32 TTL outputs: an 8×4 grid of checkboxes, written as a single 32-bit mask |
simple_page | The DAC + TTL manual control page |
centralWidget / mainQMW | Tab container and simple main window (with a File/quit menu) |
Usage
from quatm.GUI.entries.fpga_controller import DACvalues, TTLQW
# obtain the low-level FPGA driver handle (controller) from the device database
page = DACvalues(controller)
# TTL: checkbox states are combined into a mask by the "Apply/state change" callback and written to the FPGA
ttl = TTLQW(controller)
If the hardware is missing or a write fails, it only prints an error instead of crashing, which
makes the interface easy to debug without hardware.
6.27 - Background worker threads
Worker/WorkerSignals: a generic QRunnable background-task wrapper.
Overview
quatm/GUI/widgets/browser_workers.py and helper_workers.py provide a generic background-thread
wrapper based on QRunnable: time-consuming tasks are executed in a thread pool, and
results/exceptions/progress are passed back to the GUI thread through signals.
Signals
| Signal | Description |
|---|
finished | Task completed (no data) |
error(tuple) | (exception type, value, traceback string) |
result(object) | The result returned by processing, of any type |
progress(int) | Progress percentage |
Usage
from quatm.GUI.widgets.browser_workers import Worker
worker = Worker(callback=my_long_task, arg1, kw=...) # passed through to the callback
worker.signals.finished.connect(on_done)
worker.signals.error.connect(on_error)
QThreadPool.globalInstance().start(worker)
The two implementations are largely the same: helper_workers.py is reused for helper/analysis
background tasks, while browser_workers.py is reused for browser-style panels.
6.28 - Experiment timeline
FPGA experiment timing visualisation: a chart viewer for DAC and TTL output channels against time.
Overview
quatm/GUI/entries/experiment_timeline.py provides the experiment timeline (Timeline): it
loads an experiment timing JSON file (experiment.json), draws the variation of each DAC/TTL
output channel over time as step subplots, and provides a checkbox panel of output channels to
decide which curves are visible, which makes experiment timing logic easier to debug and verify.
Components
| Component | Description |
|---|
Timeline | Timeline viewer main window: file loading, plotting and channel control |
TimelinePlotter | Timing visualisation widget: plots the state of each active output against time |
SelectorWidget | Output-channel checkbox panel: select all / select none decides the visible curves |
Key methods (Timeline)
| Method | Description |
|---|
load_file | Loads and parses the timing JSON, constructs the time series and refreshes the channel selector |
plot_experimental_sequence | Creates a separate subplot for each output according to the channel selection and draws a step plot |
decode_ttl_bitmask | Splits the 32-bit TTL bitmask into set/clear commands for the individual channels |
construct_time_sequences | Converts the step-wise timing into times/vals sequences organised per channel |
reverse_device_db_transformation | Device-database reverse transformation: maps internal names back to readable names (including the gauge calibration table) |
Usage
from quatm.GUI.entries.experiment_timeline import Timeline
tl = Timeline()
tl.load_file("experiment.json") # load a timeline file and plot it
tl.show()
6.29 - Scroll window
A multi-channel real-time scrolling data viewer window: subplot buffers, data-stream subscription and dynamic refreshing.
Overview
quatm/GUI/entries/scrollwindow.py provides the scroll window (ScrollWindow): a BMainWindow
subclass that acts as a multi-channel real-time scrolling data viewer main window. It lays out
ScrollPlot subplots in the requested number (two per row), distributes data blocks to the
corresponding subplots and refreshes them uniformly; PlotDataEditor provides the data/image stream
selection drop-down.
Components
| Component | Description |
|---|
ScrollWindow | Main window: creates subplots by count, distributes data and refreshes |
ScrollPlot | Scrolling subplot: subscribes to a data stream, ring buffer + periodic refresh |
PlotDataEditor | Data-source selection panel: drop-down candidates written back to the property ydata |
Key methods
| Method | Description |
|---|
initPlots | Creates the scrolling subplots by count and places them in the layout (two per row) |
feedData | Feeds a new data block into the buffer data array |
updatePlot | Batch-refreshes the filled interval of the buffer into the curves of each subplot |
setnewData (ScrollPlot) | Timer-driven data pull and curve refresh |
dataSelectDialog (ScrollPlot) | Opens a dialog to select the data channels to plot |
subscribe_window (ScrollPlot) | Opens the subscription editor to change the data stream |
configureWindow (ScrollPlot) | Pauses refreshing and edits this plot’s configuration inline |
Usage
from quatm.GUI.entries.scrollwindow import ScrollWindow
win = ScrollWindow(name="PMT scroll monitor", parent=parent, n_plots=2)
Data-stream subscription, data-channel selection and display configuration are performed on each
subplot through its context menu; the window can also be started as a standalone process.
6.30 - Subscription editor
Manages the subscription list of data/image streams: adds, deletes and hot-updates subscribed sources.
Overview
SubscriptionEditor (quatm/GUI/editors/subscription_editor.py) manages the program’s
subscriptions to data streams/image streams: it loads the stored subscriptions from the property
database, lists the available streams for selection, supports adding/deleting, and hot-updates the
list when properties change. It is commonly used as the data-source selection component of live
plotting/monitoring panels.
Main methods
| Method | Function |
|---|
loadSubscriptions() | Reads the saved subscriptions from the property database and loads them into the list |
update_streamlist() | Rebuilds the drop-down candidates from the currently available data streams |
add() | Adds the stream selected in the combo box to the subscription list and synchronises the properties |
deletemarked() | Removes the subscription items marked for deletion and synchronises the properties |
handle_property_changes(changes) | Listens for property changes, refreshes the list and broadcasts the change |
Usage
from quatm.GUI.editors.subscription_editor import SubscriptionEditor
editor = SubscriptionEditor(props=props, category="Data", parent=parent)
editor.loadSubscriptions()
6.31 - Table parameter
A QTableWidget-based editor for numpy structured arrays, used in parameter trees.
Overview
quatm/GUI/widgets/table_parameter.py provides a table editing node for ParameterTree: the
table_parameter parameter type works together with the table_parameterItem widget item to edit
numpy structured arrays (such as calibration tables and scan sequences) directly in the parameter
tree.
Components
| Component | Description |
|---|
table_parameter | Parameter type: accepts a numpy structured array as its value |
table_parameterItem | Corresponding widget item: creates a QTableWidget as the editor from the array dtype |
table_parameterItem creates columns from the array dtype (including field name/type), and cell
edits are written back to the array immediately; valueIsDefault() is used to decide whether the
value needs to be persisted separately.
Usage
from quatm.GUI.widgets.table_parameter import table_parameter
import numpy as np
dt = np.dtype([("freq", float), ("amp", float)])
param = table_parameter(name="scan_table", value=np.zeros(10, dtype=dt))
6.32 - Remote ROI
An ROI component that publishes color/position/size through the central property database and can be synchronised remotely.
Overview
quatm/GUI/widgets/ROI.py provides ROI: it inherits ROI and communicates its color, position
and size through the central property database under a unique name, so that remote clients can view
and adjust the ROI region in sync.
Methods
| Method | Description |
|---|
update_from_properties | Refreshes the ROI pen color and geometry from the color/position/size stored in the properties |
Usage
from quatm.GUI.widgets.ROI import ROI
roi = ROI(name="analysis_roi", parent=view) # name must be unique; duplicate names share properties
roi.update_from_properties()
Note: name is the key of this ROI in the central property database, so make sure it does not clash
with any name in the property dictionary; after a property change, calling update_from_properties
refreshes the ROI appearance and position in sync.
6.33 - Image viewer
The scientific image viewing main window: a central image display with row/column profile and histogram LUT dock panels.
Overview
quatm/GUI/entries/image_group.py provides the image viewing window (ImageWindow): the central
ImageDisplay is the main widget, with panels such as the row/column profiles and the
histogram LUT docked around it, for real-time monitoring and analysis of imaging experiments.
Layout
| Dock area | Widget | Purpose |
|---|
| Central | ImageDisplay | Subscribes to and automatically refreshes an image stream; can overlay a mask |
| Top | LivePlot (row profile) | Intensity profile of an image row |
| Right | LivePlot (column profile) | Intensity profile of an image column |
| Left | HistogramLUTWidget | Histogram color scale: level range and false-color mapping |
Each profile plot is a LivePlot container in which an UpdatingPlot subscribes to the corresponding
data stream and refreshes in real time (see Live plotting for details).
Display parameter persistence
Display parameters are stored in the Viewer/ namespace of the property database; they are
restored from the properties at construction time and saved automatically on close:
| Property | Meaning |
|---|
Viewer/minlevels / Viewer/maxlevels | Histogram display level range |
Viewer/gradient | False-color gradient preset (such as magma) |
Viewer/tick_position | Color scale tick positions (0–1 normalised list) |
After dragging the histogram levels or the color scale ticks, the current values are written back to
the properties through update_min_max and get_tick_position respectively, and closeEvent saves
the view range and ticks uniformly before the window closes.
Methods
| Method | Description |
|---|
update_min_max | Writes the current histogram level range back to the properties (min/max levels) |
get_tick_position | Walks the color scale ticks and returns them normalised to the 0–1 range |
closeEvent | Saves the view range and color scale ticks to the properties before closing |
Usage
from quatm.GUI.entries.image_group import ImageWindow
win = ImageWindow(name="absorption_imaging") # standalone top-level window; parent may be omitted
win.show()
This window is often started as a standalone process, but can also be embedded in other interfaces
as a BMainWindow instance. The image stream and the row/column profile streams are subscribed from
the context menus of the individual sub-panels (image display / profile plots).
6.34 - Property checker
A property-distribution watchdog: verifies that the Properties hub is really broadcasting updates.
Overview
PropertiesChecker (quatm/GUI/entries/properties_checker.py) is a property-distribution
watchdog: it sets a marker through one property handle and reads it back through a second handle;
if the hub has not propagated the change, it raises an alert indicating that the property service may
have stalled. The check runs periodically on a timer, so the window is displayed normally and the GUI
event loop stays responsive.
Usage
from quatm.GUI.entries.properties_checker import PropertiesChecker
checker = PropertiesChecker(parent=parent)
checker.check_once() # run one watchdog check manually
The panel itself calls check_once() periodically (each time setting the marker true and then false
and reading it back to verify).
6.35 - Data plotter
A general-purpose measurement data plotting component: data source/multiple y axes, filtering, binning and fitting.
Overview
quatm/GUI/alignment/plotter.py provides the data plotter (DataPlotterWidget): it subscribes to
and plots data acquired during a measurement. It supports switching the data source, an x-axis
selector and multiple y-axis selectors; each y axis is processed through a configurable data chain
(scaling factor → filter → binning/fitting) before being drawn in the plot area, and the result can
be exported to PDF.
Components
| Component | Description |
|---|
DataPlotterWidget | Main plot: data source drop-down, x-axis selector and several y-axis selectors (right-click toggles the configuration panel) |
YAxisWidgetHelper | y-axis data chain: scaling factor, filter, binning and fitting |
Key methods
| Method | Description |
|---|
setDataSource | Sets or changes the data source |
checkForNewData / updateDatasources | Checks for new data/new data sources and refreshes |
plotData | Adds a set of x/y data to the plot area as a new curve |
toggleConfig | Toggles the data source and y-axis configuration panels |
exportpdf | Exports the current plot as a PDF suitable for archiving |
clear | Clears the data summary and all curves in the plot area |
Y-axis chain (YAxisWidgetHelper): updateYscale sets the scaling factor, the context menu offers
addBinning for binning and addFitting for fitting (such as Gaussian/linear), and reevaluateData
re-runs the whole data chain after a filter or similar change.
Usage
from quatm.GUI.alignment.plotter import DataPlotterWidget
plotter = DataPlotterWidget(
axis_name="myaxis",
prop_root="...",
datastream_names=[".../data"],
n_yaxis=1,
parent=parent,
)
plotter.toggleConfig() # expand the data-source / x, y axis selection config
7 - Communication layer
ZeroMQ Hubs, clients, properties and configuration.
Quatm’s services communicate over ZeroMQ. The design keeps the timing path independent of rendering, and makes local and remote clients interchangeable.
Hubs and streams
Each data type has a Hub that collects and redistributes messages:
- scalar data streams;
- image streams;
- command channels;
- property updates;
- messages and logs.
Clients connect to the Hubs they need. A client process does not need to know which other clients exist, and a Hub failure is visible and restartable.
Client types
| Client | Purpose |
|---|
DataClient | subscribe to scalar data series |
ImageClient | subscribe to camera images |
CommandClient | send commands to a controller |
MessageClient | publish and receive operational messages |
Properties | synchronise the distributed property tree |
Properties
The property tree is the shared configuration and state model. Properties are read and written by name through a filesystem-like path; the Property Logger persists the tree and applies the persistence policy. GUI panels, web dashboards and experiment code all read the same values.
Remote property writes use a persistent publisher and explicit acknowledgement, so a remote action is not silently lost when a connection is slow.
Transport choices
- IPC for processes on the same machine (lowest latency).
- TCP for split control/analysis machines and remote clients.
Curve encryption is available for local and network channels. The transport layer also handles multipart framing, malformed-frame recovery and request timeouts.
Configuration
Endpoints, project root and service selection are read from the project’s configuration/ directory. A service can be launched by the Operations Console, by a project launcher, or directly as a Python module for debugging.
7.1 - Clients
Data/Image/Command clients: publish/subscribe communication (TCP/IPC), with connection targets set by the central configuration.
Overview
quatm/servers/clients.py provides the communication clients: the connection target is determined by the central configuration (Data / Image / Command Hub). GenericClient offers the standard send/receive interface and is suited to small data (<10k); use ImageClient for large payloads such as images.
Client classes
| Class | Connection | Description |
|---|
GenericClient | Determined by configuration | General send/receive (including numpy arrays), subscribe/unsubscribe |
DataClient | Data Hub | Publish and subscribe ordinary data |
ImageClient | Image Hub | Sending large image-scale data (send takes a numpy array directly) |
CommandClient | Command Hub | Sends control commands to drivers (the command name is folded into the topic) |
NpEncoder | — | JSON serialization of numpy types |
Key methods (GenericClient)
| Method | Description |
|---|
send(datadict, arr=None) | Publish data (optionally with one numpy array) |
recv() | Receive one frame: (topic, dict[, array]); returns None on timeout |
subscribe / unsubscribe | Subscribe / unsubscribe a channel |
has_new_data | Non-blocking check for new data |
close | Close the PUB/SUB socket (idempotent) |
Usage
from quatm.servers.clients import DataClient
dc = DataClient(name="pmt")
dc.send({"pmt": 1234}, arr=numpy_array)
topic, data = dc.recv()
7.2 - Property tree
Distributed hierarchical property tree: the local copy is synchronized bidirectionally through the Property Hub and Propertylogger.
Overview
quatm/servers/properties.py provides the property tree (Properties): it manages the configuration and properties of the control centre and maintains a synchronized copy of the property dictionary. A local set / get publishes to the Property Hub, and central changes are pushed back to the local copy. Properties are addressed like a file system (for example Drivers/xxx, or an absolute path starting with /).
Key methods
| Method | Description |
|---|
set(key, value) | Set a property and publish the change to the Property Hub |
get(key, default=...) | Read a value (always returns a deep copy; creates the default if missing) |
delete(key) | Delete an entry (including all its children) and broadcast the change |
changes() | Return the keys changed since the previous call |
close / close_all | Close sockets and background threads |
PropertyAttribute exposes a single property of the property tree as an ordinary Python attribute (the host must have _props).
Usage
from quatm.servers.properties import Properties
props = Properties()
props.set("laser/power", 50e-3)
power = props.get("laser/power")
7.3 - Data management
Multi-stream data aggregation: aligned to each experiment run, missing streams zero-filled on timeout, then written to HDF5.
Overview
quatm/servers/datamgr.py provides data management (DataSummary): it aggregates the data streams of several input channels and assembles them into a complete dictionary for each run. Incoming data is first accumulated in incompleteData, and once the data of all subscribed channels has arrived the whole record is moved to completeData; if some data is still missing when the run ends and times out, the missing parts are filled with zeros.
Key methods
| Method | Description |
|---|
initDataq | (Re)subscribe to the channels configured in the properties and to the experiment start/end events |
recvData / processIncomingData | Receive data and merge it into the most recent run record |
savetoFile | Write the collected run dictionary to a structured HDF5 dataset |
loadFromFile | Read a set of stored run dictionaries back from HDF5 |
clear | Clear all data collected so far |
run_forever | Blocking loop that keeps receiving data |
Usage
from quatm.servers.datamgr import DataSummary
summary = DataSummary(name="sum")
summary.initDataq() # Subscribe to channels configured in properties
summary.run_forever() # Keep receiving; savetoFile writes the run dictionary to .h5
7.4 - Messaging
Standardized message stream client: sends timestamped, source-tagged messages to the Message Hub.
Overview
quatm/servers/messageclient.py provides the messaging system: MessageClient is the standardized message stream client, sending messages with a timestamp and originating file to the Message Hub (PUB). Everyday code should generally not construct it directly but use the module-level send functions instead.
Send functions
| Function | Description |
|---|
send_info(msg) | Info level: enters the message stream as an ordinary notification |
send_warning(msg) | Warning level: enters the message stream and raises a warning notification |
send_error(msg) | Error level: enters the message stream and raises an error notification |
send_debug(msg) | Debug level: shown only on the debug channel |
Usage
from quatm.servers.messageclient import send_info, send_error
send_info("Experiment started")
send_error("Camera connection timed out")
7.5 - Configuration and property persistence
configreader + propertylogger: reading JSON configuration, persisting the property tree and monitoring active streams.
Modules
| Module | Function |
|---|
configreader.ConfigReader | Read-only wrapper: reads configfile.json (getConfiguration()) |
configreader.Config() | Reads configfile.json and returns the configuration dictionary |
configreader.Properties() | Reads the properties file properties.json and returns a dictionary |
propertylogger.run_propertylogger() | Property tree persistence + INIT acknowledgement (Property Logger service) |
datalogger.DataStreamLogger | Monitors active data streams and writes them into the property tree |
imagelogger.ImageStreamLogger | Monitors active image streams and writes them into the property tree |
Usage
from quatm.servers.configreader import ConfigReader
cfg = ConfigReader()
config = cfg.getConfiguration() # Full configuration dictionary
7.6 - Peripheral service modules
Peripheral services such as InfluxDB, notification push, the image network server, the Web Hub and the XSUB/XPUB proxy.
Service list
| Module | Function |
|---|
influxdb | InfluxDB client wrapper: hardware status writes (data2db) and historical queries (query_data); can run as an RPC service |
notifications | Notification push: configuration service, push_message push, post_to_mattermost compatibility entry point |
image_network_server | Image network service: flushes the latest frame of each image stream to a Web figure |
webhub | Server entry point of the Web remote control platform (quatm.web; takes host/port from the Servers section of configfile) |
xsub_xpub | XSUB/XPUB forwarding server (run_server) |
experiment_status | Experiment status monitoring / early-warning notifications |
propertylogger / datalogger / imagelogger | Property tree persistence and monitoring of active data/image streams (see Configuration system) |
Usage
from quatm.servers.notifications import push_message
push_message("Experiment finished") # Sent through enabled push services (never raises)
8 - Deployment
Single workstation, split control/analysis machines and remote access.
Single workstation
The simplest deployment runs the GUI, services and experiment runtime on one PC. Start the project launcher; it opens the Operations Console and starts configured services. Closing the last window stops all services cleanly.
Split control and analysis
For larger experiments, run data acquisition and hardware control on the experiment PC, and analysis/GUI on a second machine:
- configure the data and property Hubs to listen on a reachable TCP endpoint;
- connect the analysis clients to that endpoint;
- keep the experiment network behind a firewall or VPN.
The transport layer recovers from malformed frames and slow clients; a slow analysis machine cannot block acquisition.
Remote access
The web console can be reached over SSH tunnelling or a reverse proxy with authentication. Do not expose control endpoints directly to an untrusted network. See the web console README for token and endpoint guidance.
Production checklist
- Pin the Python environment and driver versions.
- Back up
configuration/ and the property database. - Monitor disk headroom for HDF5 acquisition.
- Verify device leases and service health after a reboot.
- Run the release precheck and a simulated experiment before a campaign.
- Keep the desktop and web console versions matched.
Updates
Upgrade the framework in a staging environment first. Check the changelog for incompatible changes, regenerate the project if the template changed, and verify that project-local drivers still import. scripts/check.sh contains the same gates used by CI and is a useful post-upgrade smoke test.
9 - Application examples
End-to-end examples from real experiments.
The repository contains complete experiment examples. They are intended to be read together with their project configuration, not as standalone snippets.
| Example | Demonstrates |
|---|
| MOT loading optimisation | absorption imaging, MOT sequence, parameter scans and live evaluation |
| Absorption imaging | camera handling, ROI analysis and optical-density calculation |
| Optical dipole trap | trap loading, intensity ramps, calibration and lifetime measurement |
| Ion loading / state detection | ion-trap control, detection thresholds and event processing |
| Quantum-state detection | repeated readout, statistics and state assignment |
| Atom–ion interaction | combining cold-atom and ion-trap subsystems in one experiment |
| System calibration | reference measurements, fitting and calibration persistence |
Each example project includes a README.md, configuration/ and experiments/. Start with the project launcher and run it in simulation mode before connecting hardware.
Structure of an example
- Physical goal — what is measured and why.
- Device setup — the device database entries and calibration files.
- Experiment class — parameters, sequence and analysis hooks.
- Data and analysis — expected HDF5 content and fitting.
- Operational notes — safety checks, common failures and tuning advice.
9.1 - Atom–ion collision experiment
Study the collision dynamics between cold atoms and a single ion with Quatm’s coordinated multi-system control.
Overview
This experiment provides the class secondinteraction: it integrates the lithium-atom MOT with ion
loading, state preparation and shuttling into one atom–ion collision/interaction experiment. Each stage
is encapsulated in its own method, which makes it easy to reuse and extend.
Stage methods
| Method | Description |
|---|
load_ions | Ablation loading + laser cooling of the ions |
count_ions | Fluorescence counting to determine whether an ion is in the trap |
Ion_to_D_state / Ion_to_S_state | State preparation |
load_MOT / MOT_off | Loads / switches off the lithium MOT |
ion_state_preparation | Ion state preparation before the interaction |
run | Orchestrates the loading–state preparation–collision–detection sequence |
Parameters (build excerpt)
Loading_Time, Nr_Ablation_Pulses, Det_Time, Nr_Pictures,
loadingtime_MOT, detection_time, RF_Voltage_Final and others.
Key code (excerpt)
from quatm.experiment import Experiment, delay, realtime
class secondinteraction(Experiment):
def run(self):
self.load_ions() # ablation loading + laser cooling
self.Ion_to_S_state() # prepare in S1/2
self.load_MOT() # load the lithium MOT
# interaction … then imaging/counting
9.2 - Dipole-trap optimisation helper
ODT alignment with the experiment helper tool: xODT alignment + stray-field compensation.
Overview
Before an optical dipole trap (xODT/cODT) experiment, the beam has to be aligned onto the atoms/ions
with the dipole-trap optimisation helper. The experiment-helper main window odt_utils on the GUI
side (quatm/GUI/entries/odt_utils.py) aggregates two kinds of alignment tool as tabs:
| Tab | Real component | Purpose |
|---|
| xODT alignment | alignment/odt_utils.odt | piezo-mirror axis scan → Gaussian fit → publish, aligning the xODT/cODT onto the atom cloud |
| Stray-field compensation | alignment/odt_utils.compensation | find the compensation voltage along each trap axis to minimise the stray field |
Usage
from quatm.GUI.entries.odt_utils import odt_utils
helper = odt_utils() # started as a separate process, with xODT alignment and stray-field compensation tabs
For the complete description see Experiment helper in the GUI components section; for the ODT
experiment after alignment see Optical dipole trap.
9.3 - Electric-field offset compensation helper
Compensating the stray electric field of an ion trap: a GUI tool + a trap-axis voltage-scan script.
Overview
Ion-trap experiments are often affected by stray electric fields (ion-position offsets/micromotion). The
electric-field offset compensation helper brings together voltage scanning, data plotting and the
determination of the compensation voltage: on the GUI side it is provided by CompensationHelper in
quatm/GUI/alignment/comp_helper.py; on the experiment side Usage Example/experiments/compensation.py (class compensation) applies shim voltages to the ion and triggers multi-frame
images, scanning axes such as trap_x / trap_y.
GUI components (comp_helper)
| Component | Description |
|---|
Compensation | Compensation main panel (one plot per trap axis + controls) |
PlotWidget / TabWidget | Plot with one tab per trap axis |
ControlWidget / SingleAxisWidget | Scan controls per axis and “find the compensation voltage” (intersection of two fitted lines) |
CompVoltageScan | Executes a voltage scan on the rf confinement |
Usage
from quatm.GUI.alignment.comp_helper import CompensationHelper
comp = CompensationHelper(parent=parent)
For a fuller description see Experiment helper.
9.4 - Ion loading and cooling
A complete ion-loading routine from laser ablation to fluorescence detection, using Quatm’s multi-stage timing control.
Overview
This script provides the class loading_ion: it orchestrates the complete ion-loading routine from
switching on the RF trap, producing ions by ablation and laser cooling through to fluorescence
detection. The number of ablation pulses, the pulse width and the cooling/detection durations are all
GUI parameters.
Parameters (build excerpt)
Nr_Ablation_Pulses, tune_pulse_number, pulse_duration, det_time, shutter_time, V_rf (trap voltage), ion_cool_power.
Timing
Switch on the RF trap (V_rf) → ablation pulses (several) → laser cooling
→ fluorescence detection (count to decide whether the ion loaded successfully)
Key code (excerpt)
from quatm.experiment import Experiment
class loading_ion(Experiment):
def build(self):
self.setattr_argument("Nr_Ablation_Pulses", NumberValue(...))
self.setattr_argument("V_rf", NumberValue(...))
# ... remainder omitted
ablation_pulse.py (single ablation), deterministic_loading.py and
loading_deterministic.py (deterministic loading: retry on failure).
Companion
The cooling and fluorescence counting after ion loading can be combined with
quantum-state detection for state-dependent measurements; the default parameters of the RF
trap are in rf_trap.py.
9.5 - MOT loading optimisation
Automatically optimise the magneto-optical trap loading efficiency with Quatm’s parameter scans and multi-dimensional timing control.
Overview
This experiment script provides the class mot_settings: build() declares the full set of lithium
MOT/CMOT devices and exposes the axial / radial magnetic-field currents and the MOT/CMOT detunings as
GUI parameters, so that the magneto-optical trap loading can be optimised manually or by scanning.
run() contains an empty timing sequence, on top of which imaging/counting can be added to assess the
loading.
Devices (build excerpt)
| Device | Purpose |
|---|
li_cooling_aom / li_repump_aom / li_zeeman_aom | cooling / repump / Zeeman light switches |
li_cooling_power / li_repump_power / li_zeeman_power | power of the individual beams |
li_lock_mot / li_lock_cmot | frequency-lock references |
Parameters (build excerpt)
MOT_Axial / _Radial, CMOT_Axial /
_Radial (gradient/homogeneous-field currents), MOT_Current / CMOT_Current (trap currents),
MOT_detuning / CMOT_detuning (detunings).
Key code (excerpt)
from quatm.experiment import Experiment, NumberValue
class mot_settings(Experiment):
def build(self):
self.setattr_device("li_cooling_power")
self.setattr_argument("MOT_detuning", NumberValue(...))
# ... remaining devices/parameters omitted
Loading optimisation
After changing the magnetic-field currents and the detuning, the loading efficiency can be judged from
the atom number measured by fluorescence counting or absorption imaging (see Absorption imaging
in the same directory).
9.6 - System calibration
Automated applications of Quatm in routine system calibration and maintenance.
Overview
Besides physics experiments, Quatm also codes routine calibration tasks as standard experiment scripts,
giving one-click execution and automatic recording. This covers common calibration needs such as camera
dark noise, shutter response, frequency synthesiser verification and trigger-delay calibration.
Code structure
All calibration experiments follow the same structural pattern:
- subclass the
Experiment class - declare the device under test and the calibration parameters in
build() - execute the calibration procedure in
run() and record the results automatically
Typical calibration tasks
Camera dark-noise characterisation
Function: acquire image frames continuously under dark conditions and characterise the dark-current noise distribution.
Main steps:
- Switch off all light sources and set the camera exposure time
- Acquire N dark frames in a loop
- Compute the mean and the standard deviation of each pixel
- Save the noise map to HDF5
Devices involved: camera
Shutter response time measurement
Function: measure the delay of a mechanical shutter from the TTL trigger to fully open/closed.
Main steps:
- Align a photodetector with the beam behind the shutter
- Trigger the shutter open with a TTL signal and start high-speed acquisition synchronously
- Record the rise/fall edge times of the detector signal
- Repeat the measurement and average
Devices involved: shutter (TTL), photodetector
Frequency synthesiser verification
Function: verify the actual output frequency and power of a Valon 5009 or DDS frequency synthesiser.
Main steps:
- Set the frequency synthesiser to the target frequency
- Measure the actual output with a spectrum analyser
- Scan the frequency range and record the deviation
- Generate a calibration curve
Devices involved: Valon 5009/DDS (serial port), spectrum analyser
Camera trigger delay calibration
Function: measure the delay between the TTL trigger and the actual exposure window of the camera.
Main steps:
- Share the same TTL signal between the pulsed light source and the camera trigger
- Scan the trigger-delay parameter
- Detect the position at which the light pulse appears in each frame
- Calibrate the optimal delay value
Devices involved: camera, pulsed light source (TTL)
9.7 - Absorption imaging
Automated acquisition and analysis of atom-cloud absorption images with Quatm’s camera control and image analysis.
Overview
This experiment script provides the class absorption_imaging: build() declares the cooling/imaging
light and the camera trigger and exposes the MOT loading, the probe flash and the free-flight expansion
time as parameters; run() orchestrates the imaging sequence and provides two detection modes,
fluorescence_imaging and absorption_imaging.
Parameters (build excerpt)
Mot_loading, Flash_us (duration of the probe flash), expansiontime_ms (free-flight expansion),
image_detuning (imaging detuning), MOT_detuning.
Devices (build excerpt)
li_cooling_aom / li_repump_aom / li_zeeman_aom (cooling/repump/Zeeman),
li_absorption_h (horizontal absorption probe beam), li_imaging_valon + dds_0 (imaging frequency source),
trigger_radba (trigger).
Key code (excerpt)
from quatm.experiment import Experiment, NumberValue, delay
class absorption_imaging(Experiment):
def build(self):
self.setattr_device("li_absorption_h")
self.setattr_device("li_imaging_valon")
self.setattr_argument("expansiontime_ms", NumberValue(...))
# ... remainder omitted
def absorption_imaging(self):
# three frames with atoms / without atoms / background, computing the optical depth
...
Data analysis
The optical depth (column density) is computed and published by the analysis process ImageDivider, see
Data and analysis/Absorption imaging.
9.8 - Atom–ion interaction in an optical dipole trap
Example experiment on the interaction of a neutral atom with a single ion in an optical dipole trap.
Overview
In this example we combine a lithium MOT / optical dipole trap (ODT) with the cooling, shuttling and
state detection of a barium ion into one complete atom–ion interaction experiment. The experiment
class interaction_optical_trapping subclasses Experiment, and the main sequence is assembled in
stages by methods such as init_CMOT, absorption_imaging, expose_camera and run.
A typical sequence is roughly: load the MOT and pre-cool the ion and shuttle it “out of the way”, then
load the atom cloud into the CMOT and the optical dipole trap, and after evaporation and spin
polarisation move the ion back and let it interact with the atoms (loss spectroscopy or Feshbach
resonance measurements are possible); finally perform optical thermometry, absorption imaging and
detection of the ionic product state.
Main devices
| Category | Device (excerpt) | Purpose |
|---|
| Lithium MOT/CMOT | li_cooling_aom / li_repump_aom / li_zeeman_aom, dds_0/1, li_mot_det_switch | cooling/repump/Zeeman light and frequency, detuning switching |
| Magnetic field | b_quadrupole, b_homogen, quadrupole_fet_off, homogen_fet_off, Feshbach coil relays | MOT gradient, homogeneous field, Feshbach/evaporation field |
| Ba lasers and states | aom_1(_ttl), aom_2(_ttl), the shutters, trigger_cam | ion cooling, pumping, ion state detection |
| Optical dipole trap | li_odt_* (xODT/cODT), laser power | optical trap for atoms/ions |
| Imaging | li_absorption_h, li_imaging_freq, trigger_abs | absorption imaging and vertical trigger |
MOT loading → ion pre-cooling and shuttling (making room for the atom loading)
→ CMOT (optional high-gradient/power/detuning stage) → transfer into the ODT / two-colour trap
→ evaporative cooling (optional rf association) → spin polarisation
→ ion moved back into the atom cloud → interaction / loss / Feshbach measurement
→ trap-frequency measurement / time of flight (TOF) → ion optical thermometry → absorption imaging + ion state detection
Key code (excerpt)
from quatm.experiment import NumberValue, BoolValue, Experiment, delay, realtime
class interaction_optical_trapping(Experiment):
def build(self):
# devices for the lithium MOT/CMOT
self.setattr_device("li_cooling_power")
self.setattr_device("li_cooling_aom")
self.setattr_device("b_quadrupole")
self.setattr_device("b_homogen")
# ... remaining devices omitted
def run(self):
# keep the ion inside the atom cloud during the interaction
with realtime():
self.li_cooling_aom = False
delay(0.5)
# ... timing omitted
Running
After placing the script in the corresponding directory under experiments/, open it in the experiment
browser (Browser) to see the parameter panel generated by build; clicking run executes the whole
sequence according to the timing in run. The script can also serve as a template for writing custom
atom–ion interaction experiments (note the ion-shuttling ramps, the CMOT loading and the ODT transfer
stages).
9.9 - Quantum-state manipulation and detection
Optical pumping and state-dependent fluorescence detection of ionic quantum states, using Quatm’s precise timing and statistical analysis.
Overview
This experiment script provides the class state_prep: it optically pumps the ion into the S1/2 or
D3/2 state and can optionally apply one more cooling cycle at the end. State-dependent detection
then distinguishes the bright/dark states by fluorescence counting.
Parameters
on_s_off_d (pump to S1/2 (650 off) or to D3/2 (493 off)),
cooling_end (whether to add one cooling step before the end).
State-preparation logic
- S1/2: switch off the 650 light first → the ion falls into S1/2;
- D3/2: switch off the 493 light first → the ion is pumped into D3/2.
The same logic also appears in Ion_to_S_state / Ion_to_D_state of secondinteraction, where
count_ions fluorescence counting performs the state-dependent detection (bright = S1/2, dark = D3/2, etc.).
Key code (excerpt)
from quatm.experiment import Experiment, BoolValue
class state_prep(Experiment):
def build(self):
self.setattr_argument("on_s_off_d", BoolValue(value=False))
self.setattr_argument("cooling_end", BoolValue(value=True))
# ... remainder omitted
optical_dark_pumping.py (dark pumping, pumping_time_us), d_shelving.py
(D-state related measurements).
9.10 - Optical dipole trap experiment
Study the trapping and loss characteristics of cold atoms in an ODT with Quatm’s two-parameter scans and lifetime measurements.
Overview
This experiment script provides the class optical_trapping: build() declares the lithium MOT/CMOT
and the imaging devices and exposes the key timing parameters for loading / trapping / imaging as GUI
parameters; run() orchestrates the complete optical dipole trap (ODT) experiment sequence through
methods such as init_CMOT, fluorescence_imaging and absorption_imaging.
Timing stages
MOT loading (Mot_loading) → CMOT (init_CMOT: gradient/detuning/power switching)
→ ODT trapping (trapping_time_ms) → release → time of flight (time_of_flight)
→ absorption imaging / fluorescence imaging
Parameters (build excerpt)
Mot_loading, Flash_us, trapping_time_ms, resonance_shift,
image_detuning, detuning_ramp_slope_MHz_ms, time_of_flight,
MOT_detuning / CMOT_detuning, MOT_Current_Loading / CMOT_Current and the
Homogenous_Current_* series, etc.
Key code (excerpt)
from quatm.experiment import Experiment, NumberValue, delay
class optical_trapping(Experiment):
def build(self):
self.setattr_device("li_cooling_aom")
self.setattr_device("li_absorption_h")
self.setattr_argument("trapping_time_ms", NumberValue(...))
# ... remainder omitted
def init_CMOT(self):
# CMOT stage: gradient, detuning and power switching
...
Companion
The xODT/cODT beam path can be aligned with the dipole-trap optimisation helper; for imaging data
analysis see Absorption imaging and Absorption imaging in the data and analysis section.
9.11 - Optically trapped ion
Purely optical trapping of an ion in an optical dipole trap, with state detection.
Overview
This example uses the experiment class OpticalTrappingIon to demonstrate purely optical trapping
of a single ion in an optical dipole trap. The radio-frequency trap (rf) can optionally be switched
off in order to measure the performance of the pure optical trap, and optical trapping can also be
performed in an adjustable magnetic field; together with ion laser cooling, spin preparation and
product-state detection, the ion can also be moved into and out of the trap via the axial electrodes.
Main devices
| Category | Device (excerpt) | Purpose |
|---|
| Ba lasers | ba_493_aom(_ttl), ba_493_vert_aom, ba_650_aom(_ttl), ba_493_res_shut, ba_493_det_shut, ba_615_shut | cooling, pumping (S1/2↔P1/2, D3/2, etc.), shutters |
| Optical dipole trap | odt_power / odt_aom / odt_shut | power and on/off of the optical trapping beam |
| RF trap | rf, rf_off | radio-frequency Paul trap confinement (can be switched off for a pure optical trap) |
| Axial movement | trap_ax, trap_ax_pc, trap_ax_wall, trap_ax_offs | axial shuttling of the ion |
| Magnetic field | b_homogen, shim | quantisation field for pre-cooling/trapping |
| Detection | trigger_cam | trigger for radial ion fluorescence/state detection |
Parameters (excerpt of setattr_argument in build)
trapping_time, odtpower, rf_init and
rf_ramp_duration, opt_ramp_duration, repumpers_on, pump_d32,
ba_spin_prep, b_field_precooling(_applied) / b_field_opt_trap(_applied),
shuttle_ion / shift_voltage, shutter_time, detect_ba_product_state and others.
Timing stages
Initialisation
→ ion spin preparation
→ magnetic-field switching (optional high-field probing)
→ (optional) shuttle the ion out and back
→ linear ramp-up of the optical trapping beam power → switch off the RF confinement (pure optical trapping)
→ switch off the optical trap (release) and the magnetic field
→ product-state/fluorescence detection
Key code (excerpt)
from quatm.experiment import * # Experiment, NumberValue, BoolValue, delay, realtime ...
class OpticalTrappingIon(Experiment):
def build(self):
self.setattr_device("ba_493_aom")
self.setattr_device("odt_power")
self.setattr_device("rf")
self.setattr_argument("trapping_time", NumberValue(ndecimals=0, step=10,
value=100, unit="µs"))
# ... remaining parameters omitted
def run(self):
with realtime():
# ramp up the optical trapping beam and switch off the RF confinement for pure optical trapping
self.odt_power = self.odtpower
self.rf_trap_off = True
# ... timing omitted
Running
Once the script is placed in experiments, opening it in the experiment browser (Browser) generates
the parameter panel; adjusting odtpower and trapping_time scans the power and duration of the
optical trapping, while detect_ba_product_state reads out the ionic product state.
10 - Experiment framework
The experiment runtime system: task queue/execution engine, loops, sequence editing, timing inspection and run monitoring.
Overview
The experiment framework covers the complete chain from writing an experiment through scheduling to running it:
- Experiment scripts: subclass
Experiment and implement build / run (for an introduction see
Writing your first experiment; for device mapping see
Configuring instruments); - Scheduling and execution: the experiment queue (ExperimentQueue) → the execution engine (ExperimentManager) fetches tasks by
priority/reservation time; the preparation station and the looper participate in enqueueing;
- Editing and monitoring: sequence editing/simulation, FPGA timing inspection, run monitoring, the multithreaded task framework and the base
window widgets.
Scheduling and execution
Editing, timing and monitoring
10.1 - Data Widgets
Experiment parameter editing widgets: FloatBox/BoolBox/ComboBox, writing changes straight back to the parameter attribute.
Overview
quatm/GUI/widgets/arg_boxes.py provides data widgets: a family of input boxes that present
experiment parameters as editable controls. FloatBox / BoolBox / ComboBox and the other widgets
subclass the corresponding manager (FloatManager / BoolManager / ComboManager), carry a unit,
range and step, and write a changed value straight back to the attribute (including last_set); they
are used by the parameter grid of the experiment window and by the parameter system.
Components
| Component | Description |
|---|
FloatBox | Floating-point parameter input box |
BoolBox | Boolean parameter checkbox |
ComboBox | Drop-down list parameter |
IntManager / FloatManager / BoolManager / ComboManager / FrequencyManager | Widget managers for the individual parameter types |
Usage
from quatm.GUI.widgets.arg_boxes import FloatBox, BoolBox, ComboBox
# usually the parameter system generates the matching widget from the parameter declaration
For a fuller description see Parameter boxes and
Parameter system.
10.2 - Base Window Widgets
GUI base widgets: BWidget/BFrame/BMainWindow and searchable combo boxes, etc.
Overview
quatm/GUI/widgets/quatmQt.py provides the GUI base widget classes: BWidget / BFrame / BMainWindow are the base classes of the various windows/panels/main windows (with a property namespace _props, saving the geometry to QSettings on close and restoring it on start-up); SearchComboBox and others provide searchable combo boxes with autocompletion and filtering.
Components
| Component | Description |
|---|
BWidget | Base QWidget (properties + geometry persistence) |
BFrame | Base QFrame |
BMainWindow | Base main window (QMainWindow + title/geometry persistence) |
CustomCompleter | Completer supporting contains / begins matching |
SearchComboBox | Searchable combo box (with a built-in SearchLineEdit) |
SearchLineEdit | Custom line edit of SearchComboBox (select all on focus, Esc restores) |
Usage
from quatm.GUI.widgets.quatmQt import BMainWindow, BWidget, BFrame
win = BMainWindow(name="My window") # geometry persisted automatically
For a fuller description see GUI base classes.
10.3 - Sequence Editor
Experiment code editing and sequence simulation: syntax highlighting, parsing and output-channel timing visualisation.
Overview
quatm/GUI/editors/editor_sequencer.py provides the sequence editor (edit and simulate):
the CodeEditorParser composite window combines the CodeEditor code editor on the left with the
sequence visualisation on the right; after editing an experiment script you can simulate a run with one
click and the timing of the individual output channels is drawn as step plots.
Components
| Component | Description |
|---|
CodeEditor | Code editor: line numbers, Python highlighting, automatic indentation and completion |
PythonHighlighter / LineNumberArea | Syntax highlighting / line-number margin |
ExperimentParser | Parses an experiment: buildExperiment / simulateExperiment return the command stack |
ExperimentSequencer | Draws the sequence over time as a step plot for each output channel |
CodeEditorParser | Composite window: Save / Simulate / Clear toolbar + dock layout |
Usage
from quatm.GUI.editors.editor_sequencer import CodeEditorParser
win = CodeEditorParser(parent=parent, filename="experiments/my_experiment.py")
For a fuller description see Sequence editor.
10.4 - Experiment Run Manager
The core experiment execution engine: takes tasks from the queue and schedules them by measurement/sequence/run level.
Overview
quatm/GUI/experiment/experiment_manager.py provides the experiment run manager
(ExperimentManager): the engine that takes tasks out of the experiment queue and executes them. The
queue polling runs in a worker thread, and tasks are scheduled in the hierarchy measurement
(measurement) → repetition (repetition) → sequence/scan (sequence) → single run (run).
Key methods
| Method | Description |
|---|
queue_fn | Continuously polls the task dictionary: picks tasks by priority, task number and reservation time |
run | Task-run wrapper: guarantees teardown however the task ends |
pause | Pauses / resumes the current experiment |
terminate_experiment | Graceful termination: lets the current run finish naturally before stopping |
start_measurement / start_sequence / end_sequence / end_measurement | Hooks for the individual experiment stages |
set_dict | Writes a single parameter to an attribute of the experiment object |
set_run_nr / set_rep_nr | Synchronises the run / repetition number |
start_queue / shutdown | Starts / stops the resident queue worker |
Usage
from quatm.GUI.experiment.experiment_manager import ExperimentManager
manager = ExperimentManager(browser) # browser supplies the queue and the interface
manager.start_queue()
For a fuller description see Experiment manager.
10.5 - Experiment Browser
The main application browser: file tree, queue/preparation station/looper and MDI experiment windows.
Overview
quatm/GUI/browser/ provides the experiment browser (Browser): the main application window of
Quatm, built on QMainWindow + QMdiArea. The FileSelector on the left browses the experiment
scripts, the experiment queue, preparation station and looper are docked at the top, and each
experiment has its own ExperimentWindow sub-window in the central MDI area.
Core components
| Component | Module | Description |
|---|
Browser | browser/main_window.py | Main window: MDI + dock widgets + lifecycle |
FileSelector | browser/file_selector.py | Experiment file tree (double-click to open/simulate) |
ExperimentWindow | browser/experiment_window.py | Single-experiment control panel (parameters/submit/scan) |
ExperimentQueue / PrepStation | experiment/ | Queue and preparation station (docked at the top) |
Looper | looper/ | Conditional looper (docked at the top) |
Lifecycle
open_experiment: opens an experiment window; if it is already open, it is restored and brought to the front.closeEvent: saves the main-window geometry and the sub-window state, then stops the worker threads.moveEvent / resizeEvent: debounced saving of the geometry for restoration on the next start.
Usage
from quatm.GUI.browser.main_window import Browser
browser = Browser()
browser.show()
For a fuller description see Experiment browser.
10.6 - Experiment Queue Manager
Tabular experiment task queue: task dictionary, priority sorting, pause/termination and reserved execution.
Overview
quatm/GUI/experiment/experiment_queue.py provides the experiment queue manager (ExperimentQueue
): it shows the task queue as a table (the task dictionary expDict, keyed by task number) and
supports priority sorting, state tracking and controls such as pause/terminate/delete. submit() is
the only enqueueing entry point on the GUI thread (task numbers are allocated centrally, and the Looper
also goes through it via the submitItem signal).
Key methods
| Method | Description |
|---|
submit | The single enqueueing entry point: allocates task numbers centrally |
pause | Pauses/resumes the currently running experiment |
set_sleeping | Sets a task to Sleeping / Queued (reserved execution) |
terminate_clicked / terminate_all | Gracefully terminates the selected / all tasks (Termination Pending) |
delete_item / delete_clicked | Deletes a task (graceful termination if it is running) |
update_item | Refreshes the row of a task |
set_model | Replaces the table model (ScheduleModel) |
Task-state enumeration: Queued / Waiting / Sleeping / Running / Paused / Scanning /
Terminating / Termination Pending / Done / Failed.
Usage
from quatm.GUI.experiment.experiment_queue import ExperimentQueue
q = ExperimentQueue(browser=parent) # embedded in the experiment browser
q.submit(task_dict) # enqueue (called on the GUI thread)
For a fuller description see Experiment queue.
10.7 - Worker Task Manager
QRunnable multithreaded tasks: callback execution in Worker + WorkerSignals signalling back to the main thread.
Overview
quatm/GUI/widgets/browser_workers.py provides the worker task manager: Worker subclasses
QRunnable and executes a callback in the QThreadPool thread pool, then sends
finished / error / result / progress signals back to the main thread via WorkerSignals. It is used by
the experiment browser (experiment queue, looper, etc.); the isomorphic helper_workers is used by
alignment/auxiliary tools.
Components
| Component | Description |
|---|
Worker | QRunnable worker thread: setup, execution and teardown (including exception handling) |
WorkerSignals | Worker-thread signals: finished / error / result / progress, etc. |
Usage
from quatm.GUI.widgets.browser_workers import Worker
worker = Worker(fn=long_running, args=(...), kwargs={...})
worker.signals.result.connect(on_result)
QThreadPool.globalInstance().start(worker)
For a fuller description see Background worker threads.
10.8 - Auto-save Editor
Editor for the list of periodic HDF5 auto-save points in time: checkbox + date/time save instants.
Overview
quatm/GUI/editors/auto_save_editor.py provides the auto-save editor (AutoSaveEditor): a popup
dialog for editing the “list of points in time” of the periodic HDF5 auto-save, where every row is one
(tick mark + date/time) pair and a ticked row triggers one auto-save at the corresponding instant. The
configuration is stored in a property (such as regular_auto_save_times).
Components
| Component | Description |
|---|
AutoSaveEditor | Auto-save point-in-time editor (BWidget popup) |
DateTimeWidget | Single-row date/time + tick widget |
Key methods
| Method | Description |
|---|
load_AutoSaveEditor_props | Reads the date/times and tick states from the properties (static) |
update_props | Writes the date/times and tick states back to the properties (static) |
update_datetime / update_ticked | Updates when the date/time / tick state of a row changes |
accept | Saves the changes and closes the window |
Usage
from quatm.GUI.editors.auto_save_editor import AutoSaveEditor
editor = AutoSaveEditor(props=props, max_items=20, parent=parent)
editor.show()
For a fuller description see Auto-save.
10.9 - Timeline Viewer
FPGA experiment timing visualisation: a step-plot viewer of the DAC/TTL output channels over time.
Overview
quatm/GUI/entries/experiment_timeline.py provides the timeline viewer (Timeline):
it loads an experiment-timing JSON file (experiment.json), plots the variation over time of each
DAC/TTL output channel as a step subplot, and the output-channel check panel determines which curves
are visible; it is used to debug and verify the experiment timing.
Components
| Component | Description |
|---|
Timeline | Timing-viewer main window: file loading, plotting and channel control |
TimelinePlotter | Timing-visualisation widget: draws the state of every output in use over time |
SelectorWidget | Output-channel check panel: select all / select none |
Key methods (Timeline)
| Method | Description |
|---|
load_file | Loads and parses the timing JSON, builds the time series and refreshes the channel selector |
plot_experimental_sequence | Creates an individual subplot per output according to the channel selection and draws a step plot |
decode_ttl_bitmask | Splits a 32-bit TTL bitmask into set/clear commands for the individual channels |
construct_time_sequences | Converts the step-wise timing into times/vals sequences organised per channel |
reverse_device_db_transformation | Reverse device-database transformation: maps internal names back to readable names |
Usage
from quatm.GUI.entries.experiment_timeline import Timeline
tl = Timeline()
tl.load_file("experiment.json") # load the timing file and plot it
tl.show()
For a fuller description see Timing browser.
10.10 - Multithreaded Worker Framework
QRunnable multithreaded worker framework: callback execution in Worker + WorkerSignals signalling back to the main thread.
Overview
quatm/GUI/widgets/helper_workers.py (and the isomorphic browser_workers.py) provides the
multithreaded worker framework: Worker subclasses QRunnable and puts an arbitrary callback
function into the QThreadPool thread pool for execution, and returns the result to the GUI main
thread through WorkerSignals using finished / error / result / progress signals.
browser_workers: used by the experiment browser (experiment queue, looper, etc.);helper_workers: used by alignment/auxiliary and similar tools.
Components
| Component | Description |
|---|
Worker | QRunnable worker thread: setup, execution and teardown (including exception handling) |
WorkerSignals | Signals a worker thread can emit: finished / error / result / progress, etc. |
Usage
from quatm.GUI.widgets.helper_workers import Worker
worker = Worker(fn=long_running, args=(...), kwargs={...})
worker.signals.result.connect(on_result) # main-thread slot
worker.signals.error.connect(on_error)
QThreadPool.globalInstance().start(worker)
For a fuller description see Background worker threads.
10.11 - Loop Manager
Conditional loop executor: automatically repeat/jump/terminate a group of tasks based on measurement results.
Overview
quatm/GUI/looper/ provides the loop manager (Looper): task entries are organised as “loop
groups (tabs)”, the entries of a group are executed in order, and the jumps or termination are decided
from measurement results. LoopManager runs the loop in a background thread; the loop definition is
persisted in loopfile.json and shared with the web remote-control platform.
Core logic
LoopManager.run_loop(): start from the first group, loop indefinitely until terminate
└─→ execute the entries of the group in order
├─ Task: submit to the experiment queue, advance once complete (maximum count configurable)
└─ Conditional: read data-stream fields and compare → jump if satisfied / else branch otherwise
Components
| Component | Description |
|---|
LoopGroup | Loop group (one tab) |
TaskItem / ListItem | Task entry / task-list entry |
ConditionalItem etc. | Condition entries such as data conditions, arrival index and time |
Baustelle | Panel for adding/removing/editing the entries of a group |
DataManager | Subscribes to data streams and accumulates the latest measurement values |
LoopManager | Loop executor (run_loop() / terminate(), background thread) |
Usage
from quatm.GUI.looper import Looper
looper = Looper(parent=browser) # looper tab of the dock at the top of the browser
For a fuller description see Looper.
10.12 - Preparation Queue Manager
Table models for prepared tasks (preparation station): the list model PrepModel and the queue ScheduleModel.
Overview
quatm/GUI/widgets/models.py provides base classes that map Python dictionaries / lists onto Qt table
models (derived from the ARTIQ models). The preparation queue (preparation station) uses the
list-type PrepModel: prepared tasks need no unique ID and their order can be changed freely; the
experiment queue uses the dictionary-type ScheduleModel (sorted by priority, then by task
number).
Core classes
| Class | Description |
|---|
_SyncSubstruct | Helper wrapping the dictionary operations of the underlying container (append/insert/pop callbacks refresh) |
DictSyncModel | Dictionary → table model base class |
ListSyncModel | List → table model base class |
ScheduleModel | Experiment queue (ExperimentQueue) model: tasks sorted by priority/task number |
PrepModel | Preparation queue (PrepStation) model: list type, order adjustable |
Concrete business models implement three hooks in a subclass: convert (key/value/column to cell
content), sort_key (sorting key) and flags (edit flags).
Usage
from quatm.GUI.widgets.models import PrepModel
model = PrepModel(init=prep_list) # list of prepared tasks
The models are described in Data models; preparation-queue operations are described in
Preparation station.
10.13 - Experiment Run Scheduler
The run scheduling mechanism: central enqueueing in the queue, workers fetching tasks by priority/reservation time.
Overview
Experiment run scheduling is accomplished by the experiment queue and the execution engine working
together: submit() of the ExperimentQueue is the only enqueueing entry point on the GUI thread
(task numbers are allocated centrally, and the preparation station and looper also enqueue through
it); the resident worker polls the task dictionary in ExperimentManager.queue_fn and selects and
executes tasks by priority → task number → reservation time (due_check) (measurement → sequence →
run).
| Component | Module | Description |
|---|
ExperimentQueue | experiment/experiment_queue.py | Task queue: enqueueing, state tracking, pause/termination |
ExperimentManager | experiment/experiment_manager.py | Execution engine: picks tasks, runs measurement/sequence/run |
PrepStation | experiment/prepstation.py | Prepared tasks, pushed into the queue |
Looper | looper/ | Conditional loop, enqueued via submit |
Scheduling flow
PrepStation / Looper / experiment window --submit()--> queue task dictionary
↓
ExperimentManager.queue_fn (worker thread, by priority/task number/reservation time)
↓
start_measurement → start_sequence → several runs → end_sequence → end_measurement
At run time pause / resume is supported (pause) as well as graceful termination
(terminate_experiment, Termination Pending).
Related pages
10.14 - Experiment Preparation
The preparation station: staging, editing, ordering and pushing tasks before they are sent to the queue (prepfile persistence).
Overview
quatm/GUI/experiment/prepstation.py provides experiment preparation (PrepStation): the
preparation station in the dock at the top of the experiment browser, used for staging and editing
tasks before they are sent to the experiment queue. The task list is displayed with a PrepModel
table (its order can be changed freely) and persisted to prepfile.json.
Key methods
| Method | Description |
|---|
push / push_row / push_first | Pushes the selected/designated/first task to the experiment queue |
push_to_looper | Pushes the selected task to the looper |
move_up / move_down | Changes the order of tasks |
delete_clicked / set_sleeping | Deletes / sleeps a task |
open_editor / open_viewer / save_params | Edits parameters, views parameters, saves them as JSON |
update_prep_file | Saves the task list to prepfile.json |
load_previous | Restores the tasks of the previous session from prepfile.json |
Usage
from quatm.GUI.experiment.prepstation import PrepStation
ps = PrepStation(browser=parent)
ps.push_first() # push the first prepared task into the queue
For a fuller description see Preparation station.
10.15 - Process Manager
Distributed process panel: start/stop background processes grouped as Servers/Driver/GUI/Viewer/Monitor.
Overview
quatm/GUI/entries/processmanager.py provides the process manager: it reads the configuration
grouped as Servers / Driver / GUI / Viewer / Monitor and generates a SingleProcess start/stop button
panel for every script entry, managing the whole set of experiment-software processes in one place. A
process is started with the current interpreter, either as a script or as a module (python -m), and
carries the project root and the parent-process watchdog environment.
Components
| Component | Description |
|---|
ProcessManager | Category panel: reads the configuration and generates a start/stop button for every entry |
SingleProcess | Single-entry panel: start / asynchronous termination / status polling colouring |
Key methods (SingleProcess)
| Method | Description |
|---|
startProcess | Stops the old instance, then starts the child process with the current interpreter |
stopProcess | Terminates the child process asynchronously without blocking the GUI thread |
updateStatus | Periodically polls the exit status and colours it (running / exited / crashed) |
Usage
from quatm.GUI.entries.processmanager import ProcessManager
pm = ProcessManager(parent=parent)
pm.show()
For a fuller description see Process manager.
10.16 - Experiment Parameter Manager
The default experiment parameter system: DefaultExp aggregates device parameters, Dashboard edits them grouped by device.
Overview
quatm/GUI/entries/dashboard.py provides the experiment parameter manager: DefaultExp is the
experiment base class that aggregates the attributes of all devices (build() walks the device
database and groups them by type), and the per-type parameter managers (BasicManager /
IntManager / FloatManager / BoolManager / ComboManager / FrequencyManager) generate input
widgets with unit/range from the parameter declarations; the Dashboard panel presents the device
groups as editable default-parameter sections.
Components
| Component | Description |
|---|
DefaultExp | Default experiment: aggregates all device attributes and groups them by device |
Dashboard | Default-parameter panel: generates parameter widgets grouped by device |
BasicManager etc. | Per-type parameter-widget managers (Int/Float/Bool/Combo/Frequency) |
Usage
from quatm.GUI.entries.dashboard import Dashboard
panel = Dashboard(parent=parent) # show the default parameters grouped by device
For a fuller description see Parameter system.
10.17 - Run Monitor
Data-stream monitoring GUI: data/image/command/message multi-tab monitoring and single-stream panels.
Overview
quatm/GUI/streammonitor/ provides the run monitor: MainWindow hosts monitoring tabs for
data streams (Data), image streams (Image), commands (Command), the message log (Message) and history
(History); each monitoring tab consists of a StreamMonitor that subscribes to a single data stream
and displays it in real time.
Components
| Component | Description |
|---|
MainWindow | Monitoring main window: multi-tab container + menu |
StreamMonitor | Single-stream monitoring panel: subscribes to one data stream and displays it in real time |
MessageTableModel | Message-log table model (timestamp/source/level/content, coloured by level) |
ImageTableModel | Image-stream metadata table (fixed time/source columns + dynamic key columns) |
Usage
from quatm.GUI.streammonitor import MainWindow, StreamMonitor
win = MainWindow(parent=parent) # multi-tab monitoring main window
panel = StreamMonitor(name="PMT", stream_type="Data", parent=parent)
For a fuller description see Data stream monitor.
10.18 - Collapsible Sections
Collapsible-section dialog: SectionExpandButton toggling + CollapsibleDialog defining the sections.
Overview
quatm/GUI/widgets/theexpandables.py provides collapsible section components: CollapsibleDialog
(a QDialog subclass) organises several collapsible sections with a QTreeWidget whose header is
hidden; each section header is a SectionExpandButton button, and clicking it expands/collapses the
corresponding content.
Components
| Component | Description |
|---|
SectionExpandButton | Button that expands/collapses a section (QPushButton) |
CollapsibleDialog | Base class for dialogs to which collapsible sections can be added |
Key methods (CollapsibleDialog)
| Method | Description |
|---|
define_sections | Override this method to define all sections as (title, widget) tuples |
add_section | Add one collapsible section |
add_button / add_widget | Create a top-level entry containing a button / attach a content widget |
Usage
from quatm.GUI.widgets.theexpandables import CollapsibleDialog
class MyDialog(CollapsibleDialog):
def define_sections(self):
self.add_section("Parameters", some_widget)
self.add_section("Advanced", advanced_widget)
dlg = MyDialog()
dlg.define_sections()
dlg.show()
For a fuller description see Collapsible components.
11 - Best practices
Patterns for reliable experiment code and safe operation.
Separate configuration from code
Device addresses, calibration files and channel maps belong in configuration/, not in experiment source. A run should be reproducible from the project directory and its data file.
One responsibility per class
Keep experiment logic, device communication and analysis separate. A driver should not know about a specific experiment; an experiment should not open sockets directly. This makes simulation and reuse possible.
Declare parameters, do not hard-code
Use NumberValue, BoolValue and StringCombo for anything an operator may change. The same declarations drive the GUI, the web dashboard and scan validation.
Make cleanup explicit
Acquire devices through the framework, use leases, and close clients in close(). Do not rely on process exit for hardware cleanup. Long-running services should stop timers and worker threads in closeEvent.
Keep timing deterministic
Do not perform file I/O, plotting or network calls inside a timed sequence. Prepare data before the run and hand results to analysis asynchronously.
Automate checks
Run scripts/check.sh before committing. Add regression tests for experiment semantics, not only for helper functions. Use simulation for routine CI and reserve real hardware for verification runs.
Operate safely
Before an unattended campaign, verify:
- simulated run of the exact scan;
- disk headroom and save acknowledgement behaviour;
- device leases and health;
- alerting and remote access;
- a documented stop procedure.
12 - Physics models
Atom database, optical dipole traps, Stark shifts and scattering.
quatm.physics collects reusable models shared by experiments and analysis. They are ordinary Python functions: no hardware access, no GUI dependency, and suitable for notebooks or offline design work.
Included models
- Atom database — species data, transition wavelengths, linewidths and saturation parameters.
- Optical dipole traps — trap depth, frequencies, waists and polarisability.
- Light–matter interaction — scattering rate, saturation and photon recoil.
- Stark shifts — AC and DC shifts for ground and excited states.
- Feshbach resonances — resonance lookup and field-dependent scattering length.
- Statistics — distributions and fitting helpers used by analysis code.
Using the models
from quatm.physics.odt import optical_trap
result = optical_trap(power=1.0, waist=30e-6, wavelength=1064e-9,
species='Rb87')
print(result.depth_uK)
The model layer imports no GUI and no instruments, so it can run on a laptop or inside a web service. Units are explicit in function signatures and docstrings; avoid ambiguous bare numbers in experiment code.
Relationship to analysis
Analysis modules use the same physics layer for conversion between camera counts, optical density and atom number. This keeps calibration and fitting consistent across the GUI, web console and scripts.
12.1 - Atom database
Atom properties for Ba, Rb, Li, Mg and Ca: mass, transition frequencies and Clebsch-Gordan coefficients.
Overview
atomlib provides precomputed physical data for the atomic species commonly used in cold-atom and ion experiments.
Predefined atoms
| Dictionary | Species | Typical use |
|---|
BariumDict | Ba⁺ | Ion-trap quantum computing |
RubidiumDict | ⁸⁷Rb | Bose-Einstein condensates |
LithiumDict | ⁶Li | Ultracold Fermi gases |
MagnesiumDict | Mg⁺ | Ion-trap precision measurement |
CalciumDict | Ca⁺ | Ion-trap optical clocks |
Attribute fields
| Attribute | Description |
|---|
mass | Atomic mass (kg) |
ground_state | Ground-state level identifier |
excited_state | Excited-state level identifier |
transition_frequency | Transition frequency (Hz) |
decay_rate | Spontaneous-emission decay rate (s⁻¹) |
clebsch_gordan | Clebsch-Gordan coupling coefficient |
12.2 - Electric-field calculations
Electric potentials in ion traps: stray fields, curvature and one-dimensional ion-position calculations.
Overview
efields computes the electric potential distribution of ions in a Paul trap. It supports stray-field and curvature parameters and solves for the one-dimensional ion equilibrium positions using the James method.
Classes
| Class | Description |
|---|
efields | Electric-potential calculation, including stray field and curvature |
DCTrap | Manages mean/random stray-field configurations |
from quatm.physics.odt.efields import efields
field = efields(stray_field=(1e-3, 0, 0), curvature=0.01)
potential = field.compute_potential(x_grid)
12.3 - Laser model
Gaussian-beam laser: beam size, intensity, wavefront curvature and scaled electric field.
Overview
The Laser class models a Gaussian-beam laser. It manages the automatic synchronization of wavelength/frequency/wavenumber conversion and computes the beam size, the radius of curvature R and the scaled electric field along the propagation direction.
from quatm.physics.odt.lasers import Laser
laser = Laser(wavelength=1064e-9, power=10.0, waist=50e-6)
intensity = laser.intensity(z=0.0)
w = laser.beam_radius(z=0.01)
12.4 - Light-matter interaction
AC Stark shifts and scattering rates: Clebsch-Gordan-weighted calculations for S→P and D→P transitions.
Overview
LMinteraction computes the interaction between a laser field and an atom: the AC Stark shifts for the ground-to-excited (S→P) and metastable-to-excited (D→P) transitions, and the off-resonant photon scattering rate.
from quatm.physics.odt.lightmatterint import LMinteraction
lm = LMinteraction(atom=Ba, laser=laser)
shift = lm.stark_shift(intensity=1e6) # Hz
rate = lm.scattering_rate(intensity=1e6) # s⁻¹
12.5 - Optical trap model
Composite traps: full 1D/2D/3D potential calculation for dipole beams plus electric fields.
Overview
Trap combines a Gaussian-beam optical dipole trap with electrostatic fields into a composite trap model and computes the trap depth (potential turning points) and the three-dimensional potential distribution.
from quatm.physics.odt.opticaltraputils import Trap
trap = Trap(laser=odt_laser, efield=dc_field)
depth = trap.trap_depth() # µK
potential_2d = trap.potential_2d(x, y, z=0)
12.6 - Statistical tools
Binomial Wilson score intervals: confidence intervals for ion-state discrimination.
Overview
statistics provides binomial proportion confidence intervals based on the Wilson score interval, used for statistical error estimation in ion-state discrimination.
| Function | Description |
|---|
wilson_score(p, n) | Computes the Wilson score for proportion p |
wilson_err(p, n) | Half-width of the Wilson confidence interval |
getrandbincont(p, n, N) | Samples the experiment probability N times from binomial outcomes |
Wigner symbols (utils)
The utils module provides complete Racah-formula implementations of the Wigner3j and Wigner6j symbols, for angular-momentum coupling calculations.
13 - Contributing
Issues, pull requests and review expectations.
Quatm is developed with a small core team and contributions from laboratories. We welcome bug reports, documentation fixes, driver additions and well-scoped improvements.
Before opening an issue
- Search existing issues and pull requests.
- Include the commit or version, operating system, Python version and the affected application.
- For hardware issues, include the driver, instrument model, connection type and relevant logs.
- Do not paste licence keys, tokens or personal data.
Pull requests
- Keep changes focused; do not mix unrelated refactors with a fix.
- Follow the repository conventions in
docs/CONVENTIONS.md and AGENTS.md. - Add or update tests for behaviour changes.
- Run
bash scripts/check.sh and report the exit code. - Update documentation and translations when user-facing strings change.
Review
The review checks correctness, experiment semantics, resource lifecycle, licensing boundaries and test coverage. Changes that affect timing or units require an explicit rationale. Large architectural changes should start with an ADR under docs/adr/.
Security
Do not report security vulnerabilities in public issues. Contact security@quatm.com; see the security policy.
14 - Monitoring
Environmental and instrument-health monitoring.
Long-running experiments need more than the main data stream. Quatm monitors the laboratory infrastructure and the health of its own services.
Environmental monitors
Bundled monitors cover:
- temperature controllers and sensors;
- magnetometers / Hall probes;
- wavemeters and laser power meters;
- vacuum gauges;
- PC health (CPU, memory, disk);
- oscilloscopes and general SCPI instruments.
Monitors are ordinary services that publish into the property and data streams. A failed monitor is reported and can be restarted without touching the experiment runtime.
Device health
The Device Manager can probe instruments, run driver self-tests and show the current lease owner. Health state and history are kept separately from experiment data, so a failing instrument does not corrupt a run’s HDF5 file.
Service health
The Operations Console and Task Monitor show:
- service process state and uptime;
- task queue state and heartbeats;
- configured endpoints and reachability;
- data-manager save errors and disk headroom;
- resource alerts (disk, database size, threads, file descriptors).
Alerts are rate-limited and transition-based: an ongoing error is reported once, and recovery is reported separately.
14.1 - LFI3751 temperature controller
Wavelength Electronics LFI3751 temperature controller driver: PyVISA communication.
Overview
LFI3751 drives the Wavelength Electronics high-precision semiconductor laser temperature controller over PyVISA.
from quatm.monitor.LFI3751 import LFI3751
tc = LFI3751(addr="USB0::...")
temp = tc.get_temperature()
tc.set_setpoint(25.0)
14.2 - Gaussmeter
PCE-MFM 3000 gaussmeter driver: serial communication, real-time magnetic-field measurement.
Overview
PCE_MFM_3000 drives the PCE Instruments magnetic flux density meter and reads the magnetic induction in real time over a serial port.
from quatm.monitor.PCE_MFM_3000 import PCE_MFM_3000
gauss = PCE_MFM_3000(port="/dev/ttyUSB0")
field = gauss.get_field() # Returns the value in mT
14.3 - Cavity environmental sensors
Arduino temperature, humidity and pressure sensors: real-time monitoring of the cavity environment.
Overview
The arduino driver reads temperature, humidity and pressure sensor data from inside the cavity through an Arduino Nano.
from quatm.monitor.cavity_arduino import arduino
env = arduino(port="/dev/ttyACM0")
temp, hum, press = env.read_all()
14.4 - PC health monitoring
Real-time CPU/memory/disk/network monitoring based on psutil.
Overview
PC_health uses psutil to monitor the CPU utilization, memory usage, disk space and network traffic of the experiment control computer, preventing experiment interruptions caused by resource exhaustion.
from quatm.monitor.pc_health import PC_health
health = PC_health()
cpu = health.get_cpu_percent()
mem = health.get_memory_usage()
disk = health.get_disk_free()
14.5 - Oscilloscope
Rigol DS1054Z oscilloscope driver: PyVISA waveform acquisition and voltage measurement.
Overview
The ds1054z driver controls the Rigol DS1054Z four-channel digital oscilloscope and performs waveform acquisition and automatic measurements over PyVISA.
from quatm.monitor.ds1054z import ds1054z
scope = ds1054z(addr="USB0::...")
waveform = scope.get_waveform(channel=1)
vpp = scope.measure_vpp(channel=1)
15 - Utilities
Quatm general-purpose utility functions: variable density sampling and other helpers
Overview
The quatm.utils package provides general-purpose utility functions used across the modules of the Quatm framework.
Variable density sampling — quatm.utils.vds
Variable density sampling (VDS) allows efficient exploration of a parameter space by concentrating the
sampling points in the regions of interest (such as near resonances or phase transitions in quantum systems).
Core idea
In a physics experiment, some parameter regions are more meaningful than others. For example:
- magnetic-field scans near a Feshbach resonance
- frequency scans near the centre of a spectral line
- parameter exploration near a phase-transition critical point
By automatically densifying the sampling points in the regions of interest and sampling sparsely in flat
regions, VDS extracts the maximum information with the minimum number of data points.
gauss_prime_norm — normalised Gaussian derivative
The derivative of a Gaussian function, normalised to a maximum of 1:
$$f(x) = \sqrt{e} \cdot \frac{x - \mu}{\sigma} \exp\left(-\frac{(x-\mu)^2}{2\sigma^2}\right)$$
from quatm.utils.vds import gauss_prime_norm
# evaluate the Gaussian derivative at x=5.0
val = gauss_prime_norm(5.0, mu=5.0, sigma=1.0)
| Parameter | Description |
|---|
x | input value, either a scalar or a numpy.ndarray |
mu | centre of the Gaussian |
sigma | standard deviation of the Gaussian |
gauss_prime_norm_abs — absolute-value normalised Gaussian derivative
The normalised form of the absolute value of the Gaussian derivative, suitable as an interest function for
symmetric peak detection.
create_support_points_stochastic — stochastic support-point generation
Starts from a fine equidistant grid and randomly removes the points in regions of low interest, keeping the
points in regions of high interest.
from quatm.utils.vds import (
create_support_points_stochastic,
gauss_prime_norm_abs
)
# define the interest function: concentrate the sampling around x=5.0
def interest(x, center, width):
return gauss_prime_norm_abs(x, center, width)
points = create_support_points_stochastic(
start=0.0, # scan start
stop=10.0, # scan stop
steps=50, # number of final support points
interest_fn=interest,
max_stepsize=0.5, # maximum permitted step size
enhancement=0.8, # 0-1, 1 means strong enhancement
center=5.0, # passed to interest_fn
width=1.0 # passed to interest_fn
)
| Parameter | Description |
|---|
start | scan start, always included in the final support points |
stop | scan stop, always included in the final support points |
steps | number of final support points |
interest_fn | interest function f(x, **kwargs) returning 0 (low interest) to 1 (high interest) |
max_stepsize | maximum spacing between adjacent support points |
enhancement | 0-1, 1 means strong enhancement of the regions of interest, 0 means uniform random sampling |
**kwargs | additional arguments passed to interest_fn |
Application scenarios
Variable density sampling is particularly suitable for:
- Feshbach resonance spectrum scans: densify the sampling near the resonance peak
- Spectral line scans: improve the resolution near the line centre
- Phase-transition parameter scans: explore the critical region finely
- Trap-frequency measurements: concentrate the sampling near the expected resonance frequency
15.1 - Variable density sampling
Variable density sampling: a support-point generator that densifies the sampling in parameter space according to an interest function.
Overview
quatm/utils/vds.py provides variable density sampling (VDS) tools: it generates support points
(sample points) that are randomly distributed and densified in the regions of interest according to a
given interest function, so as to optimise the limited experiment time.
Functions
| Function | Description |
|---|
create_support_points_stochastic(start, stop, steps, interest_fn, ...) | Generates randomly distributed support points densified in the ROI |
gauss_prime_norm(x, mu, sigma) | Normalised form of the derivative of a Gaussian function (peak normalised to 1) |
gauss_prime_norm_abs(x, mu, sigma) | Normalised form of the absolute value of the Gaussian derivative |
Usage
from quatm.utils.vds import create_support_points_stochastic
# interest_fn returns a weight (0-1) that determines the sampling density at each position
points = create_support_points_stochastic(
start=0, stop=100, steps=200,
interest_fn=lambda x: gauss_prime_norm_abs(x, mu=50, sigma=10),
)
16 - Web remote control
The browser console, mobile operation and synchronisation with the desktop.
The web console is installed with the core package (quatm.web) and served by the Web Hub. It is not a reduced viewer: it shares the same experiment queue, scans, preparation file, looper and property tree as the desktop GUI.
Capabilities
- Monitoring — live curves, message log and camera images.
- Experiments — experiment browser, experiment windows and a queue synchronised with the desktop.
- Dashboard — instrument parameters grouped by device, with units, limits and steps.
- Data — per-run live tracking, HDF5 archive browsing, fits and CSV export.
- Properties — browse and edit the shared property tree.
- Analysis — start and stop analysis jobs shared with the desktop.
- Timeline — FPGA sequence visualisation and device mapping.
- Notifications — push notification configuration.
- Processes — view and control services, drivers and GUI processes on the experiment PC.
Remote access
The Web Hub binds to a configurable endpoint. For remote access use an SSH tunnel or a reverse proxy with authentication; never expose an unauthenticated control endpoint to an untrusted network. See the Web console README for endpoint, token and firewall guidance.
Mobile operation
The layout adapts to phones and tablets: horizontally scrolling navigation, single-column stacking, larger touch targets and charts sized for small screens. The same permissions and data model apply as on the desktop.
Synchronisation
Because both clients use the same Hubs and message protocol, an action on one client is immediately visible on the other. Queue entries, scan definitions, preparation state, loop logic and property values cannot diverge between the desktop and the browser.