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
pymmcore_plus/__init__.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""pymmcore superset providing improved APIs, event handling, and a pure python acquisition engine.""" # noqa: E501
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
from importlib.metadata import PackageNotFoundError, version
|
|
4
5
|
|
|
5
6
|
try:
|
|
@@ -12,6 +13,7 @@ from ._accumulator import AbstractChangeAccumulator
|
|
|
12
13
|
from ._logger import configure_logging
|
|
13
14
|
from ._util import find_micromanager, use_micromanager
|
|
14
15
|
from .core import (
|
|
16
|
+
ActionType,
|
|
15
17
|
CFGCommand,
|
|
16
18
|
CFGGroup,
|
|
17
19
|
CMMCorePlus,
|
|
@@ -63,3 +65,26 @@ __all__ = [
|
|
|
63
65
|
"find_micromanager",
|
|
64
66
|
"use_micromanager",
|
|
65
67
|
]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _install_ipy_completer() -> None: # pragma: no cover
|
|
71
|
+
import os
|
|
72
|
+
import sys
|
|
73
|
+
|
|
74
|
+
if os.getenv("PYMM_DISABLE_IPYTHON_COMPLETIONS", "0") == "1":
|
|
75
|
+
return
|
|
76
|
+
try:
|
|
77
|
+
if (IPython := sys.modules.get("IPython")) and (shell := IPython.get_ipython()):
|
|
78
|
+
from ._ipy_completion import install_pymmcore_ipy_completion
|
|
79
|
+
|
|
80
|
+
install_pymmcore_ipy_completion(shell)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
# If we fail to install the completer, we don't want to crash the import.
|
|
83
|
+
# This is a best-effort installation.
|
|
84
|
+
logging.warning(
|
|
85
|
+
f"Failed to install pymmcore-plus IPython completer:\n {e}",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
_install_ipy_completer()
|
|
90
|
+
del _install_ipy_completer
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import TYPE_CHECKING, Callable
|
|
5
|
+
|
|
6
|
+
from IPython import get_ipython # pyright: ignore
|
|
7
|
+
from IPython.core.completer import SimpleCompletion, context_matcher
|
|
8
|
+
|
|
9
|
+
from pymmcore_plus import CMMCorePlus, DeviceType
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
|
|
14
|
+
from IPython.core.completer import (
|
|
15
|
+
CompletionContext,
|
|
16
|
+
SimpleMatcherResult,
|
|
17
|
+
)
|
|
18
|
+
from IPython.core.interactiveshell import InteractiveShell
|
|
19
|
+
|
|
20
|
+
# functions that only require the CMMCorePlus instance to return completions
|
|
21
|
+
CoreCompleter = Callable[[CMMCorePlus], Sequence[SimpleCompletion]]
|
|
22
|
+
# functions that require the CMMCorePlus instance and a label to return completions
|
|
23
|
+
CoreLabelCompleter = Callable[[CMMCorePlus, str], Sequence[SimpleCompletion]]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _get_argument_index(src: str, ns: dict[str, object]) -> int:
|
|
27
|
+
"""Parse argument index from method call string.
|
|
28
|
+
|
|
29
|
+
Uses a simple comma-counting approach that works reliably across
|
|
30
|
+
different backends (pymmcore, pymmcore-nano, etc.) without relying
|
|
31
|
+
on jedi's ability to understand method signatures.
|
|
32
|
+
"""
|
|
33
|
+
p0 = src.rfind("(") + 1
|
|
34
|
+
p1 = src.rfind(")") if ")" in src else None
|
|
35
|
+
if inner := src[slice(p0, p1)].strip():
|
|
36
|
+
# split on commas that are not inside quotes
|
|
37
|
+
return len(inner.split(",")) - 1 # 0-based
|
|
38
|
+
return 0
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# matches "obj.attr(" ... note trailing paren
|
|
42
|
+
OBJ_METHOD_RE = re.compile(r"(?P<obj>[A-Za-z_][\w\.]*)\s*\.\s*(?P<attr>\w+)\s*\(")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _null() -> SimpleMatcherResult:
|
|
46
|
+
return {"completions": [], "matched_fragment": "", "suppress": False}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _dev_labels(
|
|
50
|
+
core: CMMCorePlus, *dev_types: DeviceType, with_null: bool = False
|
|
51
|
+
) -> Sequence[SimpleCompletion]:
|
|
52
|
+
try:
|
|
53
|
+
if not dev_types:
|
|
54
|
+
labels = list(core.getLoadedDevices())
|
|
55
|
+
else:
|
|
56
|
+
labels = [
|
|
57
|
+
d
|
|
58
|
+
for dev_type in dev_types
|
|
59
|
+
for d in core.getLoadedDevicesOfType(dev_type)
|
|
60
|
+
]
|
|
61
|
+
completions = [SimpleCompletion(f'"{lbl}"') for lbl in labels]
|
|
62
|
+
if with_null:
|
|
63
|
+
completions.append(SimpleCompletion("''"))
|
|
64
|
+
return completions
|
|
65
|
+
except Exception: # pragma: no cover
|
|
66
|
+
return []
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _config_group_names(core: CMMCorePlus) -> Sequence[SimpleCompletion]:
|
|
70
|
+
"""Get the names of all configuration groups."""
|
|
71
|
+
try:
|
|
72
|
+
return [
|
|
73
|
+
SimpleCompletion(f'"{name}"') for name in core.getAvailableConfigGroups()
|
|
74
|
+
]
|
|
75
|
+
except Exception: # pragma: no cover
|
|
76
|
+
return []
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _config_preset_names(core: CMMCorePlus, group: str) -> Sequence[SimpleCompletion]:
|
|
80
|
+
"""Get the names of all configuration presets for a given group."""
|
|
81
|
+
try:
|
|
82
|
+
return [
|
|
83
|
+
SimpleCompletion(f'"{name}"') for name in core.getAvailableConfigs(group)
|
|
84
|
+
]
|
|
85
|
+
except Exception: # pragma: no cover
|
|
86
|
+
return []
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# fmt: off
|
|
90
|
+
# map of (method_name, arg_index) -> function that returns possible completions
|
|
91
|
+
CORE_COMPLETERS: dict[tuple[str, int], CoreCompleter] = {
|
|
92
|
+
# ROLES -----------------------------------------------------------------------
|
|
93
|
+
("setAutoFocusDevice", 0): lambda core: _dev_labels(core, DeviceType.AutoFocus, with_null=True), # noqa
|
|
94
|
+
("setCameraDevice", 0): lambda core: _dev_labels(core, DeviceType.Camera, with_null=True), # noqa
|
|
95
|
+
("setFocusDevice", 0): lambda core: _dev_labels(core, DeviceType.Stage, with_null=True), # noqa
|
|
96
|
+
("setGalvoDevice", 0): lambda core: _dev_labels(core, DeviceType.Galvo, with_null=True), # noqa
|
|
97
|
+
("setImageProcessorDevice", 0): lambda core: _dev_labels(core, DeviceType.ImageProcessor, with_null=True), # noqa
|
|
98
|
+
("setShutterDevice", 0): lambda core: _dev_labels(core, DeviceType.Shutter, with_null=True), # noqa
|
|
99
|
+
("setSLMDevice", 0): lambda core: _dev_labels(core, DeviceType.SLM, with_null=True),
|
|
100
|
+
("setXYStageDevice", 0): lambda core: _dev_labels(core, DeviceType.XYStage, with_null=True), # noqa
|
|
101
|
+
# -----------------------
|
|
102
|
+
("getFocusDirection", 0): lambda core: _dev_labels(core, DeviceType.Stage),
|
|
103
|
+
("getPosition", 0): lambda core: _dev_labels(core, DeviceType.Stage),
|
|
104
|
+
("getStageSequenceMaxLength", 0): lambda core: _dev_labels(core, DeviceType.Stage),
|
|
105
|
+
("home", 0): lambda core: _dev_labels(core, DeviceType.Stage, DeviceType.XYStage),
|
|
106
|
+
("isContinuousFocusDrive", 0): lambda core: _dev_labels(core, DeviceType.Stage),
|
|
107
|
+
("isStageLinearSequenceable", 0): lambda core: _dev_labels(core, DeviceType.Stage),
|
|
108
|
+
("isStageSequenceable", 0): lambda core: _dev_labels(core, DeviceType.Stage),
|
|
109
|
+
("loadStageSequence", 0): lambda core: _dev_labels(core, DeviceType.Stage),
|
|
110
|
+
("setAdapterOrigin", 0): lambda core: _dev_labels(core, DeviceType.Stage),
|
|
111
|
+
("setFocusDirection", 0): lambda core: _dev_labels(core, DeviceType.Stage),
|
|
112
|
+
("setOrigin", 0): lambda core: _dev_labels(core, DeviceType.Stage),
|
|
113
|
+
("setPosition", 0): lambda core: _dev_labels(core, DeviceType.Stage),
|
|
114
|
+
("setRelativePosition", 0): lambda core: _dev_labels(core, DeviceType.Stage),
|
|
115
|
+
("setStageLinearSequence", 0): lambda core: _dev_labels(core, DeviceType.Stage),
|
|
116
|
+
("startStageSequence", 0): lambda core: _dev_labels(core, DeviceType.Stage),
|
|
117
|
+
("stop", 0): lambda core: _dev_labels(core, DeviceType.Stage, DeviceType.XYStage),
|
|
118
|
+
("stopStageSequence", 0): lambda core: _dev_labels(core, DeviceType.Stage),
|
|
119
|
+
# --------------------
|
|
120
|
+
("getXPosition", 0): lambda core: _dev_labels(core, DeviceType.XYStage),
|
|
121
|
+
("getXYPosition", 0): lambda core: _dev_labels(core, DeviceType.XYStage),
|
|
122
|
+
("getXYStageSequenceMaxLength", 0): lambda core: _dev_labels(core, DeviceType.XYStage), # noqa
|
|
123
|
+
("getYPosition", 0): lambda core: _dev_labels(core, DeviceType.XYStage),
|
|
124
|
+
("isXYStageSequenceable", 0): lambda core: _dev_labels(core, DeviceType.XYStage),
|
|
125
|
+
("loadXYStageSequence", 0): lambda core: _dev_labels(core, DeviceType.XYStage),
|
|
126
|
+
("setAdapterOriginXY", 0): lambda core: _dev_labels(core, DeviceType.XYStage),
|
|
127
|
+
("setOriginX", 0): lambda core: _dev_labels(core, DeviceType.XYStage),
|
|
128
|
+
("setOriginXY", 0): lambda core: _dev_labels(core, DeviceType.XYStage),
|
|
129
|
+
("setOriginY", 0): lambda core: _dev_labels(core, DeviceType.XYStage),
|
|
130
|
+
("setRelativeXYPosition", 0): lambda core: _dev_labels(core, DeviceType.XYStage),
|
|
131
|
+
("setXYPosition", 0): lambda core: _dev_labels(core, DeviceType.XYStage),
|
|
132
|
+
("startXYStageSequence", 0): lambda core: _dev_labels(core, DeviceType.XYStage),
|
|
133
|
+
("stopXYStageSequence", 0): lambda core: _dev_labels(core, DeviceType.XYStage),
|
|
134
|
+
# --------------------
|
|
135
|
+
("deleteConfig", 2): _dev_labels,
|
|
136
|
+
("detectDevice", 0): _dev_labels,
|
|
137
|
+
("deviceBusy", 0): _dev_labels,
|
|
138
|
+
("getAllowedPropertyValues", 0): _dev_labels,
|
|
139
|
+
("getDeviceDelayMs", 0): _dev_labels,
|
|
140
|
+
("getDeviceDescription", 0): _dev_labels,
|
|
141
|
+
("getDeviceInitializationState", 0): _dev_labels,
|
|
142
|
+
("getDeviceLibrary", 0): _dev_labels,
|
|
143
|
+
("getDeviceName", 0): _dev_labels,
|
|
144
|
+
("getDevicePropertyNames", 0): _dev_labels,
|
|
145
|
+
("getDeviceType", 0): _dev_labels,
|
|
146
|
+
("getParentLabel", 0): _dev_labels,
|
|
147
|
+
("getProperty", 0): _dev_labels,
|
|
148
|
+
("getPropertyFromCache", 0): _dev_labels,
|
|
149
|
+
("getPropertyLowerLimit", 0): _dev_labels, # ?
|
|
150
|
+
("getPropertySequenceMaxLength", 0): _dev_labels, # ?
|
|
151
|
+
("getPropertyType", 0): _dev_labels,
|
|
152
|
+
("getPropertyUpperLimit", 0): _dev_labels, # ?
|
|
153
|
+
("hasProperty", 0): _dev_labels,
|
|
154
|
+
("hasPropertyLimits", 0): _dev_labels,
|
|
155
|
+
("initializeDevice", 0): _dev_labels,
|
|
156
|
+
("isPropertyPreInit", 0): _dev_labels,
|
|
157
|
+
("isPropertyReadOnly", 0): _dev_labels,
|
|
158
|
+
("isPropertySequenceable", 0): _dev_labels,
|
|
159
|
+
("loadPropertySequence", 0): _dev_labels, # ?
|
|
160
|
+
("setDeviceDelayMs", 0): _dev_labels,
|
|
161
|
+
("setParentLabel", 0): _dev_labels,
|
|
162
|
+
("setProperty", 0): _dev_labels,
|
|
163
|
+
("startPropertySequence", 0): _dev_labels, # ?
|
|
164
|
+
("unloadDevice", 0): _dev_labels,
|
|
165
|
+
("usesDeviceDelay", 0): _dev_labels,
|
|
166
|
+
("waitForDevice", 0): _dev_labels,
|
|
167
|
+
# --------------------
|
|
168
|
+
("displaySLMImage", 0): lambda core: _dev_labels(core, DeviceType.SLM),
|
|
169
|
+
("getSLMBytesPerPixel", 0): lambda core: _dev_labels(core, DeviceType.SLM),
|
|
170
|
+
("getSLMExposure", 0): lambda core: _dev_labels(core, DeviceType.SLM),
|
|
171
|
+
("getSLMHeight", 0): lambda core: _dev_labels(core, DeviceType.SLM),
|
|
172
|
+
("getSLMNumberOfComponents", 0): lambda core: _dev_labels(core, DeviceType.SLM),
|
|
173
|
+
("getSLMSequenceMaxLength", 0): lambda core: _dev_labels(core, DeviceType.SLM),
|
|
174
|
+
("getSLMWidth", 0): lambda core: _dev_labels(core, DeviceType.SLM),
|
|
175
|
+
("loadSLMSequence", 0): lambda core: _dev_labels(core, DeviceType.SLM),
|
|
176
|
+
("setSLMExposure", 0): lambda core: _dev_labels(core, DeviceType.SLM),
|
|
177
|
+
("setSLMImage", 0): lambda core: _dev_labels(core, DeviceType.SLM),
|
|
178
|
+
("setSLMPixelsTo", 0): lambda core: _dev_labels(core, DeviceType.SLM),
|
|
179
|
+
("startSLMSequence", 0): lambda core: _dev_labels(core, DeviceType.SLM),
|
|
180
|
+
("stopSLMSequence", 0): lambda core: _dev_labels(core, DeviceType.SLM),
|
|
181
|
+
# --------------------
|
|
182
|
+
("deleteGalvoPolygons", 0): lambda core: _dev_labels(core, DeviceType.Galvo),
|
|
183
|
+
("getGalvoChannels", 0): lambda core: _dev_labels(core, DeviceType.Galvo),
|
|
184
|
+
("getGalvoPosition", 0): lambda core: _dev_labels(core, DeviceType.Galvo),
|
|
185
|
+
("getGalvoXMinimum", 0): lambda core: _dev_labels(core, DeviceType.Galvo),
|
|
186
|
+
("getGalvoXRange", 0): lambda core: _dev_labels(core, DeviceType.Galvo),
|
|
187
|
+
("getGalvoYMinimum", 0): lambda core: _dev_labels(core, DeviceType.Galvo),
|
|
188
|
+
("getGalvoYRange", 0): lambda core: _dev_labels(core, DeviceType.Galvo),
|
|
189
|
+
("loadGalvoPolygons", 0): lambda core: _dev_labels(core, DeviceType.Galvo),
|
|
190
|
+
("pointGalvoAndFire", 0): lambda core: _dev_labels(core, DeviceType.Galvo),
|
|
191
|
+
("runGalvoPolygons", 0): lambda core: _dev_labels(core, DeviceType.Galvo),
|
|
192
|
+
("runGalvoSequence", 0): lambda core: _dev_labels(core, DeviceType.Galvo),
|
|
193
|
+
("setGalvoIlluminationState", 0): lambda core: _dev_labels(core, DeviceType.Galvo),
|
|
194
|
+
("setGalvoPolygonRepetitions", 0): lambda core: _dev_labels(core, DeviceType.Galvo),
|
|
195
|
+
("setGalvoPosition", 0): lambda core: _dev_labels(core, DeviceType.Galvo),
|
|
196
|
+
("setGalvoSpotInterval", 0): lambda core: _dev_labels(core, DeviceType.Galvo),
|
|
197
|
+
# --------------------
|
|
198
|
+
("getExposure", 0): lambda core: _dev_labels(core, DeviceType.Camera),
|
|
199
|
+
("getExposureSequenceMaxLength", 0): lambda core: _dev_labels(core, DeviceType.Camera), # noqa
|
|
200
|
+
("getROI", 0): lambda core: _dev_labels(core, DeviceType.Camera),
|
|
201
|
+
("isExposureSequenceable", 0): lambda core: _dev_labels(core, DeviceType.Camera),
|
|
202
|
+
("isSequenceRunning", 0): lambda core: _dev_labels(core, DeviceType.Camera),
|
|
203
|
+
("loadExposureSequence", 0): lambda core: _dev_labels(core, DeviceType.Camera),
|
|
204
|
+
("prepareSequenceAcquisition", 0): lambda core: _dev_labels(core, DeviceType.Camera), # noqa
|
|
205
|
+
("setExposure", 0): lambda core: _dev_labels(core, DeviceType.Camera),
|
|
206
|
+
("setROI", 0): lambda core: _dev_labels(core, DeviceType.Camera),
|
|
207
|
+
("startExposureSequence", 0): lambda core: _dev_labels(core, DeviceType.Camera),
|
|
208
|
+
("startSequenceAcquisition", 0): lambda core: _dev_labels(core, DeviceType.Camera),
|
|
209
|
+
("stopExposureSequence", 0): lambda core: _dev_labels(core, DeviceType.Camera),
|
|
210
|
+
("stopSequenceAcquisition", 0): lambda core: _dev_labels(core, DeviceType.Camera),
|
|
211
|
+
# --------------------
|
|
212
|
+
("defineStateLabel", 0): lambda core: _dev_labels(core, DeviceType.State),
|
|
213
|
+
("getNumberOfStates", 0): lambda core: _dev_labels(core, DeviceType.State),
|
|
214
|
+
("getState", 0): lambda core: _dev_labels(core, DeviceType.State),
|
|
215
|
+
("getStateFromLabel", 0): lambda core: _dev_labels(core, DeviceType.State),
|
|
216
|
+
("getStateLabel", 0): lambda core: _dev_labels(core, DeviceType.State),
|
|
217
|
+
("getStateLabels", 0): lambda core: _dev_labels(core, DeviceType.State),
|
|
218
|
+
("setState", 0): lambda core: _dev_labels(core, DeviceType.State),
|
|
219
|
+
("setStateLabel", 0): lambda core: _dev_labels(core, DeviceType.State),
|
|
220
|
+
# --------------------
|
|
221
|
+
("getShutterOpen", 0): lambda core: _dev_labels(core, DeviceType.Shutter),
|
|
222
|
+
("setShutterOpen", 0): lambda core: _dev_labels(core, DeviceType.Shutter),
|
|
223
|
+
# --------------------
|
|
224
|
+
("getInstalledDeviceDescriptions", 0): lambda core: _dev_labels(core, DeviceType.Hub), # noqa
|
|
225
|
+
("getInstalledDevices", 0): lambda core: _dev_labels(core, DeviceType.Hub),
|
|
226
|
+
("getLoadedPeripheralDevices", 0): lambda core: _dev_labels(core, DeviceType.Hub),
|
|
227
|
+
("getSerialPortAnswer", 0): lambda core: _dev_labels(core, DeviceType.Serial),
|
|
228
|
+
("readFromSerialPort", 0): lambda core: _dev_labels(core, DeviceType.Serial),
|
|
229
|
+
("setParentLabel", 1): lambda core: _dev_labels(core, DeviceType.Hub),
|
|
230
|
+
|
|
231
|
+
# ----------------- Config Groups --------------------
|
|
232
|
+
("deleteConfig", 0): _config_group_names,
|
|
233
|
+
("deleteConfigGroup", 0): _config_group_names,
|
|
234
|
+
("getAvailableConfigs", 0): _config_group_names,
|
|
235
|
+
("getConfigData", 0): _config_group_names,
|
|
236
|
+
("getConfigGroupState", 0): _config_group_names,
|
|
237
|
+
("getConfigGroupStateFromCache", 0): _config_group_names,
|
|
238
|
+
("getConfigState", 0): _config_group_names,
|
|
239
|
+
("getCurrentConfig", 0): _config_group_names,
|
|
240
|
+
("getCurrentConfigFromCache", 0): _config_group_names,
|
|
241
|
+
("renameConfig", 0): _config_group_names,
|
|
242
|
+
("renameConfigGroup", 0): _config_group_names,
|
|
243
|
+
("setChannelGroup", 0): _config_group_names,
|
|
244
|
+
("setConfig", 0): _config_group_names,
|
|
245
|
+
("waitForConfig", 0): _config_group_names,
|
|
246
|
+
}
|
|
247
|
+
# fmt: on
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _get_prop_names(core: CMMCorePlus, device: str) -> Sequence[SimpleCompletion]:
|
|
251
|
+
"""Get the property names for a given device."""
|
|
252
|
+
try:
|
|
253
|
+
return [
|
|
254
|
+
SimpleCompletion(f'"{prop}"')
|
|
255
|
+
for prop in core.getDevicePropertyNames(device)
|
|
256
|
+
]
|
|
257
|
+
except Exception: # pragma: no cover
|
|
258
|
+
return []
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# fmt: off
|
|
262
|
+
# Map of (method_name, arg_index) -> (label arg idx, function that returns completions)
|
|
263
|
+
LABEL_COMPLETERS: dict[tuple[str, int], tuple[int, CoreLabelCompleter]] = {
|
|
264
|
+
("define", 3): (2, _get_prop_names),
|
|
265
|
+
("definePixelSizeConfig", 2): (1, _get_prop_names),
|
|
266
|
+
("deleteConfig", 3): (2, _get_prop_names),
|
|
267
|
+
("getAllowedPropertyValues", 1): (0, _get_prop_names),
|
|
268
|
+
("getProperty", 1): (0, _get_prop_names),
|
|
269
|
+
("getPropertyFromCache", 1): (0, _get_prop_names),
|
|
270
|
+
("getPropertyLowerLimit", 1): (0, _get_prop_names),
|
|
271
|
+
("getPropertySequenceMaxLength", 1): (0, _get_prop_names),
|
|
272
|
+
("getPropertyType", 1): (0, _get_prop_names),
|
|
273
|
+
("getPropertyUpperLimit", 1): (0, _get_prop_names),
|
|
274
|
+
("hasProperty", 1): (0, _get_prop_names),
|
|
275
|
+
("hasPropertyLimits", 1): (0, _get_prop_names),
|
|
276
|
+
("isPropertyPreInit", 1): (0, _get_prop_names),
|
|
277
|
+
("isPropertyReadOnly", 1): (0, _get_prop_names),
|
|
278
|
+
("isPropertySequenceable", 1): (0, _get_prop_names),
|
|
279
|
+
("loadPropertySequence", 1): (0, _get_prop_names),
|
|
280
|
+
("setProperty", 1): (0, _get_prop_names),
|
|
281
|
+
("startPropertySequence", 1): (0, _get_prop_names),
|
|
282
|
+
("stopPropertySequence", 1): (0, _get_prop_names),
|
|
283
|
+
# ----------------- Config Presets --------------------
|
|
284
|
+
("deleteConfig", 1): (0, _config_preset_names),
|
|
285
|
+
("getConfigData", 1): (0, _config_preset_names),
|
|
286
|
+
("getConfigState", 1): (0, _config_preset_names),
|
|
287
|
+
("renameConfig", 1): (0, _config_preset_names),
|
|
288
|
+
("setConfig", 1): (0, _config_preset_names),
|
|
289
|
+
("waitForConfig", 1): (0, _config_preset_names),
|
|
290
|
+
}
|
|
291
|
+
# fmt: on
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@context_matcher() # type: ignore[misc]
|
|
295
|
+
def cmmcoreplus_matcher(ctx: CompletionContext) -> SimpleMatcherResult:
|
|
296
|
+
"""
|
|
297
|
+
Offer string completions for CMMCorePlus calls such as.
|
|
298
|
+
|
|
299
|
+
core.setCameraDevice(<TAB>)
|
|
300
|
+
core.setProperty("CAM", <TAB>)
|
|
301
|
+
"""
|
|
302
|
+
if not (ip := get_ipython()):
|
|
303
|
+
return _null() # pragma: no cover
|
|
304
|
+
ns = ip.user_ns # live user namespace
|
|
305
|
+
|
|
306
|
+
# 1. Extract the object expression and the *real* attribute name syntactically.
|
|
307
|
+
# e.g.: 'core.setCameraDevice('
|
|
308
|
+
src_to_cursor = ctx.full_text[: ctx.cursor_position]
|
|
309
|
+
if not (m := OBJ_METHOD_RE.search(src_to_cursor)):
|
|
310
|
+
return _null()
|
|
311
|
+
|
|
312
|
+
# 2. Ensure we're dealing with a CMMCorePlus method
|
|
313
|
+
# e.g. ('core', 'setCameraDevice')
|
|
314
|
+
var_expr, method_name = m.group("obj"), m.group("attr")
|
|
315
|
+
try:
|
|
316
|
+
obj = eval(var_expr, ns)
|
|
317
|
+
except Exception:
|
|
318
|
+
return _null()
|
|
319
|
+
|
|
320
|
+
if not isinstance(obj, CMMCorePlus):
|
|
321
|
+
return _null()
|
|
322
|
+
|
|
323
|
+
# 3. Get the argument index for the method call.
|
|
324
|
+
arg_index = _get_argument_index(src_to_cursor, ns)
|
|
325
|
+
|
|
326
|
+
# 4. Check if we have a specific completion for this method name and arg_index.
|
|
327
|
+
key = (method_name, arg_index)
|
|
328
|
+
if (dev_getter := CORE_COMPLETERS.get(key)) and (completions := dev_getter(obj)):
|
|
329
|
+
# If we have a specific suggestion for this method and arg_index, use it.
|
|
330
|
+
return {"completions": completions, "suppress": True}
|
|
331
|
+
|
|
332
|
+
if info := LABEL_COMPLETERS.get(key):
|
|
333
|
+
dev_idx, getter = info
|
|
334
|
+
if dev_label := _get_argument(src_to_cursor, dev_idx):
|
|
335
|
+
if completions := getter(obj, dev_label):
|
|
336
|
+
return {"completions": completions, "suppress": True}
|
|
337
|
+
|
|
338
|
+
return _null()
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _get_argument(src: str, index: int) -> str:
|
|
342
|
+
"""Parse the argument at the given index from a method call string.
|
|
343
|
+
|
|
344
|
+
For example:
|
|
345
|
+
>>> _get_argument("core.getProperty('Camera', ", 0)
|
|
346
|
+
'Camera'
|
|
347
|
+
"""
|
|
348
|
+
p0 = src.find("(") + 1
|
|
349
|
+
p1 = src.rfind(")") if ")" in src else None
|
|
350
|
+
if inner := src[slice(p0, p1)].strip():
|
|
351
|
+
args = inner.split(",")
|
|
352
|
+
if index < len(args):
|
|
353
|
+
return args[index].strip().strip("'\"")
|
|
354
|
+
return "" # pragma: no cover
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def install_pymmcore_ipy_completion(shell: InteractiveShell | None = None) -> None:
|
|
358
|
+
"""Install the CMMCorePlus completion matcher in the current IPython session."""
|
|
359
|
+
if shell is None and not (shell := get_ipython()):
|
|
360
|
+
return # pragma: no cover
|
|
361
|
+
|
|
362
|
+
if cmmcoreplus_matcher not in shell.Completer.custom_matchers:
|
|
363
|
+
shell.Completer.custom_matchers.append(cmmcoreplus_matcher)
|
pymmcore_plus/_pymmcore.py
CHANGED
|
@@ -24,7 +24,9 @@ class VersionInfo(NamedTuple):
|
|
|
24
24
|
minor: int
|
|
25
25
|
micro: int
|
|
26
26
|
device_interface: int
|
|
27
|
-
build: int
|
|
27
|
+
build: int = 0
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
# pass no more than 5 parts to VersionInfo
|
|
31
|
+
numbers = re.findall(r"\d+", __version__)[:5]
|
|
32
|
+
version_info = VersionInfo(*(int(i) for i in numbers))
|
pymmcore_plus/core/_constants.py
CHANGED
|
@@ -13,6 +13,7 @@ import pymmcore_plus._pymmcore as pymmcore
|
|
|
13
13
|
class Keyword(str, Enum):
|
|
14
14
|
Name = pymmcore.g_Keyword_Name
|
|
15
15
|
Description = pymmcore.g_Keyword_Description
|
|
16
|
+
|
|
16
17
|
CameraName = pymmcore.g_Keyword_CameraName
|
|
17
18
|
CameraID = pymmcore.g_Keyword_CameraID
|
|
18
19
|
CameraChannelName = pymmcore.g_Keyword_CameraChannelName
|
|
@@ -31,6 +32,7 @@ class Keyword(str, Enum):
|
|
|
31
32
|
Offset = pymmcore.g_Keyword_Offset
|
|
32
33
|
CCDTemperature = pymmcore.g_Keyword_CCDTemperature
|
|
33
34
|
CCDTemperatureSetPoint = pymmcore.g_Keyword_CCDTemperatureSetPoint
|
|
35
|
+
|
|
34
36
|
State = pymmcore.g_Keyword_State
|
|
35
37
|
Label = pymmcore.g_Keyword_Label
|
|
36
38
|
Position = pymmcore.g_Keyword_Position
|
|
@@ -69,12 +71,15 @@ class Keyword(str, Enum):
|
|
|
69
71
|
HubID = pymmcore.g_Keyword_HubID
|
|
70
72
|
|
|
71
73
|
# image annotations
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
Metadata_CameraLabel = pymmcore.g_Keyword_Metadata_CameraLabel
|
|
75
|
+
Metadata_Exposure = pymmcore.g_Keyword_Metadata_Exposure
|
|
76
|
+
Metadata_Height = pymmcore.g_Keyword_Metadata_Height
|
|
74
77
|
Metadata_ImageNumber = pymmcore.g_Keyword_Metadata_ImageNumber
|
|
75
78
|
Metadata_ROI_X = pymmcore.g_Keyword_Metadata_ROI_X
|
|
76
79
|
Metadata_ROI_Y = pymmcore.g_Keyword_Metadata_ROI_Y
|
|
80
|
+
Metadata_Score = pymmcore.g_Keyword_Metadata_Score
|
|
77
81
|
Metadata_TimeInCore = pymmcore.g_Keyword_Metadata_TimeInCore
|
|
82
|
+
Metadata_Width = pymmcore.g_Keyword_Metadata_Width
|
|
78
83
|
|
|
79
84
|
def __str__(self) -> str:
|
|
80
85
|
return str(self.value)
|
|
@@ -137,6 +142,8 @@ class DeviceType(IntEnum):
|
|
|
137
142
|
SLMDevice = pymmcore.SLMDevice
|
|
138
143
|
HubDevice = pymmcore.HubDevice
|
|
139
144
|
GalvoDevice = pymmcore.GalvoDevice
|
|
145
|
+
PressurePumpDevice = pymmcore.PressurePumpDevice
|
|
146
|
+
VolumetricPumpDevice = pymmcore.VolumetricPumpDevice
|
|
140
147
|
# aliases for clearer naming (e.g. `DeviceType.Camera`)
|
|
141
148
|
Unknown = UnknownType
|
|
142
149
|
Any = AnyType
|
|
@@ -155,6 +162,8 @@ class DeviceType(IntEnum):
|
|
|
155
162
|
SLM = SLMDevice
|
|
156
163
|
Hub = HubDevice
|
|
157
164
|
Galvo = GalvoDevice
|
|
165
|
+
PressurePump = PressurePumpDevice
|
|
166
|
+
VolumetricPump = VolumetricPumpDevice
|
|
158
167
|
|
|
159
168
|
def __str__(self) -> str:
|
|
160
169
|
return str(self.name).replace("Type", "").replace("Device", "")
|
|
@@ -165,7 +174,9 @@ class PropertyType(IntEnum):
|
|
|
165
174
|
String = pymmcore.String
|
|
166
175
|
Float = pymmcore.Float
|
|
167
176
|
Integer = pymmcore.Integer
|
|
177
|
+
|
|
168
178
|
Boolean = auto() # not supported in pymmcore
|
|
179
|
+
Enum = auto() # not supported in pymmcore
|
|
169
180
|
|
|
170
181
|
def to_python(self) -> type | None:
|
|
171
182
|
return {0: None, 1: str, 2: float, 3: int}[self]
|
|
@@ -183,7 +194,16 @@ class PropertyType(IntEnum):
|
|
|
183
194
|
if value is None:
|
|
184
195
|
return PropertyType.Undef
|
|
185
196
|
if isinstance(value, str):
|
|
186
|
-
|
|
197
|
+
if value.lower() in ("int", "integer"):
|
|
198
|
+
return PropertyType.Integer
|
|
199
|
+
if value.lower() in ("float", "double"):
|
|
200
|
+
return PropertyType.Float
|
|
201
|
+
if value.lower() in ("bool", "boolean"):
|
|
202
|
+
return PropertyType.Boolean
|
|
203
|
+
if value.lower() in ("string", "str"):
|
|
204
|
+
return PropertyType.String
|
|
205
|
+
if value.lower() in ("enum", "enumeration"):
|
|
206
|
+
return PropertyType.Enum
|
|
187
207
|
if isinstance(value, type):
|
|
188
208
|
if value is float:
|
|
189
209
|
return PropertyType.Float
|
|
@@ -193,6 +213,8 @@ class PropertyType(IntEnum):
|
|
|
193
213
|
return PropertyType.String
|
|
194
214
|
elif value is bool:
|
|
195
215
|
return PropertyType.Boolean
|
|
216
|
+
elif issubclass(value, Enum):
|
|
217
|
+
return PropertyType.Enum
|
|
196
218
|
|
|
197
219
|
raise TypeError(
|
|
198
220
|
f"Property type must be a PropertyType enum member, "
|