pymmcore-plus 0.12.0__py3-none-any.whl → 0.13.1__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 +3 -3
- pymmcore_plus/_benchmark.py +203 -0
- pymmcore_plus/_cli.py +78 -13
- pymmcore_plus/_logger.py +10 -2
- pymmcore_plus/_pymmcore.py +12 -0
- pymmcore_plus/_util.py +16 -10
- pymmcore_plus/core/__init__.py +3 -0
- pymmcore_plus/core/_config.py +1 -1
- pymmcore_plus/core/_config_group.py +2 -2
- pymmcore_plus/core/_constants.py +27 -3
- pymmcore_plus/core/_device.py +4 -4
- pymmcore_plus/core/_metadata.py +1 -1
- pymmcore_plus/core/_mmcore_plus.py +184 -118
- pymmcore_plus/core/_property.py +3 -5
- pymmcore_plus/core/_sequencing.py +369 -234
- pymmcore_plus/core/events/__init__.py +3 -3
- 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 +10 -7
- pymmcore_plus/mda/__init__.py +1 -1
- pymmcore_plus/mda/_engine.py +152 -43
- pymmcore_plus/mda/_runner.py +8 -1
- pymmcore_plus/mda/events/__init__.py +2 -2
- pymmcore_plus/mda/handlers/__init__.py +1 -1
- pymmcore_plus/mda/handlers/_ome_zarr_writer.py +2 -2
- pymmcore_plus/mda/handlers/_tensorstore_handler.py +6 -2
- pymmcore_plus/metadata/__init__.py +3 -3
- pymmcore_plus/metadata/functions.py +18 -8
- pymmcore_plus/metadata/schema.py +6 -5
- pymmcore_plus/mocks.py +49 -0
- pymmcore_plus/model/_config_file.py +1 -1
- pymmcore_plus/model/_core_device.py +10 -1
- pymmcore_plus/model/_device.py +17 -6
- pymmcore_plus/model/_property.py +11 -2
- pymmcore_plus/seq_tester.py +1 -1
- {pymmcore_plus-0.12.0.dist-info → pymmcore_plus-0.13.1.dist-info}/METADATA +14 -6
- pymmcore_plus-0.13.1.dist-info/RECORD +71 -0
- {pymmcore_plus-0.12.0.dist-info → pymmcore_plus-0.13.1.dist-info}/WHEEL +1 -1
- pymmcore_plus-0.12.0.dist-info/RECORD +0 -59
- {pymmcore_plus-0.12.0.dist-info → pymmcore_plus-0.13.1.dist-info}/entry_points.txt +0 -0
- {pymmcore_plus-0.12.0.dist-info → pymmcore_plus-0.13.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -12,19 +12,12 @@ from datetime import datetime
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
from re import Pattern
|
|
14
14
|
from textwrap import dedent
|
|
15
|
-
from threading import
|
|
16
|
-
from typing import
|
|
17
|
-
TYPE_CHECKING,
|
|
18
|
-
Any,
|
|
19
|
-
Callable,
|
|
20
|
-
NamedTuple,
|
|
21
|
-
TypeVar,
|
|
22
|
-
overload,
|
|
23
|
-
)
|
|
15
|
+
from threading import Thread
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Callable, NamedTuple, TypeVar, overload
|
|
24
17
|
|
|
25
|
-
import pymmcore
|
|
26
18
|
from psygnal import SignalInstance
|
|
27
19
|
|
|
20
|
+
import pymmcore_plus._pymmcore as pymmcore
|
|
28
21
|
from pymmcore_plus._logger import current_logfile, logger
|
|
29
22
|
from pymmcore_plus._util import find_micromanager, print_tabular_data
|
|
30
23
|
from pymmcore_plus.mda import MDAEngine, MDARunner, PMDAEngine
|
|
@@ -44,12 +37,11 @@ from ._constants import (
|
|
|
44
37
|
from ._device import Device
|
|
45
38
|
from ._metadata import Metadata
|
|
46
39
|
from ._property import DeviceProperty
|
|
47
|
-
from ._sequencing import can_sequence_events
|
|
48
40
|
from .events import CMMCoreSignaler, PCoreSignaler, _get_auto_core_callback_class
|
|
49
41
|
|
|
50
42
|
if TYPE_CHECKING:
|
|
51
43
|
from collections.abc import Iterable, Iterator, Sequence
|
|
52
|
-
from typing import Literal, TypedDict
|
|
44
|
+
from typing import Literal, TypedDict, Unpack
|
|
53
45
|
|
|
54
46
|
import numpy as np
|
|
55
47
|
from useq import MDAEvent
|
|
@@ -58,7 +50,6 @@ if TYPE_CHECKING:
|
|
|
58
50
|
from pymmcore_plus.metadata.schema import SummaryMetaV1
|
|
59
51
|
|
|
60
52
|
_T = TypeVar("_T")
|
|
61
|
-
_F = TypeVar("_F", bound=Callable[..., Any])
|
|
62
53
|
ListOrTuple = list[_T] | tuple[_T, ...]
|
|
63
54
|
|
|
64
55
|
class PropertySchema(TypedDict, total=False):
|
|
@@ -82,10 +73,43 @@ if TYPE_CHECKING:
|
|
|
82
73
|
type: str
|
|
83
74
|
properties: dict[str, PropertySchema]
|
|
84
75
|
|
|
85
|
-
|
|
76
|
+
class SetContextKwargs(TypedDict, total=False):
|
|
77
|
+
"""All the valid keywords and their types for the `setContext` method."""
|
|
78
|
+
|
|
79
|
+
autoFocusDevice: str
|
|
80
|
+
autoFocusOffset: float
|
|
81
|
+
autoShutter: bool
|
|
82
|
+
cameraDevice: str
|
|
83
|
+
channelGroup: str
|
|
84
|
+
circularBufferMemoryFootprint: int
|
|
85
|
+
deviceAdapterSearchPaths: list[str]
|
|
86
|
+
deviceDelayMs: tuple[str, float]
|
|
87
|
+
exposure: float | tuple[str, float]
|
|
88
|
+
focusDevice: str
|
|
89
|
+
focusDirection: str
|
|
90
|
+
galvoDevice: str
|
|
91
|
+
galvoPosition: tuple[str, float, float]
|
|
92
|
+
imageProcessorDevice: str
|
|
93
|
+
multiROI: tuple[list[int], list[int], list[int], list[int]]
|
|
94
|
+
parentLabel: tuple[str, str]
|
|
95
|
+
pixelSizeAffine: tuple[str, list[float]]
|
|
96
|
+
pixelSizeUm: tuple[str, float]
|
|
97
|
+
position: float | tuple[str, float]
|
|
98
|
+
primaryLogFile: str | tuple[str, bool]
|
|
99
|
+
property: tuple[str, str, bool | float | int | str]
|
|
100
|
+
ROI: tuple[int, int, int, int] | tuple[str, int, int, int, int]
|
|
101
|
+
SLMDevice: str
|
|
102
|
+
SLMExposure: tuple[str, float]
|
|
103
|
+
shutterDevice: str
|
|
104
|
+
shutterOpen: bool | tuple[str, bool]
|
|
105
|
+
state: tuple[str, int]
|
|
106
|
+
stateLabel: tuple[str, str]
|
|
107
|
+
systemState: pymmcore.Configuration
|
|
108
|
+
timeoutMs: int
|
|
109
|
+
XYPosition: tuple[float, float] | tuple[str, float, float]
|
|
110
|
+
XYStageDevice: str
|
|
111
|
+
ZPosition: float | tuple[str, float]
|
|
86
112
|
|
|
87
|
-
else:
|
|
88
|
-
from wrapt import synchronized
|
|
89
113
|
|
|
90
114
|
_OBJDEV_REGEX = re.compile("(.+)?(nosepiece|obj(ective)?)(turret)?s?", re.IGNORECASE)
|
|
91
115
|
_CHANNEL_REGEX = re.compile("(chan{1,2}(el)?|filt(er)?)s?", re.IGNORECASE)
|
|
@@ -151,8 +175,6 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
151
175
|
Paths to search for device adapters, by default ()
|
|
152
176
|
"""
|
|
153
177
|
|
|
154
|
-
_lock = RLock()
|
|
155
|
-
|
|
156
178
|
@classmethod
|
|
157
179
|
def instance(cls) -> CMMCorePlus:
|
|
158
180
|
"""Return the global singleton instance of `CMMCorePlus`.
|
|
@@ -240,10 +262,11 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
240
262
|
if hasattr(self, "_weak_clean"):
|
|
241
263
|
atexit.unregister(self._weak_clean)
|
|
242
264
|
self.unloadAllDevices()
|
|
265
|
+
# clean up logging
|
|
266
|
+
self.setPrimaryLogFile("")
|
|
243
267
|
|
|
244
268
|
# Re-implemented methods from the CMMCore API
|
|
245
269
|
|
|
246
|
-
@synchronized(_lock)
|
|
247
270
|
def setProperty(
|
|
248
271
|
self, label: str, propName: str, propValue: bool | float | int | str
|
|
249
272
|
) -> None:
|
|
@@ -258,7 +281,6 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
258
281
|
with self._property_change_emission_ensured(label, (propName,)):
|
|
259
282
|
super().setProperty(label, propName, propValue)
|
|
260
283
|
|
|
261
|
-
@synchronized(_lock)
|
|
262
284
|
def setState(self, stateDeviceLabel: str, state: int) -> None:
|
|
263
285
|
"""Set state (by position) on `stateDeviceLabel`, with reliable event emission.
|
|
264
286
|
|
|
@@ -271,7 +293,6 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
271
293
|
with self._property_change_emission_ensured(stateDeviceLabel, STATE_PROPS):
|
|
272
294
|
super().setState(stateDeviceLabel, state)
|
|
273
295
|
|
|
274
|
-
@synchronized(_lock)
|
|
275
296
|
def setStateLabel(self, stateDeviceLabel: str, stateLabel: str) -> None:
|
|
276
297
|
"""Set state (by label) on `stateDeviceLabel`, with reliable event emission.
|
|
277
298
|
|
|
@@ -328,33 +349,37 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
328
349
|
try:
|
|
329
350
|
super().loadDevice(label, moduleName, deviceName)
|
|
330
351
|
except RuntimeError as e:
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
352
|
+
if exc := self._load_error_with_info(label, moduleName, deviceName, str(e)):
|
|
353
|
+
raise exc from e
|
|
354
|
+
|
|
355
|
+
def _load_error_with_info(
|
|
356
|
+
self, label: str, moduleName: str, deviceName: str, msg: str = ""
|
|
357
|
+
) -> RuntimeError | None:
|
|
358
|
+
if label in self.getLoadedDevices():
|
|
359
|
+
lib = super().getDeviceLibrary(label)
|
|
360
|
+
name = super().getDeviceName(label)
|
|
361
|
+
if moduleName == lib and deviceName == name:
|
|
362
|
+
msg += f". Device {label!r} appears to be loaded already."
|
|
363
|
+
warnings.warn(msg, stacklevel=2)
|
|
364
|
+
return None
|
|
365
|
+
|
|
366
|
+
msg += f". Device {label!r} is already taken by {lib}::{name}"
|
|
367
|
+
else:
|
|
368
|
+
adapters = super().getDeviceAdapterNames()
|
|
369
|
+
if moduleName not in adapters:
|
|
370
|
+
msg += (
|
|
371
|
+
f". Adapter name {moduleName!r} not in list of known adapter "
|
|
372
|
+
f"names: {adapters}."
|
|
373
|
+
)
|
|
341
374
|
else:
|
|
342
|
-
|
|
343
|
-
if
|
|
375
|
+
devices = super().getAvailableDevices(moduleName)
|
|
376
|
+
if deviceName not in devices:
|
|
344
377
|
msg += (
|
|
345
|
-
f".
|
|
346
|
-
f"
|
|
378
|
+
f". Device name {deviceName!r} not in devices provided by "
|
|
379
|
+
f"adapter {moduleName!r}: {devices}"
|
|
347
380
|
)
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if deviceName not in devices:
|
|
351
|
-
msg += (
|
|
352
|
-
f". Device name {deviceName!r} not in devices provided by "
|
|
353
|
-
f"adapter {moduleName!r}: {devices}"
|
|
354
|
-
)
|
|
355
|
-
raise RuntimeError(msg) from e
|
|
356
|
-
|
|
357
|
-
@synchronized(_lock)
|
|
381
|
+
return RuntimeError(msg)
|
|
382
|
+
|
|
358
383
|
def loadSystemConfiguration(
|
|
359
384
|
self, fileName: str | Path = "MMConfig_demo.cfg"
|
|
360
385
|
) -> None:
|
|
@@ -582,7 +607,6 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
582
607
|
@overload
|
|
583
608
|
def getLastImageAndMD(self, *, fix: bool = True) -> tuple[np.ndarray, Metadata]: ...
|
|
584
609
|
|
|
585
|
-
@synchronized(_lock)
|
|
586
610
|
def getLastImageAndMD(
|
|
587
611
|
self, channel: int | None = None, slice: int | None = None, *, fix: bool = True
|
|
588
612
|
) -> tuple[np.ndarray, Metadata]:
|
|
@@ -618,7 +642,10 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
618
642
|
img = super().getLastImageMD(channel, slice, md)
|
|
619
643
|
else:
|
|
620
644
|
img = super().getLastImageMD(md)
|
|
621
|
-
return (
|
|
645
|
+
return (
|
|
646
|
+
self.fixImage(img) if fix and pymmcore.BACKEND == "pymmcore" else img,
|
|
647
|
+
md,
|
|
648
|
+
)
|
|
622
649
|
|
|
623
650
|
@overload
|
|
624
651
|
def popNextImageAndMD(
|
|
@@ -628,7 +655,6 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
628
655
|
@overload
|
|
629
656
|
def popNextImageAndMD(self, *, fix: bool = True) -> tuple[np.ndarray, Metadata]: ...
|
|
630
657
|
|
|
631
|
-
@synchronized(_lock)
|
|
632
658
|
def popNextImageAndMD(
|
|
633
659
|
self, channel: int = 0, slice: int = 0, *, fix: bool = True
|
|
634
660
|
) -> tuple[np.ndarray, Metadata]:
|
|
@@ -661,9 +687,12 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
661
687
|
"""
|
|
662
688
|
md = Metadata()
|
|
663
689
|
img = super().popNextImageMD(channel, slice, md)
|
|
664
|
-
|
|
690
|
+
md = Metadata(md)
|
|
691
|
+
return (
|
|
692
|
+
self.fixImage(img) if fix and pymmcore.BACKEND == "pymmcore" else img,
|
|
693
|
+
md,
|
|
694
|
+
)
|
|
665
695
|
|
|
666
|
-
@synchronized(_lock)
|
|
667
696
|
def popNextImage(self, *, fix: bool = True) -> np.ndarray:
|
|
668
697
|
"""Gets and removes the next image from the circular buffer.
|
|
669
698
|
|
|
@@ -678,9 +707,8 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
678
707
|
will be reshaped to (w, h, n_components) using `fixImage`.
|
|
679
708
|
"""
|
|
680
709
|
img: np.ndarray = super().popNextImage()
|
|
681
|
-
return self.fixImage(img) if fix else img
|
|
710
|
+
return self.fixImage(img) if fix and pymmcore.BACKEND == "pymmcore" else img
|
|
682
711
|
|
|
683
|
-
@synchronized(_lock)
|
|
684
712
|
def getNBeforeLastImageAndMD(
|
|
685
713
|
self, n: int, *, fix: bool = True
|
|
686
714
|
) -> tuple[np.ndarray, Metadata]:
|
|
@@ -707,7 +735,7 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
707
735
|
"""
|
|
708
736
|
md = Metadata()
|
|
709
737
|
img = super().getNBeforeLastImageMD(n, md)
|
|
710
|
-
return self.fixImage(img) if fix else img, md
|
|
738
|
+
return self.fixImage(img) if fix and pymmcore.BACKEND == "pymmcore" else img, md
|
|
711
739
|
|
|
712
740
|
def setConfig(self, groupName: str, configName: str) -> None:
|
|
713
741
|
"""Applies a configuration to a group.
|
|
@@ -1344,35 +1372,6 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
1344
1372
|
"""
|
|
1345
1373
|
return self.setPosition(val)
|
|
1346
1374
|
|
|
1347
|
-
@overload
|
|
1348
|
-
def setPosition(self, position: float) -> None: ...
|
|
1349
|
-
|
|
1350
|
-
@overload
|
|
1351
|
-
def setPosition(self, stageLabel: str, position: float) -> None: ...
|
|
1352
|
-
|
|
1353
|
-
@synchronized(_lock)
|
|
1354
|
-
def setPosition(self, *args: Any, **kwargs: Any) -> None:
|
|
1355
|
-
"""Set position of the stage in microns.
|
|
1356
|
-
|
|
1357
|
-
**Why Override?** To add a lock to prevent concurrent calls across threads.
|
|
1358
|
-
"""
|
|
1359
|
-
return super().setPosition(*args, **kwargs)
|
|
1360
|
-
|
|
1361
|
-
@overload
|
|
1362
|
-
def setXYPosition(self, x: float, y: float) -> None: ...
|
|
1363
|
-
|
|
1364
|
-
@overload
|
|
1365
|
-
def setXYPosition(self, xyStageLabel: str, x: float, y: float) -> None: ...
|
|
1366
|
-
|
|
1367
|
-
@synchronized(_lock)
|
|
1368
|
-
def setXYPosition(self, *args: Any, **kwargs: Any) -> None:
|
|
1369
|
-
"""Sets the position of the XY stage in microns.
|
|
1370
|
-
|
|
1371
|
-
**Why Override?** To add a lock to prevent concurrent calls across threads.
|
|
1372
|
-
"""
|
|
1373
|
-
return super().setXYPosition(*args, **kwargs)
|
|
1374
|
-
|
|
1375
|
-
@synchronized(_lock)
|
|
1376
1375
|
def getCameraChannelNames(self) -> tuple[str, ...]:
|
|
1377
1376
|
"""Convenience method to call `getCameraChannelName` for all camera channels.
|
|
1378
1377
|
|
|
@@ -1383,11 +1382,11 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
1383
1382
|
for i in range(self.getNumberOfCameraChannels())
|
|
1384
1383
|
)
|
|
1385
1384
|
|
|
1386
|
-
@synchronized(_lock)
|
|
1387
1385
|
def snapImage(self) -> None:
|
|
1388
1386
|
"""Acquires a single image with current settings.
|
|
1389
1387
|
|
|
1390
|
-
**Why Override?**
|
|
1388
|
+
**Why Override?** to emit the `imageSnapped` event after snapping an image.
|
|
1389
|
+
and to emit shutter property changes if `getAutoShutter` is `True`.
|
|
1391
1390
|
"""
|
|
1392
1391
|
if autoshutter := self.getAutoShutter():
|
|
1393
1392
|
self.events.propertyChanged.emit(self.getShutterDevice(), "State", True)
|
|
@@ -1502,9 +1501,9 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
1502
1501
|
"""
|
|
1503
1502
|
if ncomponents is None:
|
|
1504
1503
|
ncomponents = self.getNumberOfComponents()
|
|
1505
|
-
if ncomponents == 4:
|
|
1504
|
+
if ncomponents == 4 and img.ndim != 3:
|
|
1506
1505
|
new_shape = (*img.shape, 4)
|
|
1507
|
-
img = img.view(dtype=f"u{img.dtype.itemsize//4}").reshape(new_shape)
|
|
1506
|
+
img = img.view(dtype=f"u{img.dtype.itemsize // 4}").reshape(new_shape)
|
|
1508
1507
|
img = img[..., [2, 1, 0]] # Convert from BGRA to RGB
|
|
1509
1508
|
return img
|
|
1510
1509
|
|
|
@@ -1519,7 +1518,7 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
1519
1518
|
# best as I can tell, this is a hard-coded string in Utilities/MultiCamera.cpp
|
|
1520
1519
|
# (it also appears in ArduinoCounter.cpp). This appears to be "the way"
|
|
1521
1520
|
# to get at the original camera when using the multi-camera utility.
|
|
1522
|
-
prop_name = f"Physical Camera {channel_index+1}"
|
|
1521
|
+
prop_name = f"Physical Camera {channel_index + 1}"
|
|
1523
1522
|
if self.hasProperty(cam_dev, prop_name):
|
|
1524
1523
|
return self.getProperty(cam_dev, prop_name)
|
|
1525
1524
|
if channel_index > 0:
|
|
@@ -1669,7 +1668,7 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
1669
1668
|
if numChannel is not None
|
|
1670
1669
|
else super().getImage()
|
|
1671
1670
|
)
|
|
1672
|
-
return self.fixImage(img) if fix else img
|
|
1671
|
+
return self.fixImage(img) if fix and pymmcore.BACKEND == "pymmcore" else img
|
|
1673
1672
|
|
|
1674
1673
|
def startContinuousSequenceAcquisition(self, intervalMs: float = 0) -> None:
|
|
1675
1674
|
"""Start a ContinuousSequenceAcquisition.
|
|
@@ -1913,30 +1912,33 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
1913
1912
|
super().definePixelSizeConfig(*args, **kwargs)
|
|
1914
1913
|
self.events.pixelSizeChanged.emit(0.0)
|
|
1915
1914
|
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
) -> tuple[list[int], list[int], list[int], list[int]]:
|
|
1919
|
-
"""Get multiple ROIs from the current camera device.
|
|
1915
|
+
# pymmcore-SWIG needs this, but pymmcore-nano doesn't
|
|
1916
|
+
if hasattr(pymmcore, "UnsignedVector"):
|
|
1920
1917
|
|
|
1921
|
-
|
|
1922
|
-
|
|
1918
|
+
def getMultiROI( # type: ignore [override]
|
|
1919
|
+
self, *_: Any
|
|
1920
|
+
) -> tuple[list[int], list[int], list[int], list[int]]:
|
|
1921
|
+
"""Get multiple ROIs from the current camera device.
|
|
1923
1922
|
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
"""
|
|
1927
|
-
if _:
|
|
1928
|
-
warnings.warn( # pragma: no cover
|
|
1929
|
-
"Unlike pymmcore, CMMCorePlus.getMultiROI does not require arguments."
|
|
1930
|
-
"Arguments are ignored.",
|
|
1931
|
-
stacklevel=2,
|
|
1932
|
-
)
|
|
1923
|
+
Will fail if the camera does not support multiple ROIs. Will return empty
|
|
1924
|
+
vectors if multiple ROIs are not currently being used.
|
|
1933
1925
|
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1926
|
+
**Why Override?** So that the user doesn't need to pass in four empty
|
|
1927
|
+
pymmcore.UnsignedVector() objects.
|
|
1928
|
+
"""
|
|
1929
|
+
if _:
|
|
1930
|
+
warnings.warn( # pragma: no cover
|
|
1931
|
+
"Unlike pymmcore, CMMCorePlus.getMultiROI does not require "
|
|
1932
|
+
"arguments. Arguments are ignored.",
|
|
1933
|
+
stacklevel=2,
|
|
1934
|
+
)
|
|
1935
|
+
|
|
1936
|
+
xs = pymmcore.UnsignedVector() # type: ignore [attr-defined]
|
|
1937
|
+
ys = pymmcore.UnsignedVector() # type: ignore [attr-defined]
|
|
1938
|
+
ws = pymmcore.UnsignedVector() # type: ignore [attr-defined]
|
|
1939
|
+
hs = pymmcore.UnsignedVector() # type: ignore [attr-defined]
|
|
1940
|
+
super().getMultiROI(xs, ys, ws, hs)
|
|
1941
|
+
return list(xs), list(ys), list(ws), list(hs)
|
|
1940
1942
|
|
|
1941
1943
|
@overload
|
|
1942
1944
|
def setROI(self, x: int, y: int, width: int, height: int) -> None: ...
|
|
@@ -2000,7 +2002,12 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
2000
2002
|
with open(filename, "a") as f:
|
|
2001
2003
|
f.write("\n".join(cfg))
|
|
2002
2004
|
|
|
2003
|
-
def describe(
|
|
2005
|
+
def describe(
|
|
2006
|
+
self,
|
|
2007
|
+
sort: str | None = None,
|
|
2008
|
+
show_config_groups: bool = False,
|
|
2009
|
+
show_available: bool = False,
|
|
2010
|
+
) -> None:
|
|
2004
2011
|
"""Print information table with the current configuration.
|
|
2005
2012
|
|
|
2006
2013
|
Intended to provide a quick overview of the microscope configuration during
|
|
@@ -2032,8 +2039,50 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
2032
2039
|
|
|
2033
2040
|
print(f"{self.getVersionInfo()}, {self.getAPIVersionInfo()}")
|
|
2034
2041
|
print("Adapter path:", ",".join(self.getDeviceAdapterSearchPaths()))
|
|
2042
|
+
print("\nLoaded Devices:")
|
|
2035
2043
|
print_tabular_data(data, sort=sort)
|
|
2036
2044
|
|
|
2045
|
+
state = self.state(cached=False)
|
|
2046
|
+
if show_config_groups:
|
|
2047
|
+
group_data: defaultdict[str, list[str]] = defaultdict(list)
|
|
2048
|
+
groups = state["config_groups"]
|
|
2049
|
+
for group in groups:
|
|
2050
|
+
for pi, preset in enumerate(group["presets"]):
|
|
2051
|
+
for si, stng in enumerate(preset["settings"]):
|
|
2052
|
+
dev, prop, val = stng["dev"], stng["prop"], stng["val"]
|
|
2053
|
+
group_name = group["name"] if (pi == 0 and si == 0) else ""
|
|
2054
|
+
preset_name = preset["name"] if si == 0 else ""
|
|
2055
|
+
group_data["Group"].append(group_name)
|
|
2056
|
+
group_data["Preset"].append(preset_name)
|
|
2057
|
+
group_data["Device"].append(dev)
|
|
2058
|
+
group_data["Property"].append(prop)
|
|
2059
|
+
group_data["Value"].append(val)
|
|
2060
|
+
# add break between presets
|
|
2061
|
+
group_data["Group"].append("")
|
|
2062
|
+
group_data["Preset"].append("")
|
|
2063
|
+
group_data["Device"].append("")
|
|
2064
|
+
group_data["Property"].append("")
|
|
2065
|
+
group_data["Value"].append("")
|
|
2066
|
+
|
|
2067
|
+
print("\nConfig Groups:")
|
|
2068
|
+
print_tabular_data(group_data, sort=sort)
|
|
2069
|
+
|
|
2070
|
+
if show_available:
|
|
2071
|
+
avail_data: defaultdict[str, list[str]] = defaultdict(list)
|
|
2072
|
+
avail_adapters = self.getDeviceAdapterNames()
|
|
2073
|
+
for adapt in avail_adapters:
|
|
2074
|
+
with suppress(Exception):
|
|
2075
|
+
devices = self.getAvailableDevices(adapt)
|
|
2076
|
+
descriptions = self.getAvailableDeviceDescriptions(adapt)
|
|
2077
|
+
types = self.getAvailableDeviceTypes(adapt)
|
|
2078
|
+
for dev, desc, type_ in zip(devices, descriptions, types):
|
|
2079
|
+
avail_data["Library, DeviceName"].append(f"{adapt!r}, {dev!r}")
|
|
2080
|
+
avail_data["Type"].append(str(DeviceType(type_)))
|
|
2081
|
+
avail_data["Description"].append(desc)
|
|
2082
|
+
|
|
2083
|
+
print("\nAvailable Devices:")
|
|
2084
|
+
print_tabular_data(avail_data, sort=sort)
|
|
2085
|
+
|
|
2037
2086
|
def state(
|
|
2038
2087
|
self, *, cached: bool = True, include_time: bool = False, **_kwargs: Any
|
|
2039
2088
|
) -> SummaryMetaV1:
|
|
@@ -2089,7 +2138,7 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
2089
2138
|
self.events.propertyChanged.emit(device, properties[i], val)
|
|
2090
2139
|
|
|
2091
2140
|
@contextmanager
|
|
2092
|
-
def setContext(self, **kwargs:
|
|
2141
|
+
def setContext(self, **kwargs: Unpack[SetContextKwargs]) -> Iterator[None]:
|
|
2093
2142
|
"""Set core properties in a context restoring the initial values on exit.
|
|
2094
2143
|
|
|
2095
2144
|
:sparkles: *This method is new in `CMMCorePlus`.*
|
|
@@ -2098,9 +2147,12 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
2098
2147
|
----------
|
|
2099
2148
|
**kwargs : Any
|
|
2100
2149
|
Keyword arguments may be any `Name` for which `get<Name>` and `set<Name>`
|
|
2101
|
-
methods exist
|
|
2150
|
+
methods exist (where the first letter in `<Name>` may be either lower or
|
|
2151
|
+
upper case). For example, `setContext(exposure=10)` will call
|
|
2102
2152
|
`setExposure(10)` when entering the context and `setExposure(<initial>)`
|
|
2103
|
-
when exiting the context.
|
|
2153
|
+
when exiting the context. If the property is not found, a warning is logged
|
|
2154
|
+
and the property is skipped. If the value is a tuple, it is unpacked and
|
|
2155
|
+
passed to the `set<Name>` method (but lists are not unpacked).
|
|
2104
2156
|
|
|
2105
2157
|
Examples
|
|
2106
2158
|
--------
|
|
@@ -2120,11 +2172,16 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
2120
2172
|
try:
|
|
2121
2173
|
for name, v in kwargs.items():
|
|
2122
2174
|
name = name[0].upper() + name[1:]
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
getattr(self, f"set{name}")(v)
|
|
2126
|
-
except AttributeError:
|
|
2175
|
+
get_name, set_name = f"get{name}", f"set{name}"
|
|
2176
|
+
if not hasattr(self, get_name) or not hasattr(self, set_name):
|
|
2127
2177
|
logger.warning("%s is not a valid property, skipping.", name)
|
|
2178
|
+
continue
|
|
2179
|
+
|
|
2180
|
+
orig_values[name] = getattr(self, get_name)()
|
|
2181
|
+
if isinstance(v, tuple):
|
|
2182
|
+
getattr(self, set_name)(*v)
|
|
2183
|
+
else:
|
|
2184
|
+
getattr(self, set_name)(v)
|
|
2128
2185
|
yield
|
|
2129
2186
|
finally:
|
|
2130
2187
|
for k, v in orig_values.items():
|
|
@@ -2186,7 +2243,16 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
2186
2243
|
False
|
|
2187
2244
|
```
|
|
2188
2245
|
"""
|
|
2189
|
-
|
|
2246
|
+
warnings.warn(
|
|
2247
|
+
"canSequenceEvents is deprecated.\nPlease use "
|
|
2248
|
+
"`list(pymmcore_plus.core.iter_sequenced_events(core, [e1, e2]))` "
|
|
2249
|
+
"to see how this core will combine MDAEvents into SequencedEvents.",
|
|
2250
|
+
DeprecationWarning,
|
|
2251
|
+
stacklevel=2,
|
|
2252
|
+
)
|
|
2253
|
+
from ._sequencing import can_sequence_events
|
|
2254
|
+
|
|
2255
|
+
return can_sequence_events(self, e1, e2)
|
|
2190
2256
|
|
|
2191
2257
|
|
|
2192
2258
|
for name in (
|
pymmcore_plus/core/_property.py
CHANGED
|
@@ -3,9 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from functools import cached_property
|
|
4
4
|
from typing import TYPE_CHECKING, Any, TypedDict
|
|
5
5
|
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
from ._constants import DeviceType, PropertyType
|
|
6
|
+
from ._constants import DeviceType, Keyword, PropertyType
|
|
9
7
|
from .events._device_signal_view import _DevicePropValueSignal
|
|
10
8
|
|
|
11
9
|
if TYPE_CHECKING:
|
|
@@ -149,10 +147,10 @@ class DeviceProperty:
|
|
|
149
147
|
# https://github.com/micro-manager/mmCoreAndDevices/issues/172
|
|
150
148
|
allowed = self._mmc.getAllowedPropertyValues(self.device, self.name)
|
|
151
149
|
if not allowed and self.deviceType() is DeviceType.StateDevice:
|
|
152
|
-
if self.name ==
|
|
150
|
+
if self.name == Keyword.State:
|
|
153
151
|
n_states = self._mmc.getNumberOfStates(self.device)
|
|
154
152
|
allowed = tuple(str(i) for i in range(n_states))
|
|
155
|
-
elif self.name ==
|
|
153
|
+
elif self.name == Keyword.Label:
|
|
156
154
|
allowed = self._mmc.getStateLabels(self.device)
|
|
157
155
|
return allowed
|
|
158
156
|
|