Skip to content

API#

In addition to declaring a schema (which is intended to be language agnostic), useq-schema offers a python API for working with MDASequence and MDAEvent objects.

MDASequence #

A sequence of MDA (Multi-Dimensional Acquisition) events.

This is the core object in the useq library, and is used define a sequence of events to be run on a microscope. It object may be constructed manually, or from file (e.g. json or yaml).

The object itself acts as an iterator for useq.MDAEvent objects:

Attributes:

Name Type Description
metadata dict

A dictionary of user metadata to be stored with the sequence.

axis_order str

The order of the axes in the sequence. Must be a permutation of "tpgcz". The default is "tpgcz".

stage_positions tuple[Position, ...]

The stage positions to visit. (each with x, y, z, name, and sequence, all of which are optional).

grid_plan GridFromEdges | GridRelative | None

The grid plan to follow. One of GridFromEdges, GridRelative or None.

channels tuple[Channel, ...]

The channels to acquire. see Channel.

time_plan MultiPhaseTimePlan | TIntervalDuration | TIntervalLoops | TDurationLoops | None

The time plan to follow. One of TIntervalDuration, TIntervalLoops, TDurationLoops, MultiPhaseTimePlan, or None

z_plan ZTopBottom | ZRangeAround | ZAboveBelow | ZRelativePositions | ZAbsolutePositions | None

The z plan to follow. One of ZTopBottom, ZRangeAround, ZAboveBelow, ZRelativePositions, ZAbsolutePositions, or None.

uid UUID

A read-only unique identifier (uuid version 4) for the sequence. This will be generated, do not set.

autofocus_plan AxesBasedAF | None

The hardware autofocus plan to follow. One of AxesBasedAF or None.

keep_shutter_open_across tuple[str, ...]

