2.9. Scripting PyMoDAQ
PyMoDAQ can be controlled from a plain Python script.
The pymodaq.scripting module provides a small, synchronous and asynchronous API
that lets you move actuators, acquire data, and manage experiments or configurations programmatically.
Communication between the script and the running PyMoDAQ application uses LECO. Before running any script you must therefore have:
A LECO Coordinator running (
coordinatorin a terminal, or PyMoDAQ setting to start it automatically enabled).The PyMoDAQ components you want to control already connected to LECO (see LECO communication).
Important
New PyMoDAQ versions containing scripting modules automatically starts a coordinator and connects devices.
2.9.1. Public Classes
Three classes are available directly from pymodaq.scripting:
Class |
Purpose |
|---|---|
|
Connect to a running Dashboard, apply experiments/configurations, retrieve the list of loaded modules. |
|
Move an actuator (absolute, relative, home), read its current position, stop a move. |
|
Acquire a single frame (snap) or a continuous stream (grab), stop acquisition. |
Every method that communicates over the network returns a
Future. Calling .result() on it blocks
until the remote component replies and the result in set in the corresponding Future.
future = actuator.move_abs('90°') # non-blocking — returns immediately
position = future.result() # blocks until the move is finished
2.9.2. Connecting to a Dashboard
A good starting point is a Dashboard.
It lets you load an experiment (which starts the instrument modules), then retrieve
the corresponding Actuator and
Detector objects with a single call.
from pymodaq.scripting import Dashboard
dashboard = Dashboard()
# list available experiments and apply one
print(dashboard.get_experiments().result())
# >>> ['default']
dashboard.apply_experiment('default').result()
# list available configurations
print(dashboard.get_configurations().result())
# >>> ['default', 'my_custom_config']
dashboard.apply_configuration('default').result()
# retrieve all loaded modules by name
print(dashboard.get_devices().result())
# >>> {'actuators': ['Theta', 'Temperature'], 'detectors': ['Camera', 'Det0D']}
# get ready-to-use Actuator / Detector objects
devices = dashboard.get_scripting_devices()
theta = devices['actuators']['Theta']
camera = devices['detectors']['Camera']
2.9.3. Controlling Actuators
from pymodaq.scripting import Actuator
from pymodaq_data import Q_
theta = Actuator('Theta')
# read current position
pos = theta.get_actuator_value().result()
# absolute move (accepts a DataActuator, a Quantity, a plain number, or a string)
new_pos = theta.move_abs(Q_('90°')).result()
# relative move
theta.move_rel(Q_('10°')).result()
# go to home position
theta.move_home().result()
# stop a running move
theta.stop_move()
2.9.4. Acquiring Data with Detectors
from pymodaq.scripting import Detector
camera = Detector('Camera')
# single acquisition (blocking)
data = camera.snap().result() # returns a DataToExport
# continuous acquisition — collect N frames
frames = []
for _ in range(10):
camera.grab() # start / continue grabbing
frames.append(camera.grab(keep=True)) # keep=True returns accumulated data
camera.stop_grab()
2.9.5. A Complete Scan Script
The following script sweeps an actuator over a range, snaps two detectors at each step, and saves the result to an HDF5 file.
from pathlib import Path
from pymodaq_data import Q_
from pymodaq_data.h5modules.backends import GroupType
from pymodaq_data.h5modules.data_saving import DataToExportEnlargeableSaver
from pymodaq.scripting import Actuator, Detector
theta = Actuator('Angle')
det0d = Detector('Det0D')
det1d = Detector('Det1D')
base = theta.get_actuator_value().result()
target = base + Q_('90°')
step = Q_('10°')
count = int(((target - base) / step).quantities[0].m[0]) + 1
filename = Path(r'C:\Data\2026\scripted_data.h5')
with DataToExportEnlargeableSaver(
filename,
enl_axis_names=('count',),
enl_axis_units=('',),
) as saver:
group = saver.add_group('Script', group_type=GroupType.data,
where='/RawData/', title='scripted_data')
group0d = saver.add_det_group(where=group, title=det0d.name)
group1d = saver.add_det_group(where=group, title=det1d.name)
for idx in range(count):
value = theta.move_abs(base + idx * step).result()
dte0d_future = det0d.snap() # fire both snaps concurrently …
dte1d_future = det1d.snap()
saver.add_data(group0d, dte0d_future.result(), (value.value(),))
saver.add_data(group1d, dte1d_future.result(), (value.value(),))
See also
LECO communication — how to start the Coordinator and connect PyMoDAQ modules as LECO Actors.
Making a PyMoDAQ Component Available Through LECO — developer guide for exposing a new component over LECO and creating the corresponding scripting wrapper.