pymmcore-plus 0.13.7__py3-none-any.whl → 0.14.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 +2 -0
- pymmcore_plus/_accumulator.py +258 -0
- pymmcore_plus/core/__init__.py +34 -1
- pymmcore_plus/core/_device.py +739 -19
- pymmcore_plus/core/_mmcore_plus.py +165 -8
- pymmcore_plus/mda/handlers/_tensorstore_handler.py +3 -1
- {pymmcore_plus-0.13.7.dist-info → pymmcore_plus-0.14.0.dist-info}/METADATA +14 -37
- {pymmcore_plus-0.13.7.dist-info → pymmcore_plus-0.14.0.dist-info}/RECORD +11 -10
- {pymmcore_plus-0.13.7.dist-info → pymmcore_plus-0.14.0.dist-info}/WHEEL +0 -0
- {pymmcore_plus-0.13.7.dist-info → pymmcore_plus-0.14.0.dist-info}/entry_points.txt +0 -0
- {pymmcore_plus-0.13.7.dist-info → pymmcore_plus-0.14.0.dist-info}/licenses/LICENSE +0 -0
pymmcore_plus/core/_device.py
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Literal, overload
|
|
4
4
|
|
|
5
|
-
from ._constants import DeviceType
|
|
5
|
+
from ._constants import DeviceType, FocusDirection, Keyword
|
|
6
6
|
from ._property import DeviceProperty
|
|
7
7
|
from .events._device_signal_view import _DevicePropValueSignal
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Sequence
|
|
11
|
+
|
|
12
|
+
from pymmcore import StateLabel
|
|
13
|
+
from typing_extensions import Self
|
|
14
|
+
|
|
15
|
+
from pymmcore_plus._accumulator import (
|
|
16
|
+
PositionChangeAccumulator,
|
|
17
|
+
XYPositionChangeAccumulator,
|
|
18
|
+
)
|
|
10
19
|
from pymmcore_plus.core.events._protocol import PSignalInstance
|
|
11
20
|
|
|
12
21
|
from ._constants import DeviceDetectionStatus
|
|
@@ -14,7 +23,7 @@ if TYPE_CHECKING:
|
|
|
14
23
|
|
|
15
24
|
|
|
16
25
|
class Device:
|
|
17
|
-
"""Convenience
|
|
26
|
+
"""Convenience object-oriented device API.
|
|
18
27
|
|
|
19
28
|
This is the type of object that is returned by
|
|
20
29
|
[`pymmcore_plus.CMMCorePlus.getDeviceObject`][]
|
|
@@ -22,9 +31,9 @@ class Device:
|
|
|
22
31
|
Parameters
|
|
23
32
|
----------
|
|
24
33
|
device_label : str
|
|
25
|
-
Device
|
|
34
|
+
Device label assigned to this device.
|
|
26
35
|
mmcore : CMMCorePlus
|
|
27
|
-
CMMCorePlus instance
|
|
36
|
+
CMMCorePlus instance that owns this device.
|
|
28
37
|
|
|
29
38
|
Examples
|
|
30
39
|
--------
|
|
@@ -46,6 +55,21 @@ class Device:
|
|
|
46
55
|
UNASSIGNED = "__UNASSIGNED__"
|
|
47
56
|
propertyChanged: PSignalInstance
|
|
48
57
|
|
|
58
|
+
@classmethod
|
|
59
|
+
def create(cls, device_label: str, mmcore: CMMCorePlus) -> Self:
|
|
60
|
+
sub_cls = cls.get_subclass(device_label, mmcore)
|
|
61
|
+
# make sure it's an error to call this class method on a subclass with
|
|
62
|
+
# a non-matching type
|
|
63
|
+
if issubclass(sub_cls, cls):
|
|
64
|
+
return sub_cls(device_label, mmcore)
|
|
65
|
+
dev_type = mmcore.getDeviceType(device_label).name
|
|
66
|
+
raise TypeError(f"Cannot cast {dev_type} {device_label!r} to {cls}")
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def get_subclass(cls, device_label: str, mmcore: CMMCorePlus) -> type[Device]:
|
|
70
|
+
dev_type = mmcore.getDeviceType(device_label)
|
|
71
|
+
return _TYPE_MAP[dev_type]
|
|
72
|
+
|
|
49
73
|
def __init__(
|
|
50
74
|
self,
|
|
51
75
|
device_label: str = UNASSIGNED,
|
|
@@ -63,6 +87,18 @@ class Device:
|
|
|
63
87
|
self._mmc = mmcore
|
|
64
88
|
|
|
65
89
|
self._label = device_label
|
|
90
|
+
self._type = None
|
|
91
|
+
if self.isLoaded():
|
|
92
|
+
adapter_name = self._mmc.getDeviceLibrary(device_label)
|
|
93
|
+
device_name = self._mmc.getDeviceName(device_label)
|
|
94
|
+
description = self._mmc.getDeviceDescription(device_label)
|
|
95
|
+
type = self._mmc.getDeviceType(device_label) # noqa: A001
|
|
96
|
+
if self.type() != type:
|
|
97
|
+
raise TypeError(
|
|
98
|
+
f"Cannot create loaded device with label {device_label!r} and type "
|
|
99
|
+
f"{type.name!r} as an instance of {self.__class__.__name__!r}"
|
|
100
|
+
)
|
|
101
|
+
|
|
66
102
|
self._adapter_name = adapter_name
|
|
67
103
|
self._device_name = device_name
|
|
68
104
|
self._type = type
|
|
@@ -77,7 +113,9 @@ class Device:
|
|
|
77
113
|
@label.setter
|
|
78
114
|
def label(self, value: str) -> None:
|
|
79
115
|
if self.isLoaded():
|
|
80
|
-
raise RuntimeError("Cannot change label of loaded device")
|
|
116
|
+
raise RuntimeError(f"Cannot change label of loaded device {self.label!r}.")
|
|
117
|
+
if value in self._mmc.getLoadedDevices(): # pragma: no cover
|
|
118
|
+
raise RuntimeError(f"Label {value!r} is already in use.")
|
|
81
119
|
self._label = value
|
|
82
120
|
|
|
83
121
|
@property
|
|
@@ -120,24 +158,54 @@ class Device:
|
|
|
120
158
|
@property
|
|
121
159
|
def properties(self) -> tuple[DeviceProperty, ...]:
|
|
122
160
|
"""Get all properties supported by device as DeviceProperty objects."""
|
|
123
|
-
return tuple(
|
|
124
|
-
DeviceProperty(self.label, name, self._mmc) for name in self.propertyNames()
|
|
125
|
-
)
|
|
161
|
+
return tuple(self.getPropertyObject(name) for name in self.propertyNames())
|
|
126
162
|
|
|
127
163
|
def getPropertyObject(self, property_name: str) -> DeviceProperty:
|
|
128
164
|
"""Return a `DeviceProperty` object bound to this device on this core."""
|
|
165
|
+
if not self._mmc.hasProperty(self.label, property_name):
|
|
166
|
+
raise ValueError(f"Device {self.label!r} has no property {property_name!r}")
|
|
129
167
|
return DeviceProperty(self.label, property_name, self._mmc)
|
|
130
168
|
|
|
169
|
+
def setProperty(self, property_name: str, value: bool | float | int | str) -> None:
|
|
170
|
+
"""Set a device property value.
|
|
171
|
+
|
|
172
|
+
See also,
|
|
173
|
+
[`Device.getPropertyObject`][pymmcore_plus.core.Device.getPropertyObject].
|
|
174
|
+
|
|
175
|
+
Examples
|
|
176
|
+
--------
|
|
177
|
+
>>> camera = Device("Camera")
|
|
178
|
+
>>> camera.setProperty("Exposure", 100)
|
|
179
|
+
>>> print(camera.getProperty("Exposure"))
|
|
180
|
+
# or
|
|
181
|
+
>>> exposure = camera.getPropertyObject("Exposure")
|
|
182
|
+
>>> exposure.value = 100
|
|
183
|
+
>>> print(exposure.value)
|
|
184
|
+
"""
|
|
185
|
+
return self._mmc.setProperty(self.label, property_name, value)
|
|
186
|
+
|
|
187
|
+
def getProperty(self, property_name: str) -> str:
|
|
188
|
+
"""Get a device property value."""
|
|
189
|
+
return self._mmc.getProperty(self.label, property_name)
|
|
190
|
+
|
|
131
191
|
def initialize(self) -> None:
|
|
132
192
|
"""Initialize device."""
|
|
133
193
|
return self._mmc.initializeDevice(self.label)
|
|
134
194
|
|
|
195
|
+
def unload(self) -> None:
|
|
196
|
+
"""Unload device from the core and adjust all configuration data."""
|
|
197
|
+
return self._mmc.unloadDevice(self.label)
|
|
198
|
+
|
|
199
|
+
def isLoaded(self) -> bool:
|
|
200
|
+
"""Return `True` if device is loaded."""
|
|
201
|
+
return self.label in self._mmc.getLoadedDevices()
|
|
202
|
+
|
|
135
203
|
def load(
|
|
136
204
|
self,
|
|
137
205
|
adapter_name: str = "",
|
|
138
206
|
device_name: str = "",
|
|
139
207
|
device_label: str = "",
|
|
140
|
-
) ->
|
|
208
|
+
) -> Device:
|
|
141
209
|
"""Load device from the plugin library.
|
|
142
210
|
|
|
143
211
|
Parameters
|
|
@@ -156,6 +224,9 @@ class Device:
|
|
|
156
224
|
assigned a default name: `adapter_name-device_name`, unless this Device
|
|
157
225
|
instance was initialized with a label.
|
|
158
226
|
"""
|
|
227
|
+
# if self.isLoaded():
|
|
228
|
+
# raise RuntimeError(f"Device {self.label!r} is already loaded.")
|
|
229
|
+
|
|
159
230
|
if not (adapter_name := adapter_name or self._adapter_name):
|
|
160
231
|
raise TypeError("Must specify adapter_name")
|
|
161
232
|
if not (device_name := device_name or self._device_name):
|
|
@@ -165,15 +236,10 @@ class Device:
|
|
|
165
236
|
elif self.label == self.UNASSIGNED:
|
|
166
237
|
self.label = f"{adapter_name}-{device_name}"
|
|
167
238
|
|
|
239
|
+
# note: this method takes care of label already being loaded and only
|
|
240
|
+
# warns if the exact label, adapter, and device are in use
|
|
168
241
|
self._mmc.loadDevice(self.label, adapter_name, device_name)
|
|
169
|
-
|
|
170
|
-
def unload(self) -> None:
|
|
171
|
-
"""Unload device from the core and adjust all configuration data."""
|
|
172
|
-
return self._mmc.unloadDevice(self.label)
|
|
173
|
-
|
|
174
|
-
def isLoaded(self) -> bool:
|
|
175
|
-
"""Return `True` if device is loaded."""
|
|
176
|
-
return self.label in self._mmc.getLoadedDevices()
|
|
242
|
+
return Device.create(self.label, self._mmc)
|
|
177
243
|
|
|
178
244
|
def detect(self) -> DeviceDetectionStatus:
|
|
179
245
|
"""Tries to communicate to device through a given serial port.
|
|
@@ -205,6 +271,14 @@ class Device:
|
|
|
205
271
|
"""Block the calling thread until device becomes non-busy."""
|
|
206
272
|
self._mmc.waitForDevice(self.label)
|
|
207
273
|
|
|
274
|
+
def getParentLabel(self) -> str:
|
|
275
|
+
"""Return the parent device label of this device."""
|
|
276
|
+
return self._mmc.getParentLabel(self.label)
|
|
277
|
+
|
|
278
|
+
def setParentLabel(self, parent_label: str) -> None:
|
|
279
|
+
"""Set the parent device label of this device."""
|
|
280
|
+
self._mmc.setParentLabel(self.label, parent_label)
|
|
281
|
+
|
|
208
282
|
def __repr__(self) -> str:
|
|
209
283
|
if self.isLoaded():
|
|
210
284
|
n = len(self.propertyNames())
|
|
@@ -214,4 +288,650 @@ class Device:
|
|
|
214
288
|
props = "NOT LOADED"
|
|
215
289
|
lib = ""
|
|
216
290
|
core = repr(self._mmc).strip("<>")
|
|
217
|
-
return f"<
|
|
291
|
+
return f"<{self.__class__.__name__} {self.label!r} {lib}on {core}: {props}>"
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class CameraDevice(Device):
|
|
295
|
+
def type(self) -> Literal[DeviceType.Camera]:
|
|
296
|
+
return DeviceType.Camera
|
|
297
|
+
|
|
298
|
+
def setROI(self, x: int, y: int, width: int, height: int) -> None:
|
|
299
|
+
"""Set region of interest for camera."""
|
|
300
|
+
self._mmc.setROI(self.label, x, y, width, height)
|
|
301
|
+
|
|
302
|
+
def getROI(self) -> list[int]: # always a list of 4 ints ... but not a tuple
|
|
303
|
+
"""Return region of interest for camera."""
|
|
304
|
+
return self._mmc.getROI(self.label)
|
|
305
|
+
|
|
306
|
+
# no device label-specific method for these ... would need to implement directly
|
|
307
|
+
# clearROI
|
|
308
|
+
# isMultiROISupported
|
|
309
|
+
# isMultiROIEnabled
|
|
310
|
+
# setMultiROI
|
|
311
|
+
# getMultiROI
|
|
312
|
+
# snapImage
|
|
313
|
+
# getImage
|
|
314
|
+
# getImageWidth
|
|
315
|
+
# getImageHeight
|
|
316
|
+
# getBytesPerPixel
|
|
317
|
+
# getImageBitDepth
|
|
318
|
+
# getNumberOfComponents
|
|
319
|
+
# getNumberOfCameraChannels
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def exposure(self) -> float:
|
|
323
|
+
return self.getExposure()
|
|
324
|
+
|
|
325
|
+
@exposure.setter
|
|
326
|
+
def exposure(self, value: float) -> None:
|
|
327
|
+
self.setExposure(value)
|
|
328
|
+
|
|
329
|
+
def setExposure(self, exposure: float) -> None:
|
|
330
|
+
"""Set exposure time for camera."""
|
|
331
|
+
self._mmc.setExposure(self.label, exposure)
|
|
332
|
+
|
|
333
|
+
def getExposure(self) -> float:
|
|
334
|
+
"""Return exposure time for camera."""
|
|
335
|
+
return self._mmc.getExposure(self.label)
|
|
336
|
+
|
|
337
|
+
def startSequenceAcquisition(
|
|
338
|
+
self, numImages: int, intervalMs: float, stopOnOverflow: bool
|
|
339
|
+
) -> None:
|
|
340
|
+
"""Start sequence acquisition."""
|
|
341
|
+
self._mmc.startSequenceAcquisition(
|
|
342
|
+
self.label, numImages, intervalMs, stopOnOverflow
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
def prepareSequenceAcquisition(self) -> None:
|
|
346
|
+
"""Prepare sequence acquisition."""
|
|
347
|
+
self._mmc.prepareSequenceAcquisition(self.label)
|
|
348
|
+
|
|
349
|
+
def stopSequenceAcquisition(self) -> None:
|
|
350
|
+
"""Stop sequence acquisition."""
|
|
351
|
+
self._mmc.stopSequenceAcquisition(self.label)
|
|
352
|
+
|
|
353
|
+
def isSequenceRunning(self) -> bool:
|
|
354
|
+
"""Return `True` if sequence acquisition is running."""
|
|
355
|
+
return self._mmc.isSequenceRunning(self.label)
|
|
356
|
+
|
|
357
|
+
def isExposureSequenceable(self) -> bool:
|
|
358
|
+
"""Return `True` if camera supports exposure sequence."""
|
|
359
|
+
return self._mmc.isExposureSequenceable(self.label)
|
|
360
|
+
|
|
361
|
+
def loadExposureSequence(self, sequence: Sequence[float]) -> None:
|
|
362
|
+
"""Load exposure sequence."""
|
|
363
|
+
self._mmc.loadExposureSequence(self.label, sequence)
|
|
364
|
+
|
|
365
|
+
def startExposureSequence(self) -> None:
|
|
366
|
+
"""Start exposure sequence."""
|
|
367
|
+
self._mmc.startExposureSequence(self.label)
|
|
368
|
+
|
|
369
|
+
def stopExposureSequence(self) -> None:
|
|
370
|
+
"""Stop exposure sequence."""
|
|
371
|
+
self._mmc.stopExposureSequence(self.label)
|
|
372
|
+
|
|
373
|
+
def getExposureSequenceMaxLength(self) -> int:
|
|
374
|
+
"""Return the maximum length of a camera's exposure sequence."""
|
|
375
|
+
return self._mmc.getExposureSequenceMaxLength(self.label)
|
|
376
|
+
|
|
377
|
+
isSequenceable = isExposureSequenceable
|
|
378
|
+
loadSequence = loadExposureSequence
|
|
379
|
+
startSequence = startExposureSequence
|
|
380
|
+
stopSequence = stopExposureSequence
|
|
381
|
+
getSequenceMaxLength = getExposureSequenceMaxLength
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class ShutterDevice(Device):
|
|
385
|
+
def type(self) -> Literal[DeviceType.Shutter]:
|
|
386
|
+
return DeviceType.Shutter
|
|
387
|
+
|
|
388
|
+
def open(self) -> None:
|
|
389
|
+
"""Open shutter."""
|
|
390
|
+
self._mmc.setShutterOpen(self.label, True)
|
|
391
|
+
|
|
392
|
+
def close(self) -> None:
|
|
393
|
+
"""Close shutter."""
|
|
394
|
+
self._mmc.setShutterOpen(self.label, False)
|
|
395
|
+
|
|
396
|
+
def isOpen(self) -> bool:
|
|
397
|
+
"""Return `True` if shutter is open."""
|
|
398
|
+
return self._mmc.getShutterOpen(self.label)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class StateDevice(Device):
|
|
402
|
+
def type(self) -> Literal[DeviceType.State]:
|
|
403
|
+
return DeviceType.State
|
|
404
|
+
|
|
405
|
+
@property
|
|
406
|
+
def state(self) -> int:
|
|
407
|
+
return self._mmc.getState(self.label)
|
|
408
|
+
|
|
409
|
+
@state.setter
|
|
410
|
+
def state(self, state: int) -> None:
|
|
411
|
+
self._mmc.setState(self.label, state)
|
|
412
|
+
|
|
413
|
+
def setState(self, state: int) -> None:
|
|
414
|
+
"""Set state."""
|
|
415
|
+
self._mmc.setState(self.label, state)
|
|
416
|
+
|
|
417
|
+
def getState(self) -> int:
|
|
418
|
+
"""Return state."""
|
|
419
|
+
return self._mmc.getState(self.label)
|
|
420
|
+
|
|
421
|
+
def getNumberOfStates(self) -> int:
|
|
422
|
+
"""Return number of states."""
|
|
423
|
+
return self._mmc.getNumberOfStates(self.label)
|
|
424
|
+
|
|
425
|
+
def setStateLabel(self, label: str) -> None:
|
|
426
|
+
"""Set state by label."""
|
|
427
|
+
self._mmc.setStateLabel(self.label, label)
|
|
428
|
+
|
|
429
|
+
def getStateLabel(self) -> StateLabel:
|
|
430
|
+
"""Return state label."""
|
|
431
|
+
return self._mmc.getStateLabel(self.label)
|
|
432
|
+
|
|
433
|
+
def defineStateLabel(self, state: int, label: str) -> None:
|
|
434
|
+
"""Define state labels."""
|
|
435
|
+
self._mmc.defineStateLabel(self.label, state, label)
|
|
436
|
+
|
|
437
|
+
def getStateLabels(self) -> tuple[StateLabel, ...]:
|
|
438
|
+
"""Return state labels."""
|
|
439
|
+
return self._mmc.getStateLabels(self.label)
|
|
440
|
+
|
|
441
|
+
def getStateFromLabel(self, label: str) -> int:
|
|
442
|
+
"""Return state for given label."""
|
|
443
|
+
return self._mmc.getStateFromLabel(self.label, label)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
class _StageBase(Device):
|
|
447
|
+
def stop(self) -> None:
|
|
448
|
+
"""Stop XY stage movement."""
|
|
449
|
+
self._mmc.stop(self.label)
|
|
450
|
+
|
|
451
|
+
def home(self) -> None:
|
|
452
|
+
"""Home XY stage."""
|
|
453
|
+
self._mmc.home(self.label)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class StageDevice(_StageBase):
|
|
457
|
+
def type(self) -> Literal[DeviceType.Stage]:
|
|
458
|
+
return DeviceType.Stage
|
|
459
|
+
|
|
460
|
+
def setPosition(self, position: float) -> None:
|
|
461
|
+
self._mmc.setPosition(self.label, position)
|
|
462
|
+
|
|
463
|
+
def getPosition(self) -> float:
|
|
464
|
+
return self._mmc.getPosition(self.label)
|
|
465
|
+
|
|
466
|
+
@property
|
|
467
|
+
def position(self) -> float:
|
|
468
|
+
return self.getPosition()
|
|
469
|
+
|
|
470
|
+
@position.setter
|
|
471
|
+
def position(self, value: float) -> None:
|
|
472
|
+
self.setPosition(value)
|
|
473
|
+
|
|
474
|
+
def setRelativePosition(self, offset: float) -> None:
|
|
475
|
+
self._mmc.setRelativePosition(self.label, offset)
|
|
476
|
+
|
|
477
|
+
def getPositionAccumulator(self) -> PositionChangeAccumulator:
|
|
478
|
+
from pymmcore_plus._accumulator import PositionChangeAccumulator
|
|
479
|
+
|
|
480
|
+
return PositionChangeAccumulator.get_cached(self.label, self._mmc)
|
|
481
|
+
|
|
482
|
+
def setOrigin(self) -> None:
|
|
483
|
+
self._mmc.setOrigin(self.label)
|
|
484
|
+
|
|
485
|
+
def setAdapterOrigin(self, newZUm: float) -> None:
|
|
486
|
+
self._mmc.setAdapterOrigin(self.label, newZUm)
|
|
487
|
+
|
|
488
|
+
def setFocusDirection(self, sign: int) -> None:
|
|
489
|
+
self._mmc.setFocusDirection(self.label, sign)
|
|
490
|
+
|
|
491
|
+
def getFocusDirection(self) -> FocusDirection:
|
|
492
|
+
return self._mmc.getFocusDirection(self.label)
|
|
493
|
+
|
|
494
|
+
def isContinuousFocusDrive(self) -> bool:
|
|
495
|
+
"""Return `True` if device supports continuous focus."""
|
|
496
|
+
return self._mmc.isContinuousFocusDrive(self.label)
|
|
497
|
+
|
|
498
|
+
def isStageSequenceable(self) -> bool:
|
|
499
|
+
"""Return `True` if device supports stage sequence."""
|
|
500
|
+
return self._mmc.isStageSequenceable(self.label)
|
|
501
|
+
|
|
502
|
+
def isStageLinearSequenceable(self) -> bool:
|
|
503
|
+
"""Return `True` if device supports linear stage sequence."""
|
|
504
|
+
return self._mmc.isStageLinearSequenceable(self.label)
|
|
505
|
+
|
|
506
|
+
def startStageSequence(self) -> None:
|
|
507
|
+
"""Start stage sequence."""
|
|
508
|
+
self._mmc.startStageSequence(self.label)
|
|
509
|
+
|
|
510
|
+
def stopStageSequence(self) -> None:
|
|
511
|
+
"""Stop stage sequence."""
|
|
512
|
+
self._mmc.stopStageSequence(self.label)
|
|
513
|
+
|
|
514
|
+
def getStageSequenceMaxLength(self) -> int:
|
|
515
|
+
"""Return maximum length of stage sequence."""
|
|
516
|
+
return self._mmc.getStageSequenceMaxLength(self.label)
|
|
517
|
+
|
|
518
|
+
def loadStageSequence(self, positions: Sequence[float]) -> None:
|
|
519
|
+
"""Load stage sequence."""
|
|
520
|
+
self._mmc.loadStageSequence(self.label, positions)
|
|
521
|
+
|
|
522
|
+
def setStageLinearSequence(self, dZ_um: float, nSlices: int) -> None:
|
|
523
|
+
"""Set stage linear sequence."""
|
|
524
|
+
self._mmc.setStageLinearSequence(self.label, dZ_um, nSlices)
|
|
525
|
+
|
|
526
|
+
isSequenceable = isStageSequenceable
|
|
527
|
+
loadSequence = loadStageSequence
|
|
528
|
+
startSequence = startStageSequence
|
|
529
|
+
stopSequence = stopStageSequence
|
|
530
|
+
getSequenceMaxLength = getStageSequenceMaxLength
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
class XYStageDevice(_StageBase):
|
|
534
|
+
def type(self) -> Literal[DeviceType.XYStage]:
|
|
535
|
+
return DeviceType.XYStage
|
|
536
|
+
|
|
537
|
+
def setXYPosition(self, x: float, y: float) -> None:
|
|
538
|
+
"""Set the position of the XY stage in microns."""
|
|
539
|
+
self._mmc.setXYPosition(self.label, x, y)
|
|
540
|
+
|
|
541
|
+
def getXYPosition(self) -> Sequence[float]:
|
|
542
|
+
"""Return the position of the XY stage in microns."""
|
|
543
|
+
return self._mmc.getXYPosition(self.label)
|
|
544
|
+
|
|
545
|
+
@property
|
|
546
|
+
def position(self) -> tuple[float, float]:
|
|
547
|
+
"""Return the position of the XY stage in microns."""
|
|
548
|
+
return tuple(self._mmc.getXYPosition(self.label)) # type: ignore [return-value]
|
|
549
|
+
|
|
550
|
+
@position.setter
|
|
551
|
+
def position(self, value: tuple[float, float]) -> None:
|
|
552
|
+
"""Set the position of the XY stage in microns."""
|
|
553
|
+
self._mmc.setXYPosition(self.label, *value)
|
|
554
|
+
|
|
555
|
+
def setRelativeXYPosition(self, dx: float, dy: float) -> None:
|
|
556
|
+
"""Set the relative position of the XY stage in microns."""
|
|
557
|
+
self._mmc.setRelativeXYPosition(self.label, dx, dy)
|
|
558
|
+
|
|
559
|
+
def getPositionAccumulator(self) -> XYPositionChangeAccumulator:
|
|
560
|
+
from pymmcore_plus._accumulator import XYPositionChangeAccumulator
|
|
561
|
+
|
|
562
|
+
return XYPositionChangeAccumulator.get_cached(self.label, self._mmc)
|
|
563
|
+
|
|
564
|
+
def getXPosition(self) -> float:
|
|
565
|
+
"""Return the X position of the XY stage in microns."""
|
|
566
|
+
return self._mmc.getXPosition(self.label)
|
|
567
|
+
|
|
568
|
+
def getYPosition(self) -> float:
|
|
569
|
+
"""Return the Y position of the XY stage in microns."""
|
|
570
|
+
return self._mmc.getYPosition(self.label)
|
|
571
|
+
|
|
572
|
+
def setOriginXY(self) -> None:
|
|
573
|
+
"""Zero the current XY stage's coordinates at the current position."""
|
|
574
|
+
self._mmc.setOriginXY(self.label)
|
|
575
|
+
|
|
576
|
+
setOrigin = setOriginXY
|
|
577
|
+
|
|
578
|
+
def setOriginX(self) -> None:
|
|
579
|
+
"""Zero the given XY stage's X coordinate at the current position."""
|
|
580
|
+
self._mmc.setOriginX(self.label)
|
|
581
|
+
|
|
582
|
+
def setOriginY(self) -> None:
|
|
583
|
+
"""Zero the given XY stage's Y coordinate at the current position."""
|
|
584
|
+
self._mmc.setOriginY(self.label)
|
|
585
|
+
|
|
586
|
+
def setAdapterOriginXY(self, newXUm: float, newYUm: float) -> None:
|
|
587
|
+
"""Enable software translation of coordinates for the current XY stage.
|
|
588
|
+
|
|
589
|
+
The current position of the stage becomes (newXUm, newYUm). It is recommended
|
|
590
|
+
that setOriginXY() be used instead where available.
|
|
591
|
+
"""
|
|
592
|
+
self._mmc.setAdapterOriginXY(self.label, newXUm, newYUm)
|
|
593
|
+
|
|
594
|
+
def isXYStageSequenceable(self) -> bool:
|
|
595
|
+
"""Return `True` if device supports XY stage sequence."""
|
|
596
|
+
return self._mmc.isXYStageSequenceable(self.label)
|
|
597
|
+
|
|
598
|
+
def startXYStageSequence(self) -> None:
|
|
599
|
+
"""Start XY stage sequence."""
|
|
600
|
+
self._mmc.startXYStageSequence(self.label)
|
|
601
|
+
|
|
602
|
+
def stopXYStageSequence(self) -> None:
|
|
603
|
+
"""Stop XY stage sequence."""
|
|
604
|
+
self._mmc.stopXYStageSequence(self.label)
|
|
605
|
+
|
|
606
|
+
def getXYStageSequenceMaxLength(self) -> int:
|
|
607
|
+
"""Return maximum length of XY stage sequence."""
|
|
608
|
+
return self._mmc.getXYStageSequenceMaxLength(self.label)
|
|
609
|
+
|
|
610
|
+
def loadXYStageSequence(
|
|
611
|
+
self, xSequence: Sequence[float], ySequence: Sequence[float]
|
|
612
|
+
) -> None:
|
|
613
|
+
"""Load XY stage sequence."""
|
|
614
|
+
self._mmc.loadXYStageSequence(self.label, xSequence, ySequence)
|
|
615
|
+
|
|
616
|
+
def loadSequence(self, sequence: Sequence[tuple[float, float]]) -> None:
|
|
617
|
+
"""Load XY stage sequence with a sequence of 2-tuples.
|
|
618
|
+
|
|
619
|
+
Provided as a wrapper for loadXYStageSequence, for API parity with other
|
|
620
|
+
sequencaable devices.
|
|
621
|
+
"""
|
|
622
|
+
xSequence, ySequence = zip(*sequence)
|
|
623
|
+
self._mmc.loadXYStageSequence(self.label, xSequence, ySequence)
|
|
624
|
+
|
|
625
|
+
isSequenceable = isXYStageSequenceable
|
|
626
|
+
startSequence = startXYStageSequence
|
|
627
|
+
stopSequence = stopXYStageSequence
|
|
628
|
+
getSequenceMaxLength = getXYStageSequenceMaxLength
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
class SerialDevice(Device):
|
|
632
|
+
def type(self) -> Literal[DeviceType.Serial]:
|
|
633
|
+
return DeviceType.Serial
|
|
634
|
+
|
|
635
|
+
def setCommand(self, command: str, term: str) -> None:
|
|
636
|
+
"""Send string to the serial device and return an answer."""
|
|
637
|
+
self._mmc.setSerialPortCommand(self.label, command, term)
|
|
638
|
+
|
|
639
|
+
def getAnswer(self, term: str) -> str:
|
|
640
|
+
"""Continuously read from the serial port until the term is encountered."""
|
|
641
|
+
return self._mmc.getSerialPortAnswer(self.label, term)
|
|
642
|
+
|
|
643
|
+
def write(self, data: bytes) -> None:
|
|
644
|
+
"""Send string to the serial device."""
|
|
645
|
+
self._mmc.writeToSerialPort(self.label, data)
|
|
646
|
+
|
|
647
|
+
def read(self) -> list[str]:
|
|
648
|
+
"""Reads the contents of the Rx buffer."""
|
|
649
|
+
return self._mmc.readFromSerialPort(self.label)
|
|
650
|
+
|
|
651
|
+
def setProperties(
|
|
652
|
+
self,
|
|
653
|
+
answerTimeout: str,
|
|
654
|
+
baudRate: str,
|
|
655
|
+
delayBetweenCharsMs: str,
|
|
656
|
+
handshaking: str,
|
|
657
|
+
parity: str,
|
|
658
|
+
stopBits: str,
|
|
659
|
+
) -> None:
|
|
660
|
+
"""Sets all com port properties in a single call."""
|
|
661
|
+
self._mmc.setSerialProperties(
|
|
662
|
+
self.label,
|
|
663
|
+
answerTimeout,
|
|
664
|
+
baudRate,
|
|
665
|
+
delayBetweenCharsMs,
|
|
666
|
+
handshaking,
|
|
667
|
+
parity,
|
|
668
|
+
stopBits,
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
@property
|
|
672
|
+
def answer_timeout(self) -> str:
|
|
673
|
+
"""Return the timeout for serial port commands."""
|
|
674
|
+
return self._mmc.getProperty(self.label, Keyword.AnswerTimeout)
|
|
675
|
+
|
|
676
|
+
@property
|
|
677
|
+
def baud_rate(self) -> str:
|
|
678
|
+
"""Return the baud rate for serial port commands."""
|
|
679
|
+
return self._mmc.getProperty(self.label, Keyword.BaudRate)
|
|
680
|
+
|
|
681
|
+
@property
|
|
682
|
+
def data_bits(self) -> str:
|
|
683
|
+
"""Return the data bits for serial port commands."""
|
|
684
|
+
return self._mmc.getProperty(self.label, Keyword.DataBits)
|
|
685
|
+
|
|
686
|
+
@property
|
|
687
|
+
def parity(self) -> str:
|
|
688
|
+
"""Return the parity for serial port commands."""
|
|
689
|
+
return self._mmc.getProperty(self.label, Keyword.Parity)
|
|
690
|
+
|
|
691
|
+
@property
|
|
692
|
+
def stop_bits(self) -> str:
|
|
693
|
+
"""Return the stop bits for serial port commands."""
|
|
694
|
+
return self._mmc.getProperty(self.label, Keyword.StopBits)
|
|
695
|
+
|
|
696
|
+
@property
|
|
697
|
+
def handshaking(self) -> str:
|
|
698
|
+
"""Return the handshaking for serial port commands."""
|
|
699
|
+
return self._mmc.getProperty(self.label, Keyword.Handshaking)
|
|
700
|
+
|
|
701
|
+
@property
|
|
702
|
+
def delay_between_chars_ms(self) -> str:
|
|
703
|
+
"""Return the delay between characters in milliseconds."""
|
|
704
|
+
return self._mmc.getProperty(self.label, Keyword.DelayBetweenCharsMs)
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
class GenericDevice(Device):
|
|
708
|
+
def type(self) -> Literal[DeviceType.Generic]:
|
|
709
|
+
return DeviceType.Generic
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
class AutoFocusDevice(Device):
|
|
713
|
+
def type(self) -> Literal[DeviceType.AutoFocus]:
|
|
714
|
+
return DeviceType.AutoFocus
|
|
715
|
+
|
|
716
|
+
# none of these actually accept a label, and should be called on Core
|
|
717
|
+
# getLastFocusScore
|
|
718
|
+
# getCurrentFocusScore
|
|
719
|
+
# enableContinuousFocus
|
|
720
|
+
# isContinuousFocusEnabled
|
|
721
|
+
# isContinuousFocusLocked
|
|
722
|
+
# isContinuousFocusDrive
|
|
723
|
+
# fullFocus
|
|
724
|
+
# incrementalFocus
|
|
725
|
+
# setAutoFocusOffset
|
|
726
|
+
# getAutoFocusOffset
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
class SLMDevice(Device):
|
|
730
|
+
def type(self) -> Literal[DeviceType.SLM]:
|
|
731
|
+
return DeviceType.SLM
|
|
732
|
+
|
|
733
|
+
def setImage(self, pixels: Any) -> None:
|
|
734
|
+
"""Write an image to the SLM ."""
|
|
735
|
+
self._mmc.setSLMImage(self.label, pixels)
|
|
736
|
+
|
|
737
|
+
@overload
|
|
738
|
+
def setPixelsTo(self, intensity: int, /) -> None: ...
|
|
739
|
+
@overload
|
|
740
|
+
def setPixelsTo(self, red: int, green: int, blue: int, /) -> None: ...
|
|
741
|
+
def setPixelsTo(self, *args: int) -> None:
|
|
742
|
+
"""Set all SLM pixels to a single 8-bit intensity or RGB color."""
|
|
743
|
+
self._mmc.setSLMPixelsTo(self.label, *args)
|
|
744
|
+
|
|
745
|
+
def displayImage(self) -> None:
|
|
746
|
+
"""Display the image on the SLM."""
|
|
747
|
+
self._mmc.displaySLMImage(self.label)
|
|
748
|
+
|
|
749
|
+
def setExposure(self, exposure_ms: float) -> None:
|
|
750
|
+
"""Set exposure time for SLM."""
|
|
751
|
+
self._mmc.setSLMExposure(self.label, exposure_ms)
|
|
752
|
+
|
|
753
|
+
def getExposure(self) -> float:
|
|
754
|
+
"""Return exposure time for SLM."""
|
|
755
|
+
return self._mmc.getSLMExposure(self.label)
|
|
756
|
+
|
|
757
|
+
@property
|
|
758
|
+
def exposure(self) -> float:
|
|
759
|
+
return self.getExposure()
|
|
760
|
+
|
|
761
|
+
@exposure.setter
|
|
762
|
+
def exposure(self, value: float) -> None:
|
|
763
|
+
self.setExposure(value)
|
|
764
|
+
|
|
765
|
+
def width(self) -> int:
|
|
766
|
+
"""Return the width of the SLM image."""
|
|
767
|
+
return self._mmc.getSLMWidth(self.label)
|
|
768
|
+
|
|
769
|
+
def height(self) -> int:
|
|
770
|
+
"""Return the height of the SLM image."""
|
|
771
|
+
return self._mmc.getSLMHeight(self.label)
|
|
772
|
+
|
|
773
|
+
def numberOfComponents(self) -> int:
|
|
774
|
+
"""Return the number of components in the SLM image."""
|
|
775
|
+
return self._mmc.getSLMNumberOfComponents(self.label)
|
|
776
|
+
|
|
777
|
+
def bytesPerPixel(self) -> int:
|
|
778
|
+
"""Return the number of bytes per pixel in the SLM image."""
|
|
779
|
+
return self._mmc.getSLMBytesPerPixel(self.label)
|
|
780
|
+
|
|
781
|
+
def getSequenceMaxLength(self) -> int:
|
|
782
|
+
"""Return the maximum length of a sequence for the SLM."""
|
|
783
|
+
return self._mmc.getSLMSequenceMaxLength(self.label)
|
|
784
|
+
|
|
785
|
+
def isSequenceable(self) -> bool:
|
|
786
|
+
"""Return `True` if the SLM supports sequences."""
|
|
787
|
+
# there is no MMCore API for this
|
|
788
|
+
try:
|
|
789
|
+
return self.getSequenceMaxLength() > 0
|
|
790
|
+
except RuntimeError:
|
|
791
|
+
return False
|
|
792
|
+
|
|
793
|
+
def loadSequence(self, imageSequence: list[bytes]) -> None:
|
|
794
|
+
"""Load a sequence of images to the SLM."""
|
|
795
|
+
self._mmc.loadSLMSequence(self.label, imageSequence)
|
|
796
|
+
|
|
797
|
+
def startSequence(self) -> None:
|
|
798
|
+
"""Start the sequence of images on the SLM."""
|
|
799
|
+
self._mmc.startSLMSequence(self.label)
|
|
800
|
+
|
|
801
|
+
def stopSequence(self) -> None:
|
|
802
|
+
"""Stop the sequence of images on the SLM."""
|
|
803
|
+
self._mmc.stopSLMSequence(self.label)
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
class HubDevice(Device):
|
|
807
|
+
def type(self) -> Literal[DeviceType.Hub]:
|
|
808
|
+
return DeviceType.Hub
|
|
809
|
+
|
|
810
|
+
def getInstalledDevices(self) -> tuple[str, ...]:
|
|
811
|
+
"""Return the list of installed devices."""
|
|
812
|
+
return self._mmc.getInstalledDevices(self.label)
|
|
813
|
+
|
|
814
|
+
def getInstalledDeviceDescription(self, device_label: str) -> str:
|
|
815
|
+
"""Return the description of the installed device."""
|
|
816
|
+
return self._mmc.getInstalledDeviceDescription(self.label, device_label)
|
|
817
|
+
|
|
818
|
+
def getLoadedPeripheralDevices(self) -> tuple[str, ...]:
|
|
819
|
+
"""Return the list of loaded peripheral devices."""
|
|
820
|
+
return self._mmc.getLoadedPeripheralDevices(self.label)
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
class GalvoDevice(Device):
|
|
824
|
+
def type(self) -> Literal[DeviceType.Galvo]:
|
|
825
|
+
return DeviceType.Galvo
|
|
826
|
+
|
|
827
|
+
def pointAndFire(self, x: float, y: float, pulseTime_us: float) -> None:
|
|
828
|
+
"""Set Galvo to (x, y) and fire the laser for a predetermined duration."""
|
|
829
|
+
self._mmc.pointGalvoAndFire(self.label, x, y, pulseTime_us)
|
|
830
|
+
|
|
831
|
+
def setSpotInterval(self, pulseTime_us: float) -> None:
|
|
832
|
+
"""Set the SpotInterval for the specified galvo device."""
|
|
833
|
+
self._mmc.setGalvoSpotInterval(self.label, pulseTime_us)
|
|
834
|
+
|
|
835
|
+
def setPosition(self, x: float, y: float) -> None:
|
|
836
|
+
"""Set the position of the galvo device."""
|
|
837
|
+
self._mmc.setGalvoPosition(self.label, x, y)
|
|
838
|
+
|
|
839
|
+
def getPosition(self) -> list[float]:
|
|
840
|
+
"""Return the position of the galvo device."""
|
|
841
|
+
return self._mmc.getGalvoPosition(self.label)
|
|
842
|
+
|
|
843
|
+
@property
|
|
844
|
+
def position(self) -> list[float]:
|
|
845
|
+
return self._mmc.getGalvoPosition(self.label)
|
|
846
|
+
|
|
847
|
+
@position.setter
|
|
848
|
+
def position(self, value: tuple[float, float]) -> None:
|
|
849
|
+
self._mmc.setGalvoPosition(self.label, *value)
|
|
850
|
+
|
|
851
|
+
def setIlluminationState(self, state: bool) -> None:
|
|
852
|
+
"""Set the galvo's illumination state to on or off."""
|
|
853
|
+
self._mmc.setGalvoIlluminationState(self.label, state)
|
|
854
|
+
|
|
855
|
+
def getXRange(self) -> float:
|
|
856
|
+
"""Get the Galvo x range."""
|
|
857
|
+
return self._mmc.getGalvoXRange(self.label)
|
|
858
|
+
|
|
859
|
+
def getXMinimum(self) -> float:
|
|
860
|
+
"""Get the Galvo x minimum."""
|
|
861
|
+
return self._mmc.getGalvoXMinimum(self.label)
|
|
862
|
+
|
|
863
|
+
def getYRange(self) -> float:
|
|
864
|
+
"""Get the Galvo y range."""
|
|
865
|
+
return self._mmc.getGalvoYRange(self.label)
|
|
866
|
+
|
|
867
|
+
def getYMinimum(self) -> float:
|
|
868
|
+
"""Get the Galvo y minimum."""
|
|
869
|
+
return self._mmc.getGalvoYMinimum(self.label)
|
|
870
|
+
|
|
871
|
+
def addPolygonVertex(self, polygonIndex: int, x: float, y: float) -> None:
|
|
872
|
+
"""Add a vertex to the polygon."""
|
|
873
|
+
self._mmc.addGalvoPolygonVertex(self.label, polygonIndex, x, y)
|
|
874
|
+
|
|
875
|
+
def deletePolygons(self) -> None:
|
|
876
|
+
"""Delete all polygons."""
|
|
877
|
+
self._mmc.deleteGalvoPolygons(self.label)
|
|
878
|
+
|
|
879
|
+
def loadPolygons(self) -> None:
|
|
880
|
+
"""Load a set of galvo polygons to the device."""
|
|
881
|
+
self._mmc.loadGalvoPolygons(self.label)
|
|
882
|
+
|
|
883
|
+
def runPolygons(self) -> None:
|
|
884
|
+
"""Run a loop of galvo polygons."""
|
|
885
|
+
self._mmc.runGalvoPolygons(self.label)
|
|
886
|
+
|
|
887
|
+
def runSequence(self) -> None:
|
|
888
|
+
"""Run a sequence of galvo positions."""
|
|
889
|
+
self._mmc.runGalvoSequence(self.label)
|
|
890
|
+
|
|
891
|
+
def setPolygonRepetitions(self, repetitions: int) -> None:
|
|
892
|
+
"""Set the number of times the galvo polygon should be repeated."""
|
|
893
|
+
self._mmc.setGalvoPolygonRepetitions(self.label, repetitions)
|
|
894
|
+
|
|
895
|
+
def getChannel(self) -> str:
|
|
896
|
+
"""Get the name of the active galvo channel (for a multi-laser galvo device)."""
|
|
897
|
+
return self._mmc.getGalvoChannel(self.label)
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
class ImageProcessorDevice(Device):
|
|
901
|
+
def type(self) -> Literal[DeviceType.ImageProcessor]:
|
|
902
|
+
return DeviceType.ImageProcessor
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
class SignalIODevice(Device):
|
|
906
|
+
def type(self) -> Literal[DeviceType.SignalIO]:
|
|
907
|
+
return DeviceType.SignalIO
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
class MagnifierDevice(Device):
|
|
911
|
+
def type(self) -> Literal[DeviceType.Magnifier]:
|
|
912
|
+
return DeviceType.Magnifier
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
# Special device...
|
|
916
|
+
class CoreDevice(Device):
|
|
917
|
+
def type(self) -> Literal[DeviceType.Core]:
|
|
918
|
+
return DeviceType.Core
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
_TYPE_MAP: dict[DeviceType, type[Device]] = {
|
|
922
|
+
DeviceType.Camera: CameraDevice,
|
|
923
|
+
DeviceType.Shutter: ShutterDevice,
|
|
924
|
+
DeviceType.State: StateDevice,
|
|
925
|
+
DeviceType.Stage: StageDevice,
|
|
926
|
+
DeviceType.XYStage: XYStageDevice,
|
|
927
|
+
DeviceType.Serial: SerialDevice,
|
|
928
|
+
DeviceType.Generic: GenericDevice,
|
|
929
|
+
DeviceType.AutoFocus: AutoFocusDevice,
|
|
930
|
+
DeviceType.Core: CoreDevice,
|
|
931
|
+
DeviceType.ImageProcessor: ImageProcessorDevice,
|
|
932
|
+
DeviceType.SignalIO: SignalIODevice,
|
|
933
|
+
DeviceType.Magnifier: MagnifierDevice,
|
|
934
|
+
DeviceType.SLM: SLMDevice,
|
|
935
|
+
DeviceType.Hub: HubDevice,
|
|
936
|
+
DeviceType.Galvo: GalvoDevice,
|
|
937
|
+
}
|