pymmcore-plus 0.9.3__py3-none-any.whl → 0.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pymmcore_plus/__init__.py +7 -4
- pymmcore_plus/_benchmark.py +203 -0
- pymmcore_plus/_build.py +6 -1
- pymmcore_plus/_cli.py +131 -31
- pymmcore_plus/_logger.py +19 -10
- pymmcore_plus/_pymmcore.py +12 -0
- pymmcore_plus/_util.py +139 -32
- pymmcore_plus/core/__init__.py +5 -0
- pymmcore_plus/core/_config.py +6 -4
- pymmcore_plus/core/_config_group.py +4 -3
- pymmcore_plus/core/_constants.py +135 -10
- pymmcore_plus/core/_device.py +4 -4
- pymmcore_plus/core/_metadata.py +3 -3
- pymmcore_plus/core/_mmcore_plus.py +254 -170
- pymmcore_plus/core/_property.py +6 -6
- pymmcore_plus/core/_sequencing.py +370 -233
- pymmcore_plus/core/events/__init__.py +6 -6
- pymmcore_plus/core/events/_device_signal_view.py +8 -6
- pymmcore_plus/core/events/_norm_slot.py +2 -4
- pymmcore_plus/core/events/_prop_event_mixin.py +7 -4
- pymmcore_plus/core/events/_protocol.py +5 -2
- pymmcore_plus/core/events/_psygnal.py +2 -2
- pymmcore_plus/experimental/__init__.py +0 -0
- pymmcore_plus/experimental/unicore/__init__.py +14 -0
- pymmcore_plus/experimental/unicore/_device_manager.py +173 -0
- pymmcore_plus/experimental/unicore/_proxy.py +127 -0
- pymmcore_plus/experimental/unicore/_unicore.py +703 -0
- pymmcore_plus/experimental/unicore/devices/__init__.py +0 -0
- pymmcore_plus/experimental/unicore/devices/_device.py +269 -0
- pymmcore_plus/experimental/unicore/devices/_properties.py +400 -0
- pymmcore_plus/experimental/unicore/devices/_stage.py +221 -0
- pymmcore_plus/install.py +16 -11
- pymmcore_plus/mda/__init__.py +1 -1
- pymmcore_plus/mda/_engine.py +320 -148
- pymmcore_plus/mda/_protocol.py +6 -4
- pymmcore_plus/mda/_runner.py +62 -51
- pymmcore_plus/mda/_thread_relay.py +5 -3
- pymmcore_plus/mda/events/__init__.py +2 -2
- pymmcore_plus/mda/events/_protocol.py +10 -2
- pymmcore_plus/mda/events/_psygnal.py +2 -2
- pymmcore_plus/mda/handlers/_5d_writer_base.py +106 -15
- pymmcore_plus/mda/handlers/__init__.py +7 -1
- pymmcore_plus/mda/handlers/_img_sequence_writer.py +11 -6
- pymmcore_plus/mda/handlers/_ome_tiff_writer.py +8 -4
- pymmcore_plus/mda/handlers/_ome_zarr_writer.py +82 -9
- pymmcore_plus/mda/handlers/_tensorstore_handler.py +374 -0
- pymmcore_plus/mda/handlers/_util.py +1 -1
- pymmcore_plus/metadata/__init__.py +36 -0
- pymmcore_plus/metadata/functions.py +353 -0
- pymmcore_plus/metadata/schema.py +472 -0
- pymmcore_plus/metadata/serialize.py +120 -0
- pymmcore_plus/mocks.py +51 -0
- pymmcore_plus/model/_config_file.py +5 -6
- pymmcore_plus/model/_config_group.py +29 -2
- pymmcore_plus/model/_core_device.py +12 -1
- pymmcore_plus/model/_core_link.py +2 -1
- pymmcore_plus/model/_device.py +39 -8
- pymmcore_plus/model/_microscope.py +39 -3
- pymmcore_plus/model/_pixel_size_config.py +27 -4
- pymmcore_plus/model/_property.py +13 -3
- pymmcore_plus/seq_tester.py +1 -1
- {pymmcore_plus-0.9.3.dist-info → pymmcore_plus-0.13.0.dist-info}/METADATA +22 -12
- pymmcore_plus-0.13.0.dist-info/RECORD +71 -0
- {pymmcore_plus-0.9.3.dist-info → pymmcore_plus-0.13.0.dist-info}/WHEEL +1 -1
- pymmcore_plus/core/_state.py +0 -244
- pymmcore_plus-0.9.3.dist-info/RECORD +0 -55
- {pymmcore_plus-0.9.3.dist-info → pymmcore_plus-0.13.0.dist-info}/entry_points.txt +0 -0
- {pymmcore_plus-0.9.3.dist-info → pymmcore_plus-0.13.0.dist-info}/licenses/LICENSE +0 -0
pymmcore_plus/__init__.py
CHANGED
|
@@ -9,7 +9,7 @@ except PackageNotFoundError: # pragma: no cover
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
from ._logger import configure_logging
|
|
12
|
-
from ._util import find_micromanager
|
|
12
|
+
from ._util import find_micromanager, use_micromanager
|
|
13
13
|
from .core import (
|
|
14
14
|
CFGCommand,
|
|
15
15
|
CFGGroup,
|
|
@@ -26,6 +26,7 @@ from .core import (
|
|
|
26
26
|
FocusDirection,
|
|
27
27
|
Keyword,
|
|
28
28
|
Metadata,
|
|
29
|
+
PixelFormat,
|
|
29
30
|
PortType,
|
|
30
31
|
PropertyType,
|
|
31
32
|
)
|
|
@@ -33,7 +34,6 @@ from .core.events import CMMCoreSignaler, PCoreSignaler
|
|
|
33
34
|
from .mda._runner import GeneratorMDASequence
|
|
34
35
|
|
|
35
36
|
__all__ = [
|
|
36
|
-
"__version__",
|
|
37
37
|
"ActionType",
|
|
38
38
|
"CFGCommand",
|
|
39
39
|
"CFGGroup",
|
|
@@ -41,7 +41,6 @@ __all__ = [
|
|
|
41
41
|
"CMMCoreSignaler",
|
|
42
42
|
"ConfigGroup",
|
|
43
43
|
"Configuration",
|
|
44
|
-
"configure_logging",
|
|
45
44
|
"Device",
|
|
46
45
|
"DeviceAdapter",
|
|
47
46
|
"DeviceDetectionStatus",
|
|
@@ -49,12 +48,16 @@ __all__ = [
|
|
|
49
48
|
"DeviceNotification",
|
|
50
49
|
"DeviceProperty",
|
|
51
50
|
"DeviceType",
|
|
52
|
-
"find_micromanager",
|
|
53
51
|
"FocusDirection",
|
|
54
52
|
"GeneratorMDASequence",
|
|
55
53
|
"Keyword",
|
|
56
54
|
"Metadata",
|
|
57
55
|
"PCoreSignaler",
|
|
56
|
+
"PixelFormat",
|
|
58
57
|
"PortType",
|
|
59
58
|
"PropertyType",
|
|
59
|
+
"__version__",
|
|
60
|
+
"configure_logging",
|
|
61
|
+
"find_micromanager",
|
|
62
|
+
"use_micromanager",
|
|
60
63
|
]
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import timeit
|
|
4
|
+
import warnings
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from pymmcore_plus import CMMCorePlus, DeviceType
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Iterable, Iterator, Sequence
|
|
11
|
+
|
|
12
|
+
from pymmcore_plus.core._device import Device
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Benchmark:
|
|
16
|
+
device_type = DeviceType.Camera
|
|
17
|
+
|
|
18
|
+
def __init__(self, core: CMMCorePlus, label: str = "") -> None:
|
|
19
|
+
self.core = core
|
|
20
|
+
self.label = label
|
|
21
|
+
|
|
22
|
+
def setup(self) -> None:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
def device(self) -> Device | None:
|
|
26
|
+
if self.label is not None:
|
|
27
|
+
return self.core.getDeviceObject(self.label)
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
def run(self, number: int) -> Iterator[tuple[str, float | str]]:
|
|
31
|
+
# get methods in the order of definition, in reverse MRO order
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
self.setup()
|
|
35
|
+
except Exception as e: # pragma: no cover
|
|
36
|
+
warnings.warn(
|
|
37
|
+
f"Setup failed on device {self.label!r}: {e}",
|
|
38
|
+
RuntimeWarning,
|
|
39
|
+
stacklevel=2,
|
|
40
|
+
)
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
methods: list[str] = []
|
|
44
|
+
for base in reversed(type(self).mro()):
|
|
45
|
+
methods.extend(m for m in base.__dict__ if m.startswith("bench_"))
|
|
46
|
+
|
|
47
|
+
for method_name in methods:
|
|
48
|
+
try:
|
|
49
|
+
t = timeit.timeit(getattr(self, method_name), number=number)
|
|
50
|
+
result: float | str = round(1000 * t / number, 3)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
result = str(e)
|
|
53
|
+
yield method_name[6:], result
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CoreBenchmark(Benchmark):
|
|
57
|
+
device_type = DeviceType.Core
|
|
58
|
+
|
|
59
|
+
def bench_getDeviceAdapterNames(self) -> None:
|
|
60
|
+
self.core.getDeviceAdapterNames()
|
|
61
|
+
|
|
62
|
+
def bench_getLoadedDevices(self) -> None:
|
|
63
|
+
self.core.getLoadedDevices()
|
|
64
|
+
|
|
65
|
+
def bench_getSystemState(self) -> None:
|
|
66
|
+
self.core.getSystemState()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class CameraBenchmark(Benchmark):
|
|
70
|
+
device_type = DeviceType.Camera
|
|
71
|
+
|
|
72
|
+
def setup(self) -> None:
|
|
73
|
+
self.core.setCameraDevice(self.label)
|
|
74
|
+
self.core.setExposure(self.label, 1)
|
|
75
|
+
|
|
76
|
+
def bench_getMultiROI(self) -> None:
|
|
77
|
+
self.core.getMultiROI()
|
|
78
|
+
|
|
79
|
+
def bench_getExposure(self) -> None:
|
|
80
|
+
self.core.getExposure(self.label)
|
|
81
|
+
|
|
82
|
+
def bench_snapImage(self) -> None:
|
|
83
|
+
self.core.snapImage()
|
|
84
|
+
|
|
85
|
+
def bench_getImage(self) -> None:
|
|
86
|
+
self.core.getImage()
|
|
87
|
+
|
|
88
|
+
def bench_getImageWidth(self) -> None:
|
|
89
|
+
self.core.getImageWidth()
|
|
90
|
+
|
|
91
|
+
def bench_getImageHeight(self) -> None:
|
|
92
|
+
self.core.getImageHeight()
|
|
93
|
+
|
|
94
|
+
def bench_getImageBufferSize(self) -> None:
|
|
95
|
+
self.core.getImageBufferSize()
|
|
96
|
+
|
|
97
|
+
def bench_getImageBitDepth(self) -> None:
|
|
98
|
+
self.core.getImageBitDepth()
|
|
99
|
+
|
|
100
|
+
def bench_getNumberOfComponents(self) -> None:
|
|
101
|
+
self.core.getNumberOfComponents()
|
|
102
|
+
|
|
103
|
+
def bench_getNumberOfCameraChannels(self) -> None:
|
|
104
|
+
self.core.getNumberOfCameraChannels()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class XYStageBenchmark(Benchmark):
|
|
108
|
+
device_type = DeviceType.XYStage
|
|
109
|
+
|
|
110
|
+
def setup(self) -> None:
|
|
111
|
+
self.core.setXYStageDevice(self.label)
|
|
112
|
+
self.position = self.core.getXYPosition(self.label)
|
|
113
|
+
|
|
114
|
+
def bench_getXYPosition(self) -> None:
|
|
115
|
+
self.core.getXYPosition(self.label)
|
|
116
|
+
|
|
117
|
+
def bench_getXPosition(self) -> None:
|
|
118
|
+
self.core.getXPosition(self.label)
|
|
119
|
+
|
|
120
|
+
def bench_getYPosition(self) -> None:
|
|
121
|
+
self.core.getYPosition(self.label)
|
|
122
|
+
|
|
123
|
+
def bench_setXYPosition(self) -> None:
|
|
124
|
+
self.core.setXYPosition(self.label, *self.position)
|
|
125
|
+
|
|
126
|
+
def bench_setRelativeXYPosition(self) -> None:
|
|
127
|
+
self.core.setRelativeXYPosition(self.label, 0, 0)
|
|
128
|
+
|
|
129
|
+
def bench_isXYStageSequenceable(self) -> None:
|
|
130
|
+
self.core.isXYStageSequenceable(self.label)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class StageBenchmark(Benchmark):
|
|
134
|
+
device_type = DeviceType.Stage
|
|
135
|
+
|
|
136
|
+
def setup(self) -> None:
|
|
137
|
+
self.position = self.core.getPosition(self.label)
|
|
138
|
+
|
|
139
|
+
def bench_getPosition(self) -> None:
|
|
140
|
+
self.core.getPosition(self.label)
|
|
141
|
+
|
|
142
|
+
def bench_setPosition(self) -> None:
|
|
143
|
+
self.core.setPosition(self.label, self.position)
|
|
144
|
+
|
|
145
|
+
def bench_setRelativePosition(self) -> None:
|
|
146
|
+
self.core.setRelativePosition(self.label, 0)
|
|
147
|
+
|
|
148
|
+
def bench_isStageSequenceable(self) -> None:
|
|
149
|
+
self.core.isStageSequenceable(self.label)
|
|
150
|
+
|
|
151
|
+
def bench_isStageLinearSequenceable(self) -> None:
|
|
152
|
+
self.core.isStageLinearSequenceable(self.label)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class StateBenchmark(Benchmark):
|
|
156
|
+
device_type = DeviceType.State
|
|
157
|
+
|
|
158
|
+
def setup(self) -> None:
|
|
159
|
+
self.initial_state = self.core.getState(self.label)
|
|
160
|
+
try:
|
|
161
|
+
self.labels: Sequence[str] = self.core.getStateLabels(self.label)
|
|
162
|
+
except Exception:
|
|
163
|
+
self.labels = []
|
|
164
|
+
|
|
165
|
+
def bench_getState(self) -> None:
|
|
166
|
+
self.core.getState(self.label)
|
|
167
|
+
|
|
168
|
+
def bench_setState(self) -> None:
|
|
169
|
+
self.core.setState(self.label, self.initial_state)
|
|
170
|
+
|
|
171
|
+
def bench_getNumberOfStates(self) -> None:
|
|
172
|
+
self.core.getNumberOfStates(self.label)
|
|
173
|
+
|
|
174
|
+
def bench_getStateLabel(self) -> None:
|
|
175
|
+
self.core.getStateLabel(self.label)
|
|
176
|
+
|
|
177
|
+
def bench_getStateFromLabel(self) -> None:
|
|
178
|
+
for label in self.labels:
|
|
179
|
+
self.core.getStateFromLabel(self.label, label)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def benchmark_core_and_devices(
|
|
183
|
+
core: CMMCorePlus, number: int = 100
|
|
184
|
+
) -> Iterable[Device | None | tuple[str, float | str]]:
|
|
185
|
+
"""Take an initialized core with devices and benchmark various methods.
|
|
186
|
+
|
|
187
|
+
Yields
|
|
188
|
+
------
|
|
189
|
+
Device | None | tuple[str, float | str]
|
|
190
|
+
If a `Device`, it is the device object being benchmarked.
|
|
191
|
+
If None, it is the core object being benchmarked.
|
|
192
|
+
If a tuple, it is the method name and the time taken to run it.
|
|
193
|
+
"""
|
|
194
|
+
for cls in Benchmark.__subclasses__():
|
|
195
|
+
if cls.device_type == DeviceType.Core:
|
|
196
|
+
bench = cls(core, "Core")
|
|
197
|
+
yield bench.device()
|
|
198
|
+
yield from bench.run(number)
|
|
199
|
+
else:
|
|
200
|
+
for dev in core.getLoadedDevicesOfType(cls.device_type):
|
|
201
|
+
bench = cls(core, dev)
|
|
202
|
+
yield bench.device()
|
|
203
|
+
yield from bench.run(number)
|
pymmcore_plus/_build.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Clone the micro-manager source code from GitHub and build dev devices."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import json
|
|
4
6
|
import os
|
|
5
7
|
import platform
|
|
@@ -9,12 +11,15 @@ import subprocess
|
|
|
9
11
|
import tempfile
|
|
10
12
|
from contextlib import contextmanager
|
|
11
13
|
from pathlib import Path
|
|
12
|
-
from typing import
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
13
15
|
from urllib.request import Request, urlopen
|
|
14
16
|
|
|
15
17
|
from rich import print
|
|
16
18
|
from rich.prompt import Prompt
|
|
17
19
|
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from collections.abc import Iterator, Sequence
|
|
22
|
+
|
|
18
23
|
MM_REPO = "micro-manager/micro-manager"
|
|
19
24
|
MMCORE_AND_DEV = "micro-manager/mmCoreAndDevices"
|
|
20
25
|
MM_REPO_URL = f"https://github.com/{MM_REPO}.git"
|
pymmcore_plus/_cli.py
CHANGED
|
@@ -6,7 +6,10 @@ import sys
|
|
|
6
6
|
import time
|
|
7
7
|
from contextlib import suppress
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import Optional, Union, cast
|
|
10
|
+
|
|
11
|
+
from pymmcore_plus.core._device import Device
|
|
12
|
+
from pymmcore_plus.core._mmcore_plus import CMMCorePlus
|
|
10
13
|
|
|
11
14
|
try:
|
|
12
15
|
import typer
|
|
@@ -18,23 +21,41 @@ except ImportError: # pragma: no cover
|
|
|
18
21
|
) from None
|
|
19
22
|
|
|
20
23
|
import pymmcore_plus
|
|
24
|
+
from pymmcore_plus._build import DEFAULT_PACKAGES, build
|
|
21
25
|
from pymmcore_plus._logger import configure_logging
|
|
22
26
|
from pymmcore_plus._util import USER_DATA_MM_PATH
|
|
23
27
|
from pymmcore_plus.install import PLATFORM
|
|
24
28
|
|
|
25
|
-
app = typer.Typer(no_args_is_help=True)
|
|
29
|
+
app = typer.Typer(name="mmcore", no_args_is_help=True)
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
def _show_version_and_exit(value: bool) -> None:
|
|
29
33
|
if value:
|
|
30
|
-
import pymmcore
|
|
31
|
-
|
|
32
34
|
typer.echo(f"pymmcore-plus v{pymmcore_plus.__version__}")
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
try:
|
|
36
|
+
import pymmcore_nano as pymmcore
|
|
37
|
+
|
|
38
|
+
typer.echo(f"pymmcore-nano v{pymmcore.__version__}")
|
|
39
|
+
except ImportError:
|
|
40
|
+
import pymmcore
|
|
41
|
+
|
|
42
|
+
typer.echo(f"pymmcore v{pymmcore.__version__}")
|
|
43
|
+
typer.echo(f"MMCore v{pymmcore.CMMCore().getVersionInfo()}")
|
|
44
|
+
typer.echo(f"{pymmcore.CMMCore().getAPIVersionInfo()}")
|
|
35
45
|
raise typer.Exit()
|
|
36
46
|
|
|
37
47
|
|
|
48
|
+
CONFIG_PARAM = typer.Option(
|
|
49
|
+
None,
|
|
50
|
+
"-c",
|
|
51
|
+
"--config",
|
|
52
|
+
dir_okay=False,
|
|
53
|
+
exists=True,
|
|
54
|
+
resolve_path=True,
|
|
55
|
+
help="Path to Micro-Manager system configuration file.",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
38
59
|
@app.callback()
|
|
39
60
|
def _main(
|
|
40
61
|
version: Optional[bool] = typer.Option(
|
|
@@ -47,18 +68,21 @@ def _main(
|
|
|
47
68
|
) -> None:
|
|
48
69
|
"""mmcore: pymmcore-plus command line (v{version}).
|
|
49
70
|
|
|
50
|
-
For additional help on a specific command: type
|
|
71
|
+
For additional help on a specific command: type `mmcore [command] --help`
|
|
51
72
|
"""
|
|
52
73
|
# fix for windows CI encoding and emoji printing
|
|
53
74
|
if getattr(sys.stdout, "encoding", None) != "utf-8":
|
|
54
75
|
with suppress(AttributeError):
|
|
55
|
-
sys.stdout.reconfigure(encoding="utf-8") # type: ignore [attr
|
|
76
|
+
sys.stdout.reconfigure(encoding="utf-8") # type: ignore [union-attr]
|
|
56
77
|
|
|
57
78
|
|
|
58
|
-
|
|
59
|
-
(_main.__doc__ or "").
|
|
60
|
-
|
|
61
|
-
|
|
79
|
+
if "mkdocs" in sys.argv[0]: # pragma: no cover
|
|
80
|
+
_main.__doc__ = (_main.__doc__ or "").replace(" (v{version})", "")
|
|
81
|
+
else:
|
|
82
|
+
_main.__doc__ = typer.style(
|
|
83
|
+
(_main.__doc__ or "").format(version=pymmcore_plus.__version__),
|
|
84
|
+
fg=typer.colors.BRIGHT_YELLOW,
|
|
85
|
+
)
|
|
62
86
|
|
|
63
87
|
|
|
64
88
|
@app.command()
|
|
@@ -91,7 +115,8 @@ def _list() -> None:
|
|
|
91
115
|
print(f":file_folder:[bold green] {parent}")
|
|
92
116
|
for item in items:
|
|
93
117
|
bullet = " [bold yellow]*" if first else " •"
|
|
94
|
-
|
|
118
|
+
using = " [bold blue](active)" if first else ""
|
|
119
|
+
print(f"{bullet} [cyan]{item}{using}")
|
|
95
120
|
first = False
|
|
96
121
|
else:
|
|
97
122
|
print(":x: [bold red]There are no pymmcore-plus Micro-Manager files.")
|
|
@@ -100,7 +125,11 @@ def _list() -> None:
|
|
|
100
125
|
|
|
101
126
|
@app.command()
|
|
102
127
|
def mmstudio() -> None: # pragma: no cover
|
|
103
|
-
"""Run the Java Micro-Manager GUI.
|
|
128
|
+
"""Run the Java Micro-Manager GUI.
|
|
129
|
+
|
|
130
|
+
This command will attempt to locate an execute an ImageJ application found in
|
|
131
|
+
the active Micro-Manager directory.
|
|
132
|
+
"""
|
|
104
133
|
mm = pymmcore_plus.find_micromanager()
|
|
105
134
|
app = (
|
|
106
135
|
next((x for x in Path(mm).glob("ImageJ*") if not str(x).endswith("cfg")), None)
|
|
@@ -137,7 +166,7 @@ def install(
|
|
|
137
166
|
show_default=False,
|
|
138
167
|
),
|
|
139
168
|
) -> None:
|
|
140
|
-
"""Install Micro-Manager Device adapters."""
|
|
169
|
+
"""Install Micro-Manager Device adapters from <https://download.micro-manager.org>."""
|
|
141
170
|
import pymmcore_plus.install
|
|
142
171
|
|
|
143
172
|
if plain_output:
|
|
@@ -159,15 +188,7 @@ def run(
|
|
|
159
188
|
resolve_path=True,
|
|
160
189
|
help="Path to useq-schema file.",
|
|
161
190
|
),
|
|
162
|
-
config: Optional[Path] =
|
|
163
|
-
None,
|
|
164
|
-
"-c",
|
|
165
|
-
"--config",
|
|
166
|
-
dir_okay=False,
|
|
167
|
-
exists=True,
|
|
168
|
-
resolve_path=True,
|
|
169
|
-
help="Path to Micro-Manager system configuration file.",
|
|
170
|
-
),
|
|
191
|
+
config: Optional[Path] = CONFIG_PARAM,
|
|
171
192
|
z_go_up: Optional[bool] = typer.Option(None, help="Acquire from bottom to top."),
|
|
172
193
|
z_top: Optional[float] = typer.Option(None, help="Top of z-stack."),
|
|
173
194
|
z_bottom: Optional[float] = typer.Option(None, help="Bottom of z-stack."),
|
|
@@ -181,10 +202,10 @@ def run(
|
|
|
181
202
|
None, help="Asymmetric range of z-stack below position."
|
|
182
203
|
),
|
|
183
204
|
z_step: Optional[float] = typer.Option(None, help="Step size of z-stack."),
|
|
184
|
-
z_relative: Optional[
|
|
205
|
+
z_relative: Optional[list[float]] = typer.Option(
|
|
185
206
|
None, "-zr", help="Relative z-positions to acquire (may use multiple times)."
|
|
186
207
|
),
|
|
187
|
-
z_absolute: Optional[
|
|
208
|
+
z_absolute: Optional[list[float]] = typer.Option(
|
|
188
209
|
None, "-za", help="Absolute z-positions to acquire (may use multiple times)."
|
|
189
210
|
),
|
|
190
211
|
t_interval: Optional[float] = typer.Option(
|
|
@@ -198,7 +219,7 @@ def run(
|
|
|
198
219
|
axis_order: Optional[str] = typer.Option(
|
|
199
220
|
None, help="Order of axes to acquire (e.g. 'TPCZ')."
|
|
200
221
|
),
|
|
201
|
-
channel: Optional[
|
|
222
|
+
channel: Optional[list[str]] = typer.Option(
|
|
202
223
|
None,
|
|
203
224
|
help="\bChannel to acquire. Argument is a string of the following form:\n"
|
|
204
225
|
'\b - name: "DAPI"\n'
|
|
@@ -285,8 +306,8 @@ def run(
|
|
|
285
306
|
|
|
286
307
|
@app.command()
|
|
287
308
|
def build_dev(
|
|
288
|
-
devices: Optional[
|
|
289
|
-
None, help="Device adapters to build. Defaults to
|
|
309
|
+
devices: Optional[list[str]] = typer.Argument(
|
|
310
|
+
None, help=f"Device adapters to build. Defaults to {DEFAULT_PACKAGES}"
|
|
290
311
|
),
|
|
291
312
|
dest: Path = typer.Option(
|
|
292
313
|
USER_DATA_MM_PATH,
|
|
@@ -305,9 +326,10 @@ def build_dev(
|
|
|
305
326
|
"If not specified, will prompt.",
|
|
306
327
|
),
|
|
307
328
|
) -> None: # pragma: no cover
|
|
308
|
-
"""Build
|
|
309
|
-
from pymmcore_plus._build import DEFAULT_PACKAGES, build
|
|
329
|
+
"""Build Micro-Manager device adapters from the git repo.
|
|
310
330
|
|
|
331
|
+
Currently only supports macos and linux.
|
|
332
|
+
"""
|
|
311
333
|
devices = DEFAULT_PACKAGES if not devices else devices
|
|
312
334
|
try:
|
|
313
335
|
build(dest, overwrite=overwrite, devices=devices)
|
|
@@ -374,6 +396,31 @@ def info() -> None:
|
|
|
374
396
|
typer.secho(f"{key:{length}}: {value}")
|
|
375
397
|
|
|
376
398
|
|
|
399
|
+
@app.command()
|
|
400
|
+
def use(
|
|
401
|
+
pattern: str = typer.Argument(
|
|
402
|
+
...,
|
|
403
|
+
help="Path to an existing directory, or pattern to match against installations "
|
|
404
|
+
"found by `mmcore list`",
|
|
405
|
+
),
|
|
406
|
+
) -> None:
|
|
407
|
+
"""Change the currently used Micro-manager version/path."""
|
|
408
|
+
from pymmcore_plus._util import use_micromanager
|
|
409
|
+
|
|
410
|
+
_pth = Path(pattern)
|
|
411
|
+
if _pth.exists():
|
|
412
|
+
if not _pth.is_dir():
|
|
413
|
+
raise typer.BadParameter("must be a directory")
|
|
414
|
+
result = use_micromanager(path=_pth)
|
|
415
|
+
else:
|
|
416
|
+
try:
|
|
417
|
+
result = use_micromanager(pattern=pattern)
|
|
418
|
+
except FileNotFoundError as e:
|
|
419
|
+
raise typer.BadParameter(str(e)) from None
|
|
420
|
+
|
|
421
|
+
typer.secho(f"using {result}", fg=typer.colors.BRIGHT_GREEN)
|
|
422
|
+
|
|
423
|
+
|
|
377
424
|
def _tail_file(file_path: Union[str, Path], interval: float = 0.1) -> None:
|
|
378
425
|
with open(file_path) as file:
|
|
379
426
|
# Move the file pointer to the end
|
|
@@ -388,5 +435,58 @@ def _tail_file(file_path: Union[str, Path], interval: float = 0.1) -> None:
|
|
|
388
435
|
time.sleep(1)
|
|
389
436
|
|
|
390
437
|
|
|
438
|
+
@app.command()
|
|
439
|
+
def bench(
|
|
440
|
+
config: Optional[Path] = CONFIG_PARAM,
|
|
441
|
+
number: int = typer.Option(
|
|
442
|
+
10, "-n", "--number", help="Number of iterations for each test."
|
|
443
|
+
),
|
|
444
|
+
) -> None:
|
|
445
|
+
"""Run a benchmark of Core and Devices loaded with `config` (or Demo)."""
|
|
446
|
+
from rich.console import Console
|
|
447
|
+
from rich.live import Live
|
|
448
|
+
from rich.table import Table
|
|
449
|
+
|
|
450
|
+
from pymmcore_plus._benchmark import benchmark_core_and_devices
|
|
451
|
+
|
|
452
|
+
console = Console()
|
|
453
|
+
|
|
454
|
+
core = CMMCorePlus()
|
|
455
|
+
if config is not None:
|
|
456
|
+
console.log(
|
|
457
|
+
f"Loading config {config} ...",
|
|
458
|
+
style="bright_blue",
|
|
459
|
+
end="",
|
|
460
|
+
)
|
|
461
|
+
core.loadSystemConfiguration(str(config))
|
|
462
|
+
else:
|
|
463
|
+
console.log("Loading DEMO configuration ...", style="bright_blue", end="")
|
|
464
|
+
core.loadSystemConfiguration()
|
|
465
|
+
console.log("Loaded.", style="bright_blue")
|
|
466
|
+
|
|
467
|
+
table = Table()
|
|
468
|
+
table.add_column("Method")
|
|
469
|
+
table.add_column("Time (ms)")
|
|
470
|
+
|
|
471
|
+
with Live(table, console=console, refresh_per_second=4):
|
|
472
|
+
for item in benchmark_core_and_devices(core, number):
|
|
473
|
+
if item is None:
|
|
474
|
+
table.add_row("Device: Core", "------", style="yellow")
|
|
475
|
+
elif isinstance(item, Device):
|
|
476
|
+
console.print(
|
|
477
|
+
f"Measuring ({item.type()}) Device: "
|
|
478
|
+
f"{item.label!r} <{item.library()}::{item.name()}>"
|
|
479
|
+
f": {item.description()}",
|
|
480
|
+
style="#333333",
|
|
481
|
+
)
|
|
482
|
+
table.add_row(f"Device: {item.label}", "------", style="yellow")
|
|
483
|
+
else:
|
|
484
|
+
method, time = item
|
|
485
|
+
if isinstance(time, float):
|
|
486
|
+
table.add_row(method, f"{time:.4f}")
|
|
487
|
+
else:
|
|
488
|
+
table.add_row(method, str(time), style="red")
|
|
489
|
+
|
|
490
|
+
|
|
391
491
|
def main() -> None: # pragma: no cover
|
|
392
492
|
app()
|
pymmcore_plus/_logger.py
CHANGED
|
@@ -6,22 +6,23 @@ import sys
|
|
|
6
6
|
from contextlib import contextmanager
|
|
7
7
|
from logging.handlers import RotatingFileHandler
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import Iterator
|
|
10
13
|
|
|
11
14
|
__all__ = ["logger"]
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
logger = logging.getLogger("pymmcore-plus")
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
PYMM_LOG_FILE = os.getenv("PYMM_LOG_FILE", "")
|
|
17
20
|
DEFAULT_LOG_LEVEL: str = os.getenv("PYMM_LOG_LEVEL", "INFO").upper()
|
|
18
|
-
|
|
21
|
+
|
|
22
|
+
if "PYTEST_RUNNING" in os.environ:
|
|
19
23
|
LOG_FILE = None
|
|
20
|
-
elif
|
|
21
|
-
|
|
22
|
-
LOG_FILE = None
|
|
23
|
-
else:
|
|
24
|
-
LOG_FILE = Path(os.environ["PYMM_LOG_FILE"]).expanduser().resolve()
|
|
24
|
+
elif PYMM_LOG_FILE not in ("", "0", "false", "no", "none"):
|
|
25
|
+
LOG_FILE = Path(PYMM_LOG_FILE).expanduser().resolve()
|
|
25
26
|
else:
|
|
26
27
|
from ._util import USER_DATA_DIR
|
|
27
28
|
|
|
@@ -117,9 +118,17 @@ def configure_logging(
|
|
|
117
118
|
|
|
118
119
|
# automatically log to stderr
|
|
119
120
|
if log_to_stderr and sys.stderr:
|
|
120
|
-
|
|
121
|
+
# try to use rich for stderr logging
|
|
122
|
+
# fallback to plain text if rich is not installed
|
|
123
|
+
try:
|
|
124
|
+
from rich.logging import RichHandler
|
|
125
|
+
|
|
126
|
+
stderr_handler: logging.Handler = RichHandler()
|
|
127
|
+
except ImportError:
|
|
128
|
+
stderr_handler = logging.StreamHandler(sys.stderr)
|
|
129
|
+
stderr_handler.setFormatter(CustomFormatter())
|
|
130
|
+
|
|
121
131
|
stderr_handler.setLevel(stderr_level)
|
|
122
|
-
stderr_handler.setFormatter(CustomFormatter())
|
|
123
132
|
logger.addHandler(stderr_handler)
|
|
124
133
|
|
|
125
134
|
# automatically log to file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Internal module to choose between pymmcore and pymmcore-nano."""
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from pymmcore_nano import * # noqa F403
|
|
5
|
+
from pymmcore_nano import __version__
|
|
6
|
+
|
|
7
|
+
BACKEND = "pymmcore-nano"
|
|
8
|
+
except ImportError:
|
|
9
|
+
from pymmcore import * # noqa F403
|
|
10
|
+
from pymmcore import __version__ # noqa F401
|
|
11
|
+
|
|
12
|
+
BACKEND = "pymmcore"
|