pymmcore-plus 0.9.3__py3-none-any.whl → 0.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pymmcore_plus/__init__.py +7 -4
- pymmcore_plus/_benchmark.py +203 -0
- pymmcore_plus/_build.py +6 -1
- pymmcore_plus/_cli.py +131 -31
- pymmcore_plus/_logger.py +19 -10
- pymmcore_plus/_pymmcore.py +12 -0
- pymmcore_plus/_util.py +139 -32
- pymmcore_plus/core/__init__.py +5 -0
- pymmcore_plus/core/_config.py +6 -4
- pymmcore_plus/core/_config_group.py +4 -3
- pymmcore_plus/core/_constants.py +135 -10
- pymmcore_plus/core/_device.py +4 -4
- pymmcore_plus/core/_metadata.py +3 -3
- pymmcore_plus/core/_mmcore_plus.py +254 -170
- pymmcore_plus/core/_property.py +6 -6
- pymmcore_plus/core/_sequencing.py +370 -233
- pymmcore_plus/core/events/__init__.py +6 -6
- pymmcore_plus/core/events/_device_signal_view.py +8 -6
- pymmcore_plus/core/events/_norm_slot.py +2 -4
- pymmcore_plus/core/events/_prop_event_mixin.py +7 -4
- pymmcore_plus/core/events/_protocol.py +5 -2
- pymmcore_plus/core/events/_psygnal.py +2 -2
- pymmcore_plus/experimental/__init__.py +0 -0
- pymmcore_plus/experimental/unicore/__init__.py +14 -0
- pymmcore_plus/experimental/unicore/_device_manager.py +173 -0
- pymmcore_plus/experimental/unicore/_proxy.py +127 -0
- pymmcore_plus/experimental/unicore/_unicore.py +703 -0
- pymmcore_plus/experimental/unicore/devices/__init__.py +0 -0
- pymmcore_plus/experimental/unicore/devices/_device.py +269 -0
- pymmcore_plus/experimental/unicore/devices/_properties.py +400 -0
- pymmcore_plus/experimental/unicore/devices/_stage.py +221 -0
- pymmcore_plus/install.py +16 -11
- pymmcore_plus/mda/__init__.py +1 -1
- pymmcore_plus/mda/_engine.py +320 -148
- pymmcore_plus/mda/_protocol.py +6 -4
- pymmcore_plus/mda/_runner.py +62 -51
- pymmcore_plus/mda/_thread_relay.py +5 -3
- pymmcore_plus/mda/events/__init__.py +2 -2
- pymmcore_plus/mda/events/_protocol.py +10 -2
- pymmcore_plus/mda/events/_psygnal.py +2 -2
- pymmcore_plus/mda/handlers/_5d_writer_base.py +106 -15
- pymmcore_plus/mda/handlers/__init__.py +7 -1
- pymmcore_plus/mda/handlers/_img_sequence_writer.py +11 -6
- pymmcore_plus/mda/handlers/_ome_tiff_writer.py +8 -4
- pymmcore_plus/mda/handlers/_ome_zarr_writer.py +82 -9
- pymmcore_plus/mda/handlers/_tensorstore_handler.py +374 -0
- pymmcore_plus/mda/handlers/_util.py +1 -1
- pymmcore_plus/metadata/__init__.py +36 -0
- pymmcore_plus/metadata/functions.py +353 -0
- pymmcore_plus/metadata/schema.py +472 -0
- pymmcore_plus/metadata/serialize.py +120 -0
- pymmcore_plus/mocks.py +51 -0
- pymmcore_plus/model/_config_file.py +5 -6
- pymmcore_plus/model/_config_group.py +29 -2
- pymmcore_plus/model/_core_device.py +12 -1
- pymmcore_plus/model/_core_link.py +2 -1
- pymmcore_plus/model/_device.py +39 -8
- pymmcore_plus/model/_microscope.py +39 -3
- pymmcore_plus/model/_pixel_size_config.py +27 -4
- pymmcore_plus/model/_property.py +13 -3
- pymmcore_plus/seq_tester.py +1 -1
- {pymmcore_plus-0.9.3.dist-info → pymmcore_plus-0.13.0.dist-info}/METADATA +22 -12
- pymmcore_plus-0.13.0.dist-info/RECORD +71 -0
- {pymmcore_plus-0.9.3.dist-info → pymmcore_plus-0.13.0.dist-info}/WHEEL +1 -1
- pymmcore_plus/core/_state.py +0 -244
- pymmcore_plus-0.9.3.dist-info/RECORD +0 -55
- {pymmcore_plus-0.9.3.dist-info → pymmcore_plus-0.13.0.dist-info}/entry_points.txt +0 -0
- {pymmcore_plus-0.9.3.dist-info → pymmcore_plus-0.13.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
from typing import Any, Literal, Optional, TypedDict, Union
|
|
2
|
+
|
|
3
|
+
import useq
|
|
4
|
+
from typing_extensions import NotRequired
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"ConfigGroup",
|
|
8
|
+
"ConfigPreset",
|
|
9
|
+
"DeviceInfo",
|
|
10
|
+
"FrameMetaV1",
|
|
11
|
+
"ImageInfo",
|
|
12
|
+
"PixelSizeConfigPreset",
|
|
13
|
+
"Position",
|
|
14
|
+
"PropertyInfo",
|
|
15
|
+
"PropertyValue",
|
|
16
|
+
"SummaryMetaV1",
|
|
17
|
+
"SystemInfo",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
AffineTuple = tuple[float, float, float, float, float, float]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PropertyInfo(TypedDict):
|
|
24
|
+
"""Information about a single device property.
|
|
25
|
+
|
|
26
|
+
Attributes
|
|
27
|
+
----------
|
|
28
|
+
name : str
|
|
29
|
+
The name of the property.
|
|
30
|
+
value : str | None
|
|
31
|
+
The current value of the property, if any.
|
|
32
|
+
data_type : Literal["undefined", "float", "int", "str"]
|
|
33
|
+
The data type of the `value` field.
|
|
34
|
+
is_read_only : bool
|
|
35
|
+
Whether the property is read-only.
|
|
36
|
+
allowed_values : tuple[str, ...]
|
|
37
|
+
*Not Required*. The allowed values for the property, if any. Consumers should
|
|
38
|
+
not depend on this field being present.
|
|
39
|
+
is_pre_init : bool
|
|
40
|
+
*Not Required*. Whether the property is pre-init. If missing, assume `False`.
|
|
41
|
+
limits : tuple[float, float]
|
|
42
|
+
*Not Required*. The limits of the property, if any. If missing, the property
|
|
43
|
+
has no limits.
|
|
44
|
+
sequenceable : bool
|
|
45
|
+
*Not Required*. Whether the property is sequenceable. If missing, assume
|
|
46
|
+
`False`.
|
|
47
|
+
sequence_max_length : int
|
|
48
|
+
*Not Required*. The maximum length of a sequence for the property,
|
|
49
|
+
if applicable. Will be missing if the property is not sequenceable.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
name: str
|
|
53
|
+
value: Optional[str]
|
|
54
|
+
data_type: Literal["undefined", "float", "int", "str"]
|
|
55
|
+
is_read_only: bool
|
|
56
|
+
allowed_values: NotRequired[tuple[str, ...]]
|
|
57
|
+
is_pre_init: NotRequired[bool]
|
|
58
|
+
limits: NotRequired[tuple[float, float]]
|
|
59
|
+
sequenceable: NotRequired[bool]
|
|
60
|
+
sequence_max_length: NotRequired[int]
|
|
61
|
+
# device_label: str
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class DeviceInfo(TypedDict):
|
|
65
|
+
"""Information about a specific device.
|
|
66
|
+
|
|
67
|
+
Attributes
|
|
68
|
+
----------
|
|
69
|
+
label : str
|
|
70
|
+
The user-provided label of the device.
|
|
71
|
+
library : str
|
|
72
|
+
The name of the device adapter library (e.g. "DemoCamera" or "ASITiger").
|
|
73
|
+
name : str
|
|
74
|
+
The name of the device, as known to the adapter. (e.g. "DCam" or "XYStage")
|
|
75
|
+
type : str
|
|
76
|
+
The type of the device (e.g. "Camera", "XYStage", "State", etc...)
|
|
77
|
+
description : str
|
|
78
|
+
A description of the device, provided by the adapter.
|
|
79
|
+
properties : tuple[PropertyInfo, ...]
|
|
80
|
+
Information about the device's properties.
|
|
81
|
+
parent_label : str
|
|
82
|
+
*Not Required*. The label of the parent device, if any. This will be missing for
|
|
83
|
+
hub devices and other devices that are not peripherals.
|
|
84
|
+
labels : tuple[str, ...]
|
|
85
|
+
*Not Required*. The labels of the device, if it is a state device.
|
|
86
|
+
child_names : tuple[str, ...]
|
|
87
|
+
*Not Required*. The names of the child (peripheral) devices, if it is a hub
|
|
88
|
+
device.
|
|
89
|
+
is_continuous_focus_drive : bool
|
|
90
|
+
*Not Required*. Whether the device is a continuous focus drive. If missing,
|
|
91
|
+
assume `False`.
|
|
92
|
+
focus_direction : Literal["Unknown", "TowardSample", "AwayFromSample"]
|
|
93
|
+
*Not Required*. The direction of focus movement. Will be missing if device
|
|
94
|
+
is not a Stage device.
|
|
95
|
+
is_sequenceable : bool
|
|
96
|
+
*Not Required*. Whether the device is sequenceable. If missing, assume `False`.
|
|
97
|
+
This may be present for Cameras, SLMs, Stages, and XYStages. See also the
|
|
98
|
+
`is_sequenceable` property of each
|
|
99
|
+
[`PropertyInfo`][pymmcore_plus.metadata.schema.PropertyInfo] object.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
label: str
|
|
103
|
+
library: str
|
|
104
|
+
name: str
|
|
105
|
+
type: str
|
|
106
|
+
description: str
|
|
107
|
+
properties: tuple[PropertyInfo, ...]
|
|
108
|
+
|
|
109
|
+
# hub devices and non-peripheral devices will have no parent_label
|
|
110
|
+
parent_label: NotRequired[str]
|
|
111
|
+
# state device only
|
|
112
|
+
labels: NotRequired[tuple[str, ...]]
|
|
113
|
+
# hub device only
|
|
114
|
+
child_names: NotRequired[tuple[str, ...]]
|
|
115
|
+
# stage/focus device only
|
|
116
|
+
is_continuous_focus_drive: NotRequired[bool]
|
|
117
|
+
focus_direction: NotRequired[Literal["Unknown", "TowardSample", "AwayFromSample"]]
|
|
118
|
+
# camera, slm, stage/focus, or XYStage devices only
|
|
119
|
+
is_sequenceable: NotRequired[bool]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class SystemInfo(TypedDict):
|
|
123
|
+
"""General system information.
|
|
124
|
+
|
|
125
|
+
Attributes
|
|
126
|
+
----------
|
|
127
|
+
pymmcore_version : str
|
|
128
|
+
The version of the PyMMCore library.
|
|
129
|
+
pymmcore_plus_version : str
|
|
130
|
+
The version of the PyMMCore Plus library.
|
|
131
|
+
mmcore_version : str
|
|
132
|
+
The version of the MMCore library. (e.g. `MMCore version 11.1.1`)
|
|
133
|
+
device_api_version : str
|
|
134
|
+
The version of the device API.
|
|
135
|
+
(e.g. `Device API version 71, Module API version 10`)
|
|
136
|
+
device_adapter_search_paths : tuple[str, ...]
|
|
137
|
+
The active search paths for device adapters. This may be useful to indicate
|
|
138
|
+
the nightly build of device adapters, or other information that isn't in the
|
|
139
|
+
version numbers.
|
|
140
|
+
system_configuration_file : str | None
|
|
141
|
+
The path of the last loaded system configuration file, if any.
|
|
142
|
+
primary_log_file : str
|
|
143
|
+
The path of the primary log file.
|
|
144
|
+
sequence_buffer_size_mb : int
|
|
145
|
+
The size of the circular buffer available for storing images during
|
|
146
|
+
hardware-triggered sequence acquisition.
|
|
147
|
+
continuous_focus_enabled : bool
|
|
148
|
+
Whether continuous focus is enabled.
|
|
149
|
+
continuous_focus_locked : bool
|
|
150
|
+
Whether continuous focus is currently locked.
|
|
151
|
+
auto_shutter : bool
|
|
152
|
+
Whether auto-shutter is currently active.
|
|
153
|
+
timeout_ms : int | None
|
|
154
|
+
*Not Required*. The current timeout in milliseconds for the system. The default
|
|
155
|
+
timeout is 5000 ms.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
pymmcore_version: str
|
|
159
|
+
pymmcore_plus_version: str
|
|
160
|
+
mmcore_version: str
|
|
161
|
+
device_api_version: str
|
|
162
|
+
device_adapter_search_paths: tuple[str, ...]
|
|
163
|
+
system_configuration_file: Optional[str]
|
|
164
|
+
primary_log_file: str
|
|
165
|
+
sequence_buffer_size_mb: int
|
|
166
|
+
continuous_focus_enabled: bool
|
|
167
|
+
continuous_focus_locked: bool
|
|
168
|
+
auto_shutter: bool
|
|
169
|
+
timeout_ms: NotRequired[int]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class ImageInfo(TypedDict):
|
|
173
|
+
"""Information about the image format for a camera device.
|
|
174
|
+
|
|
175
|
+
Attributes
|
|
176
|
+
----------
|
|
177
|
+
camera_label : str
|
|
178
|
+
The label of the corresponding camera device.
|
|
179
|
+
plane_shape : tuple[int, ...]
|
|
180
|
+
The shape (height, width[, num_components]) of the numpy array that will be
|
|
181
|
+
returned for each snap of the camera. This will be length 2 for monochromatic
|
|
182
|
+
images, and length 3 for images with multiple components (e.g. RGB).
|
|
183
|
+
dtype : str
|
|
184
|
+
The numpy dtype of the image array (e.g. "uint8", "uint16", etc...)
|
|
185
|
+
height : int
|
|
186
|
+
The height of the image in pixels.
|
|
187
|
+
width : int
|
|
188
|
+
The width of the image in pixels.
|
|
189
|
+
pixel_format : Literal["Mono8", "Mono10", "Mono12", "Mono14", "Mono16", "Mono32", "RGB8", "RGB10", "RGB12", "RGB14", "RGB16"]
|
|
190
|
+
The GenICam pixel format of the camera. See
|
|
191
|
+
[PixelFormat][pymmcore_plus.PixelFormat] and
|
|
192
|
+
<https://docs.baslerweb.com/pixel-format#unpacked-and-packed-pixel-formats>
|
|
193
|
+
for more information.
|
|
194
|
+
pixel_size_config_name : str
|
|
195
|
+
The name of the currently active pixel size configuration.
|
|
196
|
+
pixel_size_um : float
|
|
197
|
+
The pixel size in microns.
|
|
198
|
+
magnification_factor : float
|
|
199
|
+
*Not Required*. The product of magnification of all loaded devices of type
|
|
200
|
+
MagnifierDevice. If no devices are found, or all have magnification=1, this
|
|
201
|
+
will not be present.
|
|
202
|
+
pixel_size_affine : tuple[float, float, float, float, float, float]
|
|
203
|
+
*Not Required*. Affine Transform to relate camera pixels with stage movement,
|
|
204
|
+
corrected for binning and known magnification devices. The affine transform
|
|
205
|
+
consists of the first two rows of a 3x3 matrix, the third row is always assumed
|
|
206
|
+
to be `(0, 0, 1)`. If missing, assume identity transform.
|
|
207
|
+
roi : tuple[int, int, int, int]
|
|
208
|
+
*Not Required*. The active subarray (ROI: region of interest) on the camera, in
|
|
209
|
+
the form `(x_offset, y_offset, width, height)`. If missing, the full chip
|
|
210
|
+
is being used.
|
|
211
|
+
multi_roi : tuple[list[int], list[int], list[int], list[int]]
|
|
212
|
+
*Not Required*. The active subarrays (ROIs: regions of interest) on the camera,
|
|
213
|
+
in the form `(x_offsets, y_offsets, widths, heights)`. If missing, the camera
|
|
214
|
+
does not support multiple ROIs or is not currently using them.
|
|
215
|
+
""" # noqa: E501
|
|
216
|
+
|
|
217
|
+
camera_label: str
|
|
218
|
+
plane_shape: tuple[int, ...]
|
|
219
|
+
dtype: str
|
|
220
|
+
|
|
221
|
+
height: int
|
|
222
|
+
width: int
|
|
223
|
+
pixel_format: Literal[
|
|
224
|
+
"Mono8",
|
|
225
|
+
"Mono10",
|
|
226
|
+
"Mono12",
|
|
227
|
+
"Mono14",
|
|
228
|
+
"Mono16",
|
|
229
|
+
"Mono32",
|
|
230
|
+
"RGB8",
|
|
231
|
+
"RGB10",
|
|
232
|
+
"RGB12",
|
|
233
|
+
"RGB14",
|
|
234
|
+
"RGB16",
|
|
235
|
+
]
|
|
236
|
+
|
|
237
|
+
pixel_size_config_name: str
|
|
238
|
+
pixel_size_um: float
|
|
239
|
+
magnification_factor: NotRequired[float]
|
|
240
|
+
pixel_size_affine: NotRequired[AffineTuple]
|
|
241
|
+
roi: NotRequired[tuple[int, int, int, int]]
|
|
242
|
+
multi_roi: NotRequired[tuple[list[int], list[int], list[int], list[int]]]
|
|
243
|
+
|
|
244
|
+
# # this will be != 1 for things like multi-camera device,
|
|
245
|
+
# # or any "single" device adapter that manages multiple detectors, like PMTs, etc..
|
|
246
|
+
# num_camera_adapter_channels: NotRequired[int]
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class StagePosition(TypedDict):
|
|
250
|
+
"""Represents the position of a single stage device."""
|
|
251
|
+
|
|
252
|
+
device_label: str
|
|
253
|
+
position: Union[float, tuple[float, float]]
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class Position(TypedDict):
|
|
257
|
+
"""Represents a position in 3D space and focus.
|
|
258
|
+
|
|
259
|
+
Attributes
|
|
260
|
+
----------
|
|
261
|
+
x : float
|
|
262
|
+
*Not Required*. The X coordinate of the "active" XY stage device.
|
|
263
|
+
May be missing if there is no current XY stage device.
|
|
264
|
+
y : float
|
|
265
|
+
*Not Required*. The Y coordinate of the "active" XY stage device.
|
|
266
|
+
May be missing if there is no current XY stage device.
|
|
267
|
+
z : float
|
|
268
|
+
*Not Required*. The coordinate of the "active" focus device.
|
|
269
|
+
May be missing if there is no current focus stage device.
|
|
270
|
+
all_stages : tuple[StagePosition, ...]
|
|
271
|
+
*Not Required*. The positions of *all* stage devices (both inactive and active
|
|
272
|
+
devices that are represented by `x`, `y`, and `z`). Inclusion of this field
|
|
273
|
+
is up to the implementer.
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
x: NotRequired[float]
|
|
277
|
+
y: NotRequired[float]
|
|
278
|
+
z: NotRequired[float]
|
|
279
|
+
all_stages: NotRequired[list[StagePosition]]
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class PropertyValue(TypedDict):
|
|
283
|
+
"""A single device property setting.
|
|
284
|
+
|
|
285
|
+
This represents a single device property setting, whether it be an "active" value,
|
|
286
|
+
or an intended value as a part of a configuration preset.
|
|
287
|
+
|
|
288
|
+
Attributes
|
|
289
|
+
----------
|
|
290
|
+
dev : str
|
|
291
|
+
The label of the device.
|
|
292
|
+
prop : str
|
|
293
|
+
The name of the property.
|
|
294
|
+
val : Any
|
|
295
|
+
The value of the property.
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
dev: str
|
|
299
|
+
prop: str
|
|
300
|
+
val: Any
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class ConfigPreset(TypedDict):
|
|
304
|
+
"""A group of device property settings.
|
|
305
|
+
|
|
306
|
+
Attributes
|
|
307
|
+
----------
|
|
308
|
+
name : str
|
|
309
|
+
The name of the preset.
|
|
310
|
+
settings : tuple[PropertyValue, ...]
|
|
311
|
+
A collection of device property settings that make up the preset.
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
name: str
|
|
315
|
+
settings: tuple[PropertyValue, ...]
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class PixelSizeConfigPreset(ConfigPreset):
|
|
319
|
+
"""A specialized group of device property settings for a pixel size preset.
|
|
320
|
+
|
|
321
|
+
Attributes
|
|
322
|
+
----------
|
|
323
|
+
name : str
|
|
324
|
+
The name of the pixel size preset.
|
|
325
|
+
settings : tuple[PropertyValue, ...]
|
|
326
|
+
A collection of device property settings that make up the pixel size preset.
|
|
327
|
+
pixel_size_um : float
|
|
328
|
+
The pixel size in microns.
|
|
329
|
+
pixel_size_affine : tuple[float, float, float, float, float, float]
|
|
330
|
+
*Not Required*. Affine Transform to relate camera pixels with stage movement,
|
|
331
|
+
corrected for binning and known magnification devices. The affine transform
|
|
332
|
+
consists of the first two rows of a 3x3 matrix, the third row is always assumed
|
|
333
|
+
to be 0.0 0.0 1.0.
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
pixel_size_um: float
|
|
337
|
+
pixel_size_affine: NotRequired[AffineTuple]
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class ConfigGroup(TypedDict):
|
|
341
|
+
"""A group of configuration presets.
|
|
342
|
+
|
|
343
|
+
Attributes
|
|
344
|
+
----------
|
|
345
|
+
name : str
|
|
346
|
+
The name of the config group.
|
|
347
|
+
presets : tuple[ConfigPreset, ...]
|
|
348
|
+
A collection of presets, each of which define a set of device property settings
|
|
349
|
+
that can be applied to the system.
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
name: str
|
|
353
|
+
presets: tuple[ConfigPreset, ...]
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class SummaryMetaV1(TypedDict):
|
|
357
|
+
"""Complete summary metadata for the system.
|
|
358
|
+
|
|
359
|
+
This is the structure of the summary metadata object that is emitted during the
|
|
360
|
+
[`sequenceStarted`][pymmcore_plus.mda.events.PMDASignaler.sequenceStarted] event of
|
|
361
|
+
an MDA run. It contains general information about the system and all of the
|
|
362
|
+
devices.
|
|
363
|
+
|
|
364
|
+
It may be generated outside of a running mda sequence as well using
|
|
365
|
+
[`pymmcore_plus.metadata.summary_metadata`][]
|
|
366
|
+
|
|
367
|
+
Attributes
|
|
368
|
+
----------
|
|
369
|
+
format: Literal["summary-dict"]
|
|
370
|
+
The format of this summary metadata object.
|
|
371
|
+
version: Literal["1.0"]
|
|
372
|
+
The version of this summary metadata object.
|
|
373
|
+
datetime : str
|
|
374
|
+
The date and time when the summary metadata was generated. This is an ISO 8601
|
|
375
|
+
formatted string, including date, time and offset from UTC:
|
|
376
|
+
`YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM`
|
|
377
|
+
devices : tuple[DeviceInfo, ...]
|
|
378
|
+
Information about all loaded devices.
|
|
379
|
+
system_info : SystemInfo
|
|
380
|
+
General system information.
|
|
381
|
+
image_infos : tuple[ImageInfo, ...]
|
|
382
|
+
Information about the current image structure.
|
|
383
|
+
config_groups : tuple[ConfigGroup, ...]
|
|
384
|
+
Groups of device property settings.
|
|
385
|
+
pixel_size_configs : tuple[PixelSizeConfigPreset, ...]
|
|
386
|
+
Pixel size presets.
|
|
387
|
+
position : Position
|
|
388
|
+
Current position in 3D space.
|
|
389
|
+
mda_sequence : useq.MDASequence
|
|
390
|
+
*NotRequired*. The current MDA sequence.
|
|
391
|
+
extra: dict[str, Any]
|
|
392
|
+
*NotRequired*. Additional information, may be used to store arbitrary user info.
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
format: Literal["summary-dict"]
|
|
396
|
+
version: Literal["1.0"]
|
|
397
|
+
datetime: NotRequired[str]
|
|
398
|
+
devices: tuple[DeviceInfo, ...]
|
|
399
|
+
system_info: SystemInfo
|
|
400
|
+
image_infos: tuple[ImageInfo, ...]
|
|
401
|
+
config_groups: tuple[ConfigGroup, ...]
|
|
402
|
+
pixel_size_configs: tuple[PixelSizeConfigPreset, ...]
|
|
403
|
+
position: Position
|
|
404
|
+
mda_sequence: NotRequired[useq.MDASequence]
|
|
405
|
+
extra: NotRequired[dict[str, Any]]
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class FrameMetaV1(TypedDict):
|
|
409
|
+
"""Metadata for a single frame.
|
|
410
|
+
|
|
411
|
+
This is the structure of the summary metadata object that is emitted during the
|
|
412
|
+
[`frameReady`][pymmcore_plus.mda.events.PMDASignaler.frameReady] event of
|
|
413
|
+
an MDA run. It contains information about the frame that was just acquired. By
|
|
414
|
+
design, it is relatively lightweight and does not contain the full system state.
|
|
415
|
+
Values that are not expected to change during an MDA sequence should be looked up
|
|
416
|
+
in the summary metadata.
|
|
417
|
+
|
|
418
|
+
It may be generated outside of a running mda sequence as well using
|
|
419
|
+
[`pymmcore_plus.metadata.frame_metadata`][]
|
|
420
|
+
|
|
421
|
+
Attributes
|
|
422
|
+
----------
|
|
423
|
+
format: Literal["frame-dict"]
|
|
424
|
+
The format of this frame metadata object.
|
|
425
|
+
version: Literal["1.0"]
|
|
426
|
+
The version of this frame metadata object.
|
|
427
|
+
pixel_size_um: float
|
|
428
|
+
The pixel size in microns.
|
|
429
|
+
camera_device: str
|
|
430
|
+
The label of the camera device used to acquire the image.
|
|
431
|
+
exposure_ms: float
|
|
432
|
+
The exposure time in milliseconds.
|
|
433
|
+
property_values: tuple[PropertyValue, ...]
|
|
434
|
+
Device property settings. This is not a comprehensive list of all device
|
|
435
|
+
properties, but only those that may have changed for this frame (such as
|
|
436
|
+
properties in the channel config or light path config).
|
|
437
|
+
runner_time_ms: float
|
|
438
|
+
Elapsed time in milliseconds since the beginning of the MDA sequence.
|
|
439
|
+
position: Position
|
|
440
|
+
*NotRequired*. The current stage position(s) in 3D space. This is often slow
|
|
441
|
+
to retrieve, so its inclusion is optional and left to the implementer.
|
|
442
|
+
mda_event: useq.MDAEvent
|
|
443
|
+
*NotRequired*. The MDA event object that commanded the acquisition of this
|
|
444
|
+
frame.
|
|
445
|
+
hardware_triggered: bool
|
|
446
|
+
*NotRequired*. Whether the frame was part of a hardware-triggered sequence.
|
|
447
|
+
If missing, assume `False`.
|
|
448
|
+
images_remaining_in_buffer: int
|
|
449
|
+
*NotRequired*. The number of images remaining to be popped from the image
|
|
450
|
+
buffer (only applicable for hardware-triggered sequences).
|
|
451
|
+
camera_metadata: dict[str, Any]
|
|
452
|
+
*NotRequired*. Additional metadata from the camera device. This is unstructured
|
|
453
|
+
and may contain any information that the camera device provides. Do not rely
|
|
454
|
+
on the presence of any particular keys.
|
|
455
|
+
extra: dict[str, Any]
|
|
456
|
+
*NotRequired*. Additional information, may be used to store arbitrary user info
|
|
457
|
+
or additional metadata.
|
|
458
|
+
"""
|
|
459
|
+
|
|
460
|
+
format: Literal["frame-dict"]
|
|
461
|
+
version: Literal["1.0"]
|
|
462
|
+
pixel_size_um: float
|
|
463
|
+
camera_device: Optional[str]
|
|
464
|
+
exposure_ms: float
|
|
465
|
+
property_values: tuple[PropertyValue, ...]
|
|
466
|
+
runner_time_ms: float
|
|
467
|
+
position: NotRequired[Position]
|
|
468
|
+
mda_event: NotRequired[useq.MDAEvent]
|
|
469
|
+
hardware_triggered: NotRequired[bool]
|
|
470
|
+
images_remaining_in_buffer: NotRequired[int]
|
|
471
|
+
camera_metadata: NotRequired[dict[str, Any]]
|
|
472
|
+
extra: NotRequired[dict[str, Any]]
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
|
+
from contextlib import suppress
|
|
7
|
+
from datetime import timedelta
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from types import MappingProxyType
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
# use msgspec if available
|
|
16
|
+
import msgspec
|
|
17
|
+
except ImportError:
|
|
18
|
+
msgspec = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
import pydantic # noqa: F401
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def encode_hook(obj: Any, raises: bool = True) -> Any:
|
|
26
|
+
"""Hook to encode objects that are not JSON serializable."""
|
|
27
|
+
if not TYPE_CHECKING:
|
|
28
|
+
pydantic = sys.modules.get("pydantic")
|
|
29
|
+
if pydantic and isinstance(obj, pydantic.BaseModel):
|
|
30
|
+
try:
|
|
31
|
+
return obj.model_dump(mode="json", exclude_unset=True)
|
|
32
|
+
except (AttributeError, TypeError):
|
|
33
|
+
return to_builtins(obj.dict(exclude_unset=True))
|
|
34
|
+
if isinstance(obj, MappingProxyType):
|
|
35
|
+
return dict(obj)
|
|
36
|
+
if isinstance(obj, np.number):
|
|
37
|
+
return obj.item()
|
|
38
|
+
if isinstance(obj, timedelta):
|
|
39
|
+
return obj.total_seconds()
|
|
40
|
+
if isinstance(obj, Enum):
|
|
41
|
+
return obj.value
|
|
42
|
+
if raises:
|
|
43
|
+
raise NotImplementedError(f"Cannot serialize object of type {type(obj)}")
|
|
44
|
+
return obj
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# def decode_hook(type: type, obj: Any) -> Any:
|
|
48
|
+
# """Hook to decode objects that are not JSON deserializable."""
|
|
49
|
+
# if not TYPE_CHECKING:
|
|
50
|
+
# pydantic = sys.modules.get("pydantic")
|
|
51
|
+
# if pydantic:
|
|
52
|
+
# with suppress(TypeError):
|
|
53
|
+
# if issubclass(type, pydantic.BaseModel):
|
|
54
|
+
# return type.model_validate(obj)
|
|
55
|
+
# raise NotImplementedError(f"Cannot deserialize object of type {type}")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def schema_hook(obj: type) -> dict[str, Any]:
|
|
59
|
+
"""Hook to convert objects to schema."""
|
|
60
|
+
if not TYPE_CHECKING:
|
|
61
|
+
pydantic = sys.modules.get("pydantic")
|
|
62
|
+
if pydantic:
|
|
63
|
+
with suppress(TypeError):
|
|
64
|
+
if issubclass(obj, pydantic.BaseModel):
|
|
65
|
+
return obj.model_json_schema()
|
|
66
|
+
raise NotImplementedError(f"Cannot create schema for object of type {type(obj)}")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def msgspec_json_dumps(obj: Any, *, indent: int | None = None) -> bytes:
|
|
70
|
+
"""Serialize object to bytes."""
|
|
71
|
+
encoded = msgspec.json.encode(obj, enc_hook=encode_hook)
|
|
72
|
+
if indent is not None:
|
|
73
|
+
encoded = msgspec.json.format(encoded, indent=indent)
|
|
74
|
+
return encoded # type: ignore [no-any-return]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def msgspec_json_loads(s: bytes | str) -> Any:
|
|
78
|
+
"""Deserialize bytes to object."""
|
|
79
|
+
return msgspec.json.decode(s)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def msgspec_to_builtins(obj: Any) -> Any:
|
|
83
|
+
"""Convert object to built-in types."""
|
|
84
|
+
return msgspec.to_builtins(obj, enc_hook=encode_hook)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def msgspec_to_schema(type: Any) -> Any:
|
|
88
|
+
"""Generate JSON schema for a given type."""
|
|
89
|
+
if msgspec is None: # pragma: no cover
|
|
90
|
+
raise ImportError("msgspec is required for this function")
|
|
91
|
+
return msgspec.json.schema(type, schema_hook=schema_hook)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def std_json_dumps(obj: Any, *, indent: int | None = None) -> bytes:
|
|
95
|
+
"""Serialize object to bytes."""
|
|
96
|
+
return json.dumps(obj, default=std_to_builtins, indent=indent).encode("utf-8")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def std_json_loads(s: bytes | str) -> Any:
|
|
100
|
+
"""Deserialize bytes to object."""
|
|
101
|
+
return json.loads(s)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def std_to_builtins(obj: Any) -> Any:
|
|
105
|
+
"""Convert object to built-in types."""
|
|
106
|
+
if isinstance(obj, Mapping):
|
|
107
|
+
return {k: std_to_builtins(v) for k, v in obj.items()}
|
|
108
|
+
if isinstance(obj, Sequence) and not isinstance(obj, str):
|
|
109
|
+
return [std_to_builtins(v) for v in obj]
|
|
110
|
+
return encode_hook(obj, raises=False)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if msgspec is None: # pragma: no cover
|
|
114
|
+
json_dumps = std_json_dumps
|
|
115
|
+
json_loads = std_json_loads
|
|
116
|
+
to_builtins = std_to_builtins
|
|
117
|
+
else:
|
|
118
|
+
json_dumps = msgspec_json_dumps
|
|
119
|
+
json_loads = msgspec_json_loads
|
|
120
|
+
to_builtins = msgspec_to_builtins
|
pymmcore_plus/mocks.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from unittest.mock import MagicMock
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MockSequenceableCore(MagicMock):
|
|
6
|
+
"""Sequenceable mock for testing."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, *args: Any, max_len: int = 100, **kwargs: Any) -> None:
|
|
9
|
+
super().__init__(*args, **kwargs)
|
|
10
|
+
|
|
11
|
+
from pymmcore_plus import CMMCorePlus
|
|
12
|
+
|
|
13
|
+
if isinstance(kwargs.get("wraps", None), CMMCorePlus):
|
|
14
|
+
self.isExposureSequenceable.return_value = True
|
|
15
|
+
self.getExposureSequenceMaxLength.return_value = max_len
|
|
16
|
+
|
|
17
|
+
self.isStageSequenceable.return_value = True
|
|
18
|
+
self.getStageSequenceMaxLength.return_value = max_len
|
|
19
|
+
|
|
20
|
+
self.isXYStageSequenceable.return_value = True
|
|
21
|
+
self.getXYStageSequenceMaxLength.return_value = max_len
|
|
22
|
+
|
|
23
|
+
self.getSLMSequenceMaxLength.return_value = max_len
|
|
24
|
+
self.getPropertySequenceMaxLength.return_value = max_len
|
|
25
|
+
|
|
26
|
+
self.isPropertySequenceable.side_effect = self._isPropertySequenceable
|
|
27
|
+
|
|
28
|
+
self.loadExposureSequence.return_value = None
|
|
29
|
+
self.loadStageSequence.return_value = None
|
|
30
|
+
self.loadXYStageSequence.return_value = None
|
|
31
|
+
# TODO: add support in pymmcore-nano
|
|
32
|
+
if hasattr(CMMCorePlus, "loadSLMSequence"):
|
|
33
|
+
self.loadSLMSequence.return_value = None
|
|
34
|
+
|
|
35
|
+
self.loadPropertySequence.return_value = None
|
|
36
|
+
|
|
37
|
+
self.startExposureSequence.return_value = None
|
|
38
|
+
self.stopExposureSequence.return_value = None
|
|
39
|
+
|
|
40
|
+
self.startStageSequence.return_value = None
|
|
41
|
+
self.stopStageSequence.return_value = None
|
|
42
|
+
|
|
43
|
+
self.startXYStageSequence.return_value = None
|
|
44
|
+
self.stopXYStageSequence.return_value = None
|
|
45
|
+
|
|
46
|
+
self.startPropertySequence.return_value = None
|
|
47
|
+
self.stopPropertySequence.return_value = None
|
|
48
|
+
|
|
49
|
+
def _isPropertySequenceable(self, dev: str, prop: str) -> bool:
|
|
50
|
+
# subclass to implement more interesting behavior
|
|
51
|
+
return True
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import datetime
|
|
6
5
|
import warnings
|
|
7
|
-
from typing import TYPE_CHECKING, Any, Callable
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
8
7
|
|
|
9
8
|
from pymmcore_plus import CFGCommand, DeviceType, FocusDirection, Keyword
|
|
9
|
+
from pymmcore_plus._util import timestamp
|
|
10
10
|
|
|
11
11
|
from ._config_group import ConfigGroup, ConfigPreset, Setting
|
|
12
12
|
from ._device import Device
|
|
@@ -15,11 +15,12 @@ from ._pixel_size_config import DEFAULT_AFFINE, PixelSizePreset
|
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
17
|
import io
|
|
18
|
+
from collections.abc import Iterable, Sequence
|
|
18
19
|
from typing import TypeAlias
|
|
19
20
|
|
|
20
21
|
Executor: TypeAlias = Callable[[Microscope, Sequence[str]], None]
|
|
21
22
|
|
|
22
|
-
__all__ = ["
|
|
23
|
+
__all__ = ["dump", "load_from_string"]
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
def load_from_string(text: str, scope: Microscope | None = None) -> Microscope:
|
|
@@ -57,9 +58,7 @@ INIT = _serialize(CFGCommand.Property, Keyword.CoreDevice, Keyword.CoreInitializ
|
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
def yield_date(scope: Microscope) -> Iterable[str]:
|
|
60
|
-
|
|
61
|
-
date = now.astimezone().strftime("%a %b %d %H:%M:%S %Z %Y")
|
|
62
|
-
yield f"# Date: {date}\n"
|
|
61
|
+
yield f"# Date: {timestamp()}\n"
|
|
63
62
|
|
|
64
63
|
|
|
65
64
|
def iter_devices(scope: Microscope) -> Iterable[str]:
|