A tuple of axes str across which the illumination shutter should be kept open. Resulting events will have keep_shutter_open set to True if and only if ALL axes whose indices are changing are in this tuple. For example, if keep_shutter_open_across=('z',), then the shutter would be kept open between events axes {'t': 0, 'z: 0} and {'t': 0, 'z': 1}, but not between {'t': 0, 'z': 0} and {'t': 1, 'z': 0}.

Examples:

Create a MDASequence

>>> from useq import MDASequence, Position, Channel, TIntervalDuration
>>> seq = MDASequence(
...     axis_order="tpgcz",
...     time_plan={"interval": 0.1, "loops": 2},
...     stage_positions=[(1, 1, 1)],
...     grid_plan={"rows": 2, "columns": 2},
...     z_plan={"range": 3, "step": 1},
...     channels=[{"config": "DAPI", "exposure": 1}]
... )

Print the sequence to visualize its structure

>>> print(seq)
... MDASequence(
...     stage_positions=(Position(x=1.0, y=1.0, z=1.0, name=None),),
...     grid_plan=GridRowsColumns(
...         fov_width=None,
...         fov_height=None,
...         overlap=(0.0, 0.0),
...         mode=<OrderMode.row_wise_snake: 'row_wise_snake'>,
...         rows=2,
...         columns=2,
...         relative_to=<RelativeTo.center: 'center'>
...     ),
...     channels=(
...         Channel(
...             config='DAPI',
...             group='Channel',
...             exposure=1.0,
...             do_stack=True,
...             z_offset=0.0,
...             acquire_every=1,
...             camera=None
...         ),
...     ),
...     time_plan=TIntervalLoops(
...         prioritize_duration=False,
...         interval=datetime.timedelta(microseconds=100000),
...         loops=2
...     ),
...     z_plan=ZRangeAround(go_up=True, range=3.0, step=1.0)
... )

Iterate over the events in the sequence

>>> print(list(seq))
... [
...     MDAEvent(
...         index=mappingproxy({'t': 0, 'p': 0, 'g': 0, 'c': 0, 'z': 0}),
...         channel=Channel(config='DAPI'),
...         exposure=1.0,
...         min_start_time=0.0,
...         x_pos=0.5,
...         y_pos=1.5,
...         z_pos=-0.5
...     ),
...     MDAEvent(
...         index=mappingproxy({'t': 0, 'p': 0, 'g': 0, 'c': 0, 'z': 1}),
...         channel=Channel(config='DAPI'),
...         exposure=1.0,
...         min_start_time=0.0,
...         x_pos=0.5,
...         y_pos=1.5,
...         z_pos=0.5
...     ),
...     ...
... ]

Print the sequence as yaml

>>> print(seq.yaml())
axis_order:
   - t
   - p
   - g
   - c
   - z
channels:
   - config: DAPI
     exposure: 1.0
grid_plan:
   columns: 2
   rows: 2
stage_positions:
   - x: 1.0
     y: 1.0
     z: 1.0
time_plan:
   interval: '0:00:00.100000'
   loops: 2
z_plan:
   range: 3.0
   step: 1.0

shape: Tuple[int, ...] property #

Return the shape of this sequence.

Note

This doesn't account for jagged arrays, like channels that exclude z stacks or skip timepoints.

sizes: Mapping[str, int] property #

Mapping of axis name to size of that axis.

uid: UUID property #

A unique identifier for this sequence.

used_axes: str property #

Single letter string of axes used in this sequence, e.g. ztc.

__eq__(other: Any) -> bool #

Return True if two MDASequences are equal (uid is excluded).

__iter__() -> Iterator[MDAEvent] #

Same as iter_events. Supports for event in sequence: ... syntax.

estimate_duration() -> TimeEstimate #

Estimate duration and other timing issues of an MDASequence.

Notable mis-estimations may include: - when the time interval is shorter than the time it takes to acquire the data and any of the channels have acquire_every > 1 - when channel exposure times are omitted. In this case, we assume 1ms exposure.

Returns:

Type Description
TimeEstimate

A named 3-tuple with the following fields: - total_duration: float Estimated total duration of the experiment, in seconds. - per_t_duration: float Estimated duration of a single timepoint, in seconds. - time_interval_exceeded: bool Whether the time interval between timepoints is shorter than the time it takes to acquire the data

iter_axis(axis: str) -> Iterator[Channel | float | PositionBase] #

Iterate over the positions or items of a given axis.

iter_events() -> Iterator[MDAEvent] #

Iterate over all events in the MDA sequence.

See source of useq._mda_sequence.iter_sequence for details on how events are constructed and yielded.

Yields:

Type Description
MDAEvent

Each event in the MDA sequence.

iter_sequence(sequence) #

Iterate over all events in the MDA sequence.'.

Note

This method will usually be used via useq.MDASequence.iter_events, or by simply iterating over the sequence.

This does the job of iterating over all the frames in the MDA sequence, handling the logic of merging all z plans in channels and stage positions defined in the plans for each axis.

The is the most "logic heavy" part of useq-schema (the rest of which is almost entirely declarative). This iterator is useful for consuming MDASequence objects in a python runtime, but it isn't considered a "core" part of the schema.

Parameters:

Name Type Description Default
sequence MDASequence

The sequence to iterate over.

required

Yields:

Type Description
MDAEvent

Each event in the MDA sequence.

Source code in src/useq/_iter_sequence.py
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def iter_sequence(sequence: MDASequence) -> Iterator[MDAEvent]:
    """Iterate over all events in the MDA sequence.'.

    !!! note
        This method will usually be used via [`useq.MDASequence.iter_events`][], or by
        simply iterating over the sequence.

    This does the job of iterating over all the frames in the MDA sequence,
    handling the logic of merging all z plans in channels and stage positions
    defined in the plans for each axis.

    The is the most "logic heavy" part of `useq-schema` (the rest of which is
    almost entirely declarative).  This iterator is useful for consuming `MDASequence`
    objects in a python runtime, but it isn't considered a "core" part of the schema.

    Parameters
    ----------
    sequence : MDASequence
        The sequence to iterate over.

    Yields
    ------
    MDAEvent
        Each event in the MDA sequence.
    """
    if not (keep_shutter_open_axes := sequence.keep_shutter_open_across):
        yield from _iter_sequence(sequence)
        return

    it = _iter_sequence(sequence)
    if (this_e := next(it, None)) is None:  # pragma: no cover
        return

    for next_e in it:
        # set `keep_shutter_open` to `True` if and only if ALL axes whose index
        # changes betwee this_event and next_event are in `keep_shutter_open_axes`
        if all(
            axis in keep_shutter_open_axes
            for axis, idx in this_e.index.items()
            if idx != next_e.index[axis]
        ):
            this_e = this_e.model_copy(update={"keep_shutter_open": True})
        yield this_e
        this_e = next_e
    yield this_e