pymmcore-plus 0.14.0__py3-none-any.whl → 0.15.2__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 +25 -0
- pymmcore_plus/_ipy_completion.py +363 -0
- pymmcore_plus/_pymmcore.py +4 -2
- pymmcore_plus/core/_constants.py +25 -3
- pymmcore_plus/core/_mmcore_plus.py +110 -55
- pymmcore_plus/core/_sequencing.py +1 -1
- pymmcore_plus/core/events/_deprecated.py +67 -0
- pymmcore_plus/core/events/_protocol.py +64 -39
- pymmcore_plus/core/events/_psygnal.py +35 -6
- pymmcore_plus/core/events/_qsignals.py +34 -6
- pymmcore_plus/experimental/unicore/__init__.py +12 -2
- pymmcore_plus/experimental/unicore/_device_manager.py +1 -1
- pymmcore_plus/experimental/unicore/_proxy.py +20 -3
- pymmcore_plus/experimental/unicore/core/__init__.py +0 -0
- pymmcore_plus/experimental/unicore/core/_sequence_buffer.py +314 -0
- pymmcore_plus/experimental/unicore/core/_unicore.py +1769 -0
- pymmcore_plus/experimental/unicore/devices/_camera.py +201 -0
- pymmcore_plus/experimental/unicore/devices/{_device.py → _device_base.py} +54 -28
- pymmcore_plus/experimental/unicore/devices/_generic_device.py +12 -0
- pymmcore_plus/experimental/unicore/devices/_properties.py +9 -2
- pymmcore_plus/experimental/unicore/devices/_shutter.py +30 -0
- pymmcore_plus/experimental/unicore/devices/_slm.py +82 -0
- pymmcore_plus/experimental/unicore/devices/_stage.py +1 -1
- pymmcore_plus/experimental/unicore/devices/_state.py +152 -0
- pymmcore_plus/mda/events/_protocol.py +8 -8
- {pymmcore_plus-0.14.0.dist-info → pymmcore_plus-0.15.2.dist-info}/METADATA +2 -2
- {pymmcore_plus-0.14.0.dist-info → pymmcore_plus-0.15.2.dist-info}/RECORD +30 -21
- pymmcore_plus/experimental/unicore/_unicore.py +0 -703
- {pymmcore_plus-0.14.0.dist-info → pymmcore_plus-0.15.2.dist-info}/WHEEL +0 -0
- {pymmcore_plus-0.14.0.dist-info → pymmcore_plus-0.15.2.dist-info}/entry_points.txt +0 -0
- {pymmcore_plus-0.14.0.dist-info → pymmcore_plus-0.15.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -13,9 +13,18 @@ from pathlib import Path
|
|
|
13
13
|
from re import Pattern
|
|
14
14
|
from textwrap import dedent
|
|
15
15
|
from threading import Thread
|
|
16
|
-
from typing import
|
|
16
|
+
from typing import (
|
|
17
|
+
TYPE_CHECKING,
|
|
18
|
+
Any,
|
|
19
|
+
Callable,
|
|
20
|
+
NamedTuple,
|
|
21
|
+
TypeVar,
|
|
22
|
+
cast,
|
|
23
|
+
overload,
|
|
24
|
+
)
|
|
17
25
|
|
|
18
26
|
from psygnal import SignalInstance
|
|
27
|
+
from typing_extensions import deprecated
|
|
19
28
|
|
|
20
29
|
import pymmcore_plus._pymmcore as pymmcore
|
|
21
30
|
from pymmcore_plus._logger import current_logfile, logger
|
|
@@ -42,7 +51,7 @@ from .events import CMMCoreSignaler, PCoreSignaler, _get_auto_core_callback_clas
|
|
|
42
51
|
|
|
43
52
|
if TYPE_CHECKING:
|
|
44
53
|
from collections.abc import Iterable, Iterator, Sequence
|
|
45
|
-
from typing import Literal, TypeAlias, TypedDict, Union, Unpack
|
|
54
|
+
from typing import Literal, Never, TypeAlias, TypedDict, Union, Unpack
|
|
46
55
|
|
|
47
56
|
import numpy as np
|
|
48
57
|
from pymmcore import DeviceLabel
|
|
@@ -274,7 +283,7 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
274
283
|
|
|
275
284
|
self._events = _get_auto_core_callback_class()()
|
|
276
285
|
self._callback_relay = MMCallbackRelay(self.events)
|
|
277
|
-
|
|
286
|
+
super().registerCallback(self._callback_relay)
|
|
278
287
|
|
|
279
288
|
self._mda_runner = MDARunner()
|
|
280
289
|
self._mda_runner.set_engine(MDAEngine(self))
|
|
@@ -287,6 +296,24 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
287
296
|
self._weak_clean = weakref.WeakMethod(self.unloadAllDevices)
|
|
288
297
|
atexit.register(self._weak_clean)
|
|
289
298
|
|
|
299
|
+
@deprecated(
|
|
300
|
+
"registerCallback is disallowed in pymmcore-plus. Use .events instead."
|
|
301
|
+
)
|
|
302
|
+
def registerCallback(self, *_: Never) -> Never: # type: ignore[override]
|
|
303
|
+
"""*registerCallback is disallowed in pymmcore-plus!*
|
|
304
|
+
|
|
305
|
+
If you want to connect callbacks to events, use the
|
|
306
|
+
[`CMMCorePlus.events`][pymmcore_plus.CMMCorePlus.events] property instead.
|
|
307
|
+
""" # noqa
|
|
308
|
+
raise RuntimeError(
|
|
309
|
+
dedent("""
|
|
310
|
+
This method is disallowed in pymmcore-plus.
|
|
311
|
+
|
|
312
|
+
If you want to connect callbacks to events, use the
|
|
313
|
+
`CMMCorePlus.events` property instead.
|
|
314
|
+
""")
|
|
315
|
+
)
|
|
316
|
+
|
|
290
317
|
@property
|
|
291
318
|
def events(self) -> PCoreSignaler:
|
|
292
319
|
"""Signaler for core events.
|
|
@@ -300,14 +327,20 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
300
327
|
return self._events
|
|
301
328
|
|
|
302
329
|
def __repr__(self) -> str:
|
|
303
|
-
|
|
330
|
+
"""Return a string representation of the core object."""
|
|
331
|
+
ndevices = len(self.getLoadedDevices()) - 1
|
|
332
|
+
return f"<{type(self).__name__} at {hex(id(self))} with {ndevices} devices>"
|
|
304
333
|
|
|
305
334
|
def __del__(self) -> None:
|
|
306
335
|
if hasattr(self, "_weak_clean"):
|
|
307
336
|
atexit.unregister(self._weak_clean)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
337
|
+
try:
|
|
338
|
+
super().registerCallback(None)
|
|
339
|
+
self.reset()
|
|
340
|
+
# clean up logging
|
|
341
|
+
self.setPrimaryLogFile("")
|
|
342
|
+
except Exception as e:
|
|
343
|
+
logger.exception("Error during CMMCorePlus.__del__(): %s", e)
|
|
311
344
|
|
|
312
345
|
# Re-implemented methods from the CMMCore API
|
|
313
346
|
|
|
@@ -691,9 +724,9 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
691
724
|
"""
|
|
692
725
|
md = Metadata()
|
|
693
726
|
if channel is not None and slice is not None:
|
|
694
|
-
img =
|
|
727
|
+
img = self.getLastImageMD(channel, slice, md)
|
|
695
728
|
else:
|
|
696
|
-
img =
|
|
729
|
+
img = self.getLastImageMD(md)
|
|
697
730
|
return (self.fixImage(img) if fix and not pymmcore.NANO else img, md)
|
|
698
731
|
|
|
699
732
|
@overload
|
|
@@ -735,7 +768,7 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
735
768
|
Image and metadata
|
|
736
769
|
"""
|
|
737
770
|
md = Metadata()
|
|
738
|
-
img =
|
|
771
|
+
img = self.popNextImageMD(channel, slice, md)
|
|
739
772
|
return (self.fixImage(img) if fix and not pymmcore.NANO else img, md)
|
|
740
773
|
|
|
741
774
|
def popNextImage(self, *, fix: bool = True) -> np.ndarray:
|
|
@@ -779,7 +812,7 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
779
812
|
will be reshaped to (w, h, n_components) using `fixImage`.
|
|
780
813
|
"""
|
|
781
814
|
md = Metadata()
|
|
782
|
-
img =
|
|
815
|
+
img = self.getNBeforeLastImageMD(n, md)
|
|
783
816
|
return self.fixImage(img) if fix and not pymmcore.NANO else img, md
|
|
784
817
|
|
|
785
818
|
def setConfig(self, groupName: str, configName: str) -> None:
|
|
@@ -1534,7 +1567,7 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
1534
1567
|
def setXYPosition(self, x: float, y: float, /) -> None: ...
|
|
1535
1568
|
@overload
|
|
1536
1569
|
def setXYPosition(self, xyStageLabel: str, x: float, y: float, /) -> None: ...
|
|
1537
|
-
def setXYPosition(self, *args:
|
|
1570
|
+
def setXYPosition(self, *args: Any) -> None:
|
|
1538
1571
|
"""Sets the position of the XY stage in microns.
|
|
1539
1572
|
|
|
1540
1573
|
**Why Override?** to store the last commanded stage position internally.
|
|
@@ -1543,10 +1576,10 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
1543
1576
|
label: str | None = None
|
|
1544
1577
|
x, y = cast("tuple[float, float]", args)
|
|
1545
1578
|
elif len(args) == 3:
|
|
1546
|
-
label, x, y = args
|
|
1579
|
+
label, x, y = args
|
|
1547
1580
|
else:
|
|
1548
1581
|
raise ValueError("Invalid number of arguments. Expected 2 or 3.")
|
|
1549
|
-
super().setXYPosition(*args)
|
|
1582
|
+
super().setXYPosition(*args)
|
|
1550
1583
|
self._last_xy_position[label] = (x, y)
|
|
1551
1584
|
|
|
1552
1585
|
def getZPosition(self) -> float:
|
|
@@ -1592,8 +1625,8 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
1592
1625
|
if autoshutter := self.getAutoShutter():
|
|
1593
1626
|
self.events.propertyChanged.emit(self.getShutterDevice(), "State", True)
|
|
1594
1627
|
try:
|
|
1595
|
-
|
|
1596
|
-
self.events.imageSnapped.emit()
|
|
1628
|
+
self._do_snap_image()
|
|
1629
|
+
self.events.imageSnapped.emit(self.getCameraDevice())
|
|
1597
1630
|
finally:
|
|
1598
1631
|
if autoshutter:
|
|
1599
1632
|
self.events.propertyChanged.emit(
|
|
@@ -1877,15 +1910,12 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
1877
1910
|
**Why Override?** To emit a `startContinuousSequenceAcquisition` event.
|
|
1878
1911
|
"""
|
|
1879
1912
|
self.events.continuousSequenceAcquisitionStarting.emit()
|
|
1880
|
-
|
|
1913
|
+
self._do_start_continuous_sequence_acquisition(intervalMs)
|
|
1881
1914
|
self.events.continuousSequenceAcquisitionStarted.emit()
|
|
1882
1915
|
|
|
1883
1916
|
@overload
|
|
1884
1917
|
def startSequenceAcquisition(
|
|
1885
|
-
self,
|
|
1886
|
-
numImages: int,
|
|
1887
|
-
intervalMs: float,
|
|
1888
|
-
stopOnOverflow: bool,
|
|
1918
|
+
self, numImages: int, intervalMs: float, stopOnOverflow: bool, /
|
|
1889
1919
|
) -> None: ...
|
|
1890
1920
|
|
|
1891
1921
|
@overload
|
|
@@ -1895,9 +1925,10 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
1895
1925
|
numImages: int,
|
|
1896
1926
|
intervalMs: float,
|
|
1897
1927
|
stopOnOverflow: bool,
|
|
1928
|
+
/,
|
|
1898
1929
|
) -> None: ...
|
|
1899
1930
|
|
|
1900
|
-
def startSequenceAcquisition(self, *args: Any
|
|
1931
|
+
def startSequenceAcquisition(self, *args: Any) -> None:
|
|
1901
1932
|
"""Starts streaming camera sequence acquisition.
|
|
1902
1933
|
|
|
1903
1934
|
This command does not block the calling thread for the duration of the
|
|
@@ -1906,18 +1937,16 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
1906
1937
|
**Why Override?** To emit a `startSequenceAcquisition` event.
|
|
1907
1938
|
"""
|
|
1908
1939
|
if len(args) == 3:
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1940
|
+
args = (self.getCameraDevice(), *args)
|
|
1941
|
+
elif len(args) != 4:
|
|
1942
|
+
raise ValueError(
|
|
1943
|
+
"startSequenceAcquisition requires either 3 or 4 arguments, "
|
|
1944
|
+
f"got {len(args)}."
|
|
1945
|
+
)
|
|
1913
1946
|
|
|
1914
|
-
self.events.sequenceAcquisitionStarting.emit(
|
|
1915
|
-
|
|
1916
|
-
)
|
|
1917
|
-
super().startSequenceAcquisition(*args, **kwargs)
|
|
1918
|
-
self.events.sequenceAcquisitionStarted.emit(
|
|
1919
|
-
cameraLabel, numImages, intervalMs, stopOnOverflow
|
|
1920
|
-
)
|
|
1947
|
+
self.events.sequenceAcquisitionStarting.emit(*args)
|
|
1948
|
+
self._do_start_sequence_acquisition(*args)
|
|
1949
|
+
self.events.sequenceAcquisitionStarted.emit(*args)
|
|
1921
1950
|
|
|
1922
1951
|
def stopSequenceAcquisition(self, cameraLabel: str | None = None) -> None:
|
|
1923
1952
|
"""Stops streaming camera sequence acquisition.
|
|
@@ -1926,13 +1955,32 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
1926
1955
|
|
|
1927
1956
|
**Why Override?** To emit a `stopSequenceAcquisition` event.
|
|
1928
1957
|
"""
|
|
1929
|
-
if cameraLabel is None:
|
|
1930
|
-
super().stopSequenceAcquisition()
|
|
1931
|
-
else:
|
|
1932
|
-
super().stopSequenceAcquisition(cameraLabel)
|
|
1933
1958
|
cameraLabel = cameraLabel or super().getCameraDevice()
|
|
1959
|
+
self._do_stop_sequence_acquisition(cameraLabel)
|
|
1934
1960
|
self.events.sequenceAcquisitionStopped.emit(cameraLabel)
|
|
1935
1961
|
|
|
1962
|
+
# here for ease of overriding in Unicore ---------------------
|
|
1963
|
+
|
|
1964
|
+
def _do_snap_image(self) -> None:
|
|
1965
|
+
super().snapImage()
|
|
1966
|
+
|
|
1967
|
+
def _do_start_sequence_acquisition(
|
|
1968
|
+
self, cameraLabel: str, numImages: int, intervalMs: float, stopOnOverflow: bool
|
|
1969
|
+
) -> None:
|
|
1970
|
+
super().startSequenceAcquisition(
|
|
1971
|
+
cameraLabel, numImages, intervalMs, stopOnOverflow
|
|
1972
|
+
)
|
|
1973
|
+
|
|
1974
|
+
def _do_start_continuous_sequence_acquisition(self, intervalMs: float) -> None:
|
|
1975
|
+
"""Starts the actual continuous sequence acquisition process."""
|
|
1976
|
+
super().startContinuousSequenceAcquisition(intervalMs)
|
|
1977
|
+
|
|
1978
|
+
def _do_stop_sequence_acquisition(self, cameraLabel: str) -> None:
|
|
1979
|
+
"""Stops the actual sequence acquisition process."""
|
|
1980
|
+
super().stopSequenceAcquisition(cameraLabel)
|
|
1981
|
+
|
|
1982
|
+
# end of Unicore helpers ---------------------
|
|
1983
|
+
|
|
1936
1984
|
def setAutoFocusOffset(self, offset: float) -> None:
|
|
1937
1985
|
"""Applies offset the one-shot focusing device.
|
|
1938
1986
|
|
|
@@ -1989,26 +2037,27 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
1989
2037
|
self.events.autoShutterSet.emit(state)
|
|
1990
2038
|
|
|
1991
2039
|
@overload
|
|
1992
|
-
def setShutterOpen(self, state: bool) -> None: ...
|
|
1993
|
-
|
|
2040
|
+
def setShutterOpen(self, state: bool, /) -> None: ...
|
|
1994
2041
|
@overload
|
|
1995
|
-
def setShutterOpen(self, shutterLabel: str, state: bool) -> None: ...
|
|
1996
|
-
|
|
1997
|
-
def setShutterOpen(self, *args: Any, **kwargs: Any) -> None:
|
|
2042
|
+
def setShutterOpen(self, shutterLabel: str, state: bool, /) -> None: ...
|
|
2043
|
+
def setShutterOpen(self, *args: Any) -> None:
|
|
1998
2044
|
"""Open or close the currently selected or `shutterLabel` shutter.
|
|
1999
2045
|
|
|
2000
2046
|
**Why Override?** To emit a `propertyChanged` event.
|
|
2001
2047
|
"""
|
|
2002
|
-
super().setShutterOpen(*args, **kwargs)
|
|
2003
|
-
shutterLabel, state = kwargs.get("shutterLabel"), kwargs.get("state")
|
|
2004
2048
|
if len(args) == 2:
|
|
2005
2049
|
shutterLabel, state = args
|
|
2006
2050
|
elif len(args) == 1:
|
|
2007
2051
|
shutterLabel = super().getShutterDevice()
|
|
2008
2052
|
state = args[0]
|
|
2053
|
+
self._do_shutter_open(shutterLabel, state)
|
|
2009
2054
|
state = str(int(bool(state)))
|
|
2010
2055
|
self.events.propertyChanged.emit(shutterLabel, "State", state)
|
|
2011
2056
|
|
|
2057
|
+
def _do_shutter_open(self, shutterLabel: str, state: bool, /) -> None:
|
|
2058
|
+
"""Open or close the shutter."""
|
|
2059
|
+
super().setShutterOpen(shutterLabel, state)
|
|
2060
|
+
|
|
2012
2061
|
@overload
|
|
2013
2062
|
def deleteConfig(self, groupName: str, configName: str) -> None: ...
|
|
2014
2063
|
|
|
@@ -2147,21 +2196,29 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
2147
2196
|
return list(xs), list(ys), list(ws), list(hs)
|
|
2148
2197
|
|
|
2149
2198
|
@overload
|
|
2150
|
-
def setROI(self, x: int, y: int, width: int, height: int) -> None: ...
|
|
2199
|
+
def setROI(self, x: int, y: int, width: int, height: int, /) -> None: ...
|
|
2151
2200
|
|
|
2152
2201
|
@overload
|
|
2153
|
-
def setROI(
|
|
2202
|
+
def setROI(
|
|
2203
|
+
self, label: str, x: int, y: int, width: int, height: int, /
|
|
2204
|
+
) -> None: ...
|
|
2154
2205
|
|
|
2155
|
-
def setROI(self, *args: Any
|
|
2206
|
+
def setROI(self, *args: Any) -> None:
|
|
2156
2207
|
"""Set the camera Region of Interest (ROI).
|
|
2157
2208
|
|
|
2158
2209
|
**Why Override?** To emit a `roiSet` event.
|
|
2159
2210
|
"""
|
|
2160
|
-
super().setROI(*args, **kwargs)
|
|
2161
2211
|
if len(args) == 4:
|
|
2162
2212
|
args = (super().getCameraDevice(), *args)
|
|
2213
|
+
self._do_set_roi(*args)
|
|
2163
2214
|
self.events.roiSet.emit(*args)
|
|
2164
2215
|
|
|
2216
|
+
# here for ease of overriding in Unicore
|
|
2217
|
+
|
|
2218
|
+
def _do_set_roi(self, label: str, x: int, y: int, width: int, height: int) -> None:
|
|
2219
|
+
"""Internal method to set the ROI for a specific camera device."""
|
|
2220
|
+
super().setROI(label, x, y, width, height)
|
|
2221
|
+
|
|
2165
2222
|
def setChannelGroup(self, channelGroup: str) -> None:
|
|
2166
2223
|
"""Specifies the group determining the channel selection.
|
|
2167
2224
|
|
|
@@ -2328,8 +2385,9 @@ class CMMCorePlus(pymmcore.CMMCore):
|
|
|
2328
2385
|
try:
|
|
2329
2386
|
before = [self.getProperty(device, p) for p in properties]
|
|
2330
2387
|
except Exception as e:
|
|
2331
|
-
logger.
|
|
2332
|
-
"Error getting properties %s on %s: %s.
|
|
2388
|
+
logger.warning(
|
|
2389
|
+
"Error getting properties %s on %s: %s. "
|
|
2390
|
+
"Cannot ensure propertyChanged signal emission",
|
|
2333
2391
|
properties,
|
|
2334
2392
|
device,
|
|
2335
2393
|
e,
|
|
@@ -2506,12 +2564,9 @@ class _MMCallbackRelay(pymmcore.MMEventCallback):
|
|
|
2506
2564
|
return reemit
|
|
2507
2565
|
|
|
2508
2566
|
|
|
2567
|
+
MMCORE_SIGNAL_NAMES = {n for n in dir(pymmcore.MMEventCallback) if n.startswith("on")}
|
|
2509
2568
|
MMCallbackRelay = type(
|
|
2510
2569
|
"MMCallbackRelay",
|
|
2511
2570
|
(_MMCallbackRelay,),
|
|
2512
|
-
{
|
|
2513
|
-
n: _MMCallbackRelay.make_reemitter(n)
|
|
2514
|
-
for n in dir(pymmcore.MMEventCallback)
|
|
2515
|
-
if n.startswith("on")
|
|
2516
|
-
},
|
|
2571
|
+
{n: _MMCallbackRelay.make_reemitter(n) for n in MMCORE_SIGNAL_NAMES},
|
|
2517
2572
|
)
|
|
@@ -66,7 +66,7 @@ class SequencedEvent(MDAEvent):
|
|
|
66
66
|
slm_sequence: tuple[bytes, ...] = Field(default_factory=tuple)
|
|
67
67
|
|
|
68
68
|
# re-defining this from MDAEvent to circumvent a strange issue with pydantic 2.11
|
|
69
|
-
sequence: Optional[MDASequence] = Field(default=None, repr=False) # noqa:
|
|
69
|
+
sequence: Optional[MDASequence] = Field(default=None, repr=False) # noqa: UP045
|
|
70
70
|
|
|
71
71
|
# all other property sequences
|
|
72
72
|
property_sequences: dict[tuple[str, str], list[str]] = Field(default_factory=dict)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import warnings
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ._protocol import PSignalInstance
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DeprecatedSignalProxy:
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
signal: PSignalInstance,
|
|
15
|
+
current_n_args: int,
|
|
16
|
+
deprecated_posargs: tuple[Any, ...],
|
|
17
|
+
) -> None:
|
|
18
|
+
self._signal = signal
|
|
19
|
+
self._current_n_args = current_n_args
|
|
20
|
+
self._deprecated_posargs = deprecated_posargs
|
|
21
|
+
self._shims: dict[Callable, Callable] = {}
|
|
22
|
+
|
|
23
|
+
def connect(self, slot: Callable) -> Any:
|
|
24
|
+
"""Connect slot to this signal."""
|
|
25
|
+
min_pos_args = sum(
|
|
26
|
+
1
|
|
27
|
+
for p in inspect.signature(slot).parameters.values()
|
|
28
|
+
if p.kind
|
|
29
|
+
in {
|
|
30
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
31
|
+
inspect.Parameter.POSITIONAL_ONLY,
|
|
32
|
+
}
|
|
33
|
+
and p.default is inspect.Parameter.empty
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
num_extra_pos_args = min_pos_args - self._current_n_args
|
|
37
|
+
if num_extra_pos_args > 0:
|
|
38
|
+
extra_args = self._deprecated_posargs[:num_extra_pos_args]
|
|
39
|
+
warnings.warn(
|
|
40
|
+
f"Callback {slot.__name__!r} requires {min_pos_args} positional "
|
|
41
|
+
f"arguments, but this signal only supports {self._current_n_args}. "
|
|
42
|
+
"Fake arguments will be added, but this will be an exception in the "
|
|
43
|
+
"future. Please update your callback.",
|
|
44
|
+
FutureWarning,
|
|
45
|
+
stacklevel=2,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def _shim(*args: Any) -> Any:
|
|
49
|
+
slot(*args[: self._current_n_args], *extra_args)
|
|
50
|
+
|
|
51
|
+
self._shims[slot] = _shim
|
|
52
|
+
self._signal.connect(_shim)
|
|
53
|
+
else:
|
|
54
|
+
self._signal.connect(slot)
|
|
55
|
+
|
|
56
|
+
def disconnect(self, slot: Callable | None = None) -> Any:
|
|
57
|
+
"""Disconnect slot from this signal.
|
|
58
|
+
|
|
59
|
+
If `None`, all slots should be disconnected.
|
|
60
|
+
"""
|
|
61
|
+
if slot in self._shims:
|
|
62
|
+
slot = self._shims.pop(slot)
|
|
63
|
+
return self._signal.disconnect(slot)
|
|
64
|
+
|
|
65
|
+
def emit(self, *args: Any) -> Any:
|
|
66
|
+
"""Emits the signal with the given arguments."""
|
|
67
|
+
return self._signal.emit(*args[: self._current_n_args])
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import (
|
|
4
|
+
Any,
|
|
5
|
+
Callable,
|
|
6
|
+
ClassVar,
|
|
7
|
+
Protocol,
|
|
8
|
+
overload,
|
|
9
|
+
runtime_checkable,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from typing_extensions import Self
|
|
2
13
|
|
|
3
14
|
|
|
4
15
|
@runtime_checkable
|
|
@@ -12,7 +23,7 @@ class PSignalInstance(Protocol):
|
|
|
12
23
|
def connect(self, slot: Callable) -> Any:
|
|
13
24
|
"""Connect slot to this signal."""
|
|
14
25
|
|
|
15
|
-
def disconnect(self, slot:
|
|
26
|
+
def disconnect(self, slot: Callable | None = None) -> Any:
|
|
16
27
|
"""Disconnect slot from this signal.
|
|
17
28
|
|
|
18
29
|
If `None`, all slots should be disconnected.
|
|
@@ -23,14 +34,18 @@ class PSignalInstance(Protocol):
|
|
|
23
34
|
|
|
24
35
|
|
|
25
36
|
@runtime_checkable
|
|
26
|
-
class
|
|
37
|
+
class PSignal(Protocol):
|
|
27
38
|
"""Descriptor that returns a signal instance."""
|
|
28
39
|
|
|
29
|
-
|
|
40
|
+
@overload
|
|
41
|
+
def __get__(self, instance: None, owner: type, /) -> Self: ...
|
|
42
|
+
@overload
|
|
43
|
+
def __get__(self, instance: Any, owner: type | None = None, /) -> Any: ...
|
|
44
|
+
def __get__(
|
|
45
|
+
self, instance: Any, owner: type | None = ..., /
|
|
46
|
+
) -> PSignalInstance | PSignal:
|
|
30
47
|
"""Returns the signal instance for this descriptor."""
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
PSignal = Union[PSignalDescriptor, PSignalInstance]
|
|
48
|
+
...
|
|
34
49
|
|
|
35
50
|
|
|
36
51
|
@runtime_checkable
|
|
@@ -91,102 +106,112 @@ class PCoreSignaler(Protocol):
|
|
|
91
106
|
"""
|
|
92
107
|
|
|
93
108
|
# native MMCore callback events
|
|
94
|
-
propertiesChanged: PSignal
|
|
109
|
+
propertiesChanged: ClassVar[PSignal]
|
|
95
110
|
"""Emits with no arguments when properties have changed."""
|
|
96
|
-
propertyChanged: PSignal
|
|
97
|
-
"""Emits `(name: str,
|
|
98
|
-
channelGroupChanged: PSignal
|
|
111
|
+
propertyChanged: ClassVar[PSignal]
|
|
112
|
+
"""Emits `(name: str, propName: str, propValue: str)` when a specific property has changed.""" # noqa: E501
|
|
113
|
+
channelGroupChanged: ClassVar[PSignal]
|
|
99
114
|
"""Emits `(newChannelGroupName: str)` when a channel group has changed."""
|
|
100
|
-
configGroupChanged: PSignal
|
|
115
|
+
configGroupChanged: ClassVar[PSignal]
|
|
101
116
|
"""Emits `(groupName: str, newConfigName: str)` when a config group has changed."""
|
|
102
|
-
systemConfigurationLoaded: PSignal
|
|
117
|
+
systemConfigurationLoaded: ClassVar[PSignal]
|
|
103
118
|
"""Emits with no arguments when the system configuration has been loaded."""
|
|
104
|
-
pixelSizeChanged: PSignal
|
|
119
|
+
pixelSizeChanged: ClassVar[PSignal]
|
|
105
120
|
"""Emits `(newPixelSizeUm: float)` when the pixel size has changed."""
|
|
106
|
-
pixelSizeAffineChanged: PSignal
|
|
121
|
+
pixelSizeAffineChanged: ClassVar[PSignal]
|
|
107
122
|
"""Emits `(float, float, float, float, float, float)` when the pixel size affine has changed.""" # noqa: E501
|
|
108
|
-
stagePositionChanged: PSignal
|
|
123
|
+
stagePositionChanged: ClassVar[PSignal]
|
|
109
124
|
"""Emits `(name: str, pos: float)` when a stage position has changed."""
|
|
110
|
-
XYStagePositionChanged: PSignal
|
|
125
|
+
XYStagePositionChanged: ClassVar[PSignal]
|
|
111
126
|
"""Emits `(name: str, xpos: float, ypos: float)` when an XY stage position has changed.""" # noqa: E501
|
|
112
|
-
xYStagePositionChanged: PSignal # alias
|
|
113
|
-
exposureChanged: PSignal
|
|
127
|
+
xYStagePositionChanged: ClassVar[PSignal] # alias
|
|
128
|
+
exposureChanged: ClassVar[PSignal]
|
|
114
129
|
"""Emits `(name: str, newExposure: float)` when an exposure has changed."""
|
|
115
|
-
SLMExposureChanged: PSignal
|
|
130
|
+
SLMExposureChanged: ClassVar[PSignal]
|
|
116
131
|
"""Emits `(name: str, newExposure: float)` when the exposure of the SLM device changes.""" # noqa: E501
|
|
117
|
-
sLMExposureChanged: PSignal # alias
|
|
132
|
+
sLMExposureChanged: ClassVar[PSignal] # alias
|
|
118
133
|
|
|
119
134
|
# added for CMMCorePlus
|
|
120
|
-
configSet: PSignal
|
|
135
|
+
configSet: ClassVar[PSignal]
|
|
121
136
|
"""Emits `(str, str)` when a config has been set.
|
|
122
137
|
|
|
123
138
|
> :sparkles: This signal is unique to `pymmcore-plus`.
|
|
124
139
|
"""
|
|
125
|
-
imageSnapped: PSignal
|
|
140
|
+
imageSnapped: ClassVar[PSignal]
|
|
126
141
|
"""Emits with no arguments whenever snap is called.
|
|
127
142
|
|
|
128
143
|
> :sparkles: This signal is unique to `pymmcore-plus`.
|
|
129
144
|
"""
|
|
130
|
-
mdaEngineRegistered: PSignal
|
|
145
|
+
mdaEngineRegistered: ClassVar[PSignal]
|
|
131
146
|
"""Emits `(MDAEngine, MDAEngine)` when an MDAEngine is registered.
|
|
132
147
|
|
|
133
148
|
> :sparkles: This signal is unique to `pymmcore-plus`.
|
|
134
149
|
"""
|
|
135
150
|
|
|
136
|
-
continuousSequenceAcquisitionStarting: PSignal
|
|
151
|
+
continuousSequenceAcquisitionStarting: ClassVar[PSignal]
|
|
137
152
|
"""Emits with no arguments *before* continuous sequence acquisition is started.
|
|
138
153
|
|
|
139
154
|
> :sparkles: This signal is unique to `pymmcore-plus`.
|
|
140
155
|
"""
|
|
141
|
-
continuousSequenceAcquisitionStarted: PSignal
|
|
156
|
+
continuousSequenceAcquisitionStarted: ClassVar[PSignal]
|
|
142
157
|
"""Emits with no arguments *after* continuous sequence acquisition has started.
|
|
143
158
|
|
|
144
159
|
> :sparkles: This signal is unique to `pymmcore-plus`.
|
|
145
160
|
"""
|
|
146
|
-
sequenceAcquisitionStarting: PSignal
|
|
147
|
-
"""Emits `(str,
|
|
161
|
+
sequenceAcquisitionStarting: ClassVar[PSignal]
|
|
162
|
+
"""Emits `(str,)` *before* sequence acquisition is started.
|
|
163
|
+
|
|
164
|
+
!!! critical "Breaking Change"
|
|
165
|
+
In `pymmcore-plus` version 0.16.0, the `sequenceAcquisitionStarting` signal
|
|
166
|
+
was *reduced* from `(str, int, float, bool)` to `(str,)` to match the signature
|
|
167
|
+
emitted by the C++ MMCore library.
|
|
148
168
|
|
|
149
|
-
(cameraLabel,
|
|
169
|
+
`(cameraLabel,)`
|
|
150
170
|
> :sparkles: This signal is unique to `pymmcore-plus`.
|
|
151
171
|
"""
|
|
152
|
-
sequenceAcquisitionStarted: PSignal
|
|
153
|
-
"""Emits `(str
|
|
172
|
+
sequenceAcquisitionStarted: ClassVar[PSignal]
|
|
173
|
+
"""Emits `(str)` *after* sequence acquisition has started.
|
|
174
|
+
|
|
175
|
+
!!! critical "Breaking Change"
|
|
176
|
+
In `pymmcore-plus` version 0.16.0, the `sequenceAcquisitionStarted` signal
|
|
177
|
+
was *reduced* from `(str, int, float, bool)` to `(str,)` to match the signature
|
|
178
|
+
emitted by the C++ MMCore library.
|
|
154
179
|
|
|
155
|
-
(cameraLabel,
|
|
180
|
+
(cameraLabel,)
|
|
156
181
|
> :sparkles: This signal is unique to `pymmcore-plus`.
|
|
157
182
|
"""
|
|
158
|
-
sequenceAcquisitionStopped: PSignal
|
|
183
|
+
sequenceAcquisitionStopped: ClassVar[PSignal]
|
|
159
184
|
"""Emits `(str)` when sequence acquisition is stopped.
|
|
160
185
|
|
|
161
186
|
> :sparkles: This signal is unique to `pymmcore-plus`.
|
|
162
187
|
"""
|
|
163
|
-
autoShutterSet: PSignal
|
|
188
|
+
autoShutterSet: ClassVar[PSignal]
|
|
164
189
|
"""Emits `(bool)` when the auto shutter setting is changed.
|
|
165
190
|
|
|
166
191
|
"""
|
|
167
|
-
configGroupDeleted: PSignal
|
|
192
|
+
configGroupDeleted: ClassVar[PSignal]
|
|
168
193
|
"""Emits `(str)` when a config group is deleted.
|
|
169
194
|
|
|
170
195
|
> :sparkles: This signal is unique to `pymmcore-plus`.
|
|
171
196
|
"""
|
|
172
|
-
configDeleted: PSignal
|
|
197
|
+
configDeleted: ClassVar[PSignal]
|
|
173
198
|
"""Emits `(str, str)` when a config is deleted.
|
|
174
199
|
|
|
175
200
|
> :sparkles: This signal is unique to `pymmcore-plus`.
|
|
176
201
|
"""
|
|
177
|
-
configDefined: PSignal
|
|
202
|
+
configDefined: ClassVar[PSignal]
|
|
178
203
|
"""Emits `(str, str, str, str, str)` when a config is defined.
|
|
179
204
|
|
|
180
205
|
> :sparkles: This signal is unique to `pymmcore-plus`.
|
|
181
206
|
"""
|
|
182
|
-
roiSet: PSignal
|
|
207
|
+
roiSet: ClassVar[PSignal]
|
|
183
208
|
"""Emits `(str, int, int, int, int)` when an ROI is set.
|
|
184
209
|
|
|
185
210
|
> :sparkles: This signal is unique to `pymmcore-plus`.
|
|
186
211
|
"""
|
|
187
212
|
|
|
188
213
|
def devicePropertyChanged(
|
|
189
|
-
self, device: str, property:
|
|
214
|
+
self, device: str, property: str | None = None
|
|
190
215
|
) -> PSignalInstance:
|
|
191
216
|
"""Return object to connect/disconnect to device/property-specific changes.
|
|
192
217
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
1
3
|
from psygnal import Signal, SignalGroup, SignalInstance
|
|
2
4
|
|
|
3
5
|
from pymmcore_plus.mda import MDAEngine
|
|
4
6
|
|
|
7
|
+
from ._deprecated import DeprecatedSignalProxy
|
|
5
8
|
from ._prop_event_mixin import _DevicePropertyEventMixin
|
|
6
9
|
|
|
7
10
|
|
|
@@ -22,14 +25,19 @@ class CMMCoreSignaler(SignalGroup, _DevicePropertyEventMixin):
|
|
|
22
25
|
exposureChanged = Signal(str, float)
|
|
23
26
|
SLMExposureChanged = Signal(str, float)
|
|
24
27
|
|
|
28
|
+
# https://github.com/micro-manager/mmCoreAndDevices/pull/659
|
|
29
|
+
imageSnapped = Signal(str) # on snapImage()
|
|
30
|
+
# when (Continuous)SequenceAcquisition is stopped
|
|
31
|
+
sequenceAcquisitionStopped = Signal(str)
|
|
32
|
+
if TYPE_CHECKING: # see deprecated impl below
|
|
33
|
+
sequenceAcquisitionStarted = Signal(str)
|
|
34
|
+
|
|
25
35
|
# added for CMMCorePlus
|
|
26
|
-
imageSnapped = Signal() # whenever snapImage is called
|
|
27
36
|
mdaEngineRegistered = Signal(MDAEngine, MDAEngine)
|
|
28
37
|
continuousSequenceAcquisitionStarting = Signal()
|
|
29
38
|
continuousSequenceAcquisitionStarted = Signal()
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
sequenceAcquisitionStopped = Signal(str)
|
|
39
|
+
if TYPE_CHECKING:
|
|
40
|
+
sequenceAcquisitionStarting = Signal(str)
|
|
33
41
|
autoShutterSet = Signal(bool)
|
|
34
42
|
configGroupDeleted = Signal(str)
|
|
35
43
|
configDeleted = Signal(str, str)
|
|
@@ -38,9 +46,30 @@ class CMMCoreSignaler(SignalGroup, _DevicePropertyEventMixin):
|
|
|
38
46
|
|
|
39
47
|
# aliases for lower casing
|
|
40
48
|
@property
|
|
41
|
-
def xYStagePositionChanged(self) -> SignalInstance:
|
|
49
|
+
def xYStagePositionChanged(self) -> SignalInstance:
|
|
42
50
|
return self.XYStagePositionChanged
|
|
43
51
|
|
|
44
52
|
@property
|
|
45
|
-
def sLMExposureChanged(self) -> SignalInstance:
|
|
53
|
+
def sLMExposureChanged(self) -> SignalInstance:
|
|
46
54
|
return self.SLMExposureChanged
|
|
55
|
+
|
|
56
|
+
if not TYPE_CHECKING:
|
|
57
|
+
_sequenceAcquisitionStarting = Signal(str)
|
|
58
|
+
_sequenceAcquisitionStarted = Signal(str)
|
|
59
|
+
|
|
60
|
+
# Deprecated signal wrappers for backwards compatibility
|
|
61
|
+
@property
|
|
62
|
+
def sequenceAcquisitionStarting(self) -> SignalInstance:
|
|
63
|
+
return DeprecatedSignalProxy(
|
|
64
|
+
self._sequenceAcquisitionStarting,
|
|
65
|
+
current_n_args=1,
|
|
66
|
+
deprecated_posargs=(-1, 0, False),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def sequenceAcquisitionStarted(self) -> SignalInstance:
|
|
71
|
+
return DeprecatedSignalProxy(
|
|
72
|
+
self._sequenceAcquisitionStarted,
|
|
73
|
+
current_n_args=1,
|
|
74
|
+
deprecated_posargs=(-1, 0, False),
|
|
75
|
+
)
|