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:

  1. A LECO Coordinator running (coordinator in a terminal, or PyMoDAQ setting to start it automatically enabled).

  2. 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

Dashboard

Connect to a running Dashboard, apply experiments/configurations, retrieve the list of loaded modules.

Actuator

Move an actuator (absolute, relative, home), read its current position, stop a move.

Detector

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.