pymmcore-plus 0.13.4__tar.gz → 0.13.6__tar.gz
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-0.13.4 → pymmcore_plus-0.13.6}/PKG-INFO +22 -17
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/README.md +15 -13
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/pyproject.toml +16 -5
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/_cli.py +10 -3
- pymmcore_plus-0.13.6/src/pymmcore_plus/_pymmcore.py +30 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/_util.py +80 -15
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_constants.py +8 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_metadata.py +8 -5
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_mmcore_plus.py +68 -23
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_sequencing.py +5 -2
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/events/__init__.py +1 -1
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/_unicore.py +1 -1
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/devices/_properties.py +1 -1
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/install.py +68 -7
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/_engine.py +55 -19
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/events/__init__.py +1 -1
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/handlers/_tensorstore_handler.py +1 -1
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/metadata/functions.py +9 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/metadata/schema.py +19 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_config_file.py +16 -3
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_device.py +6 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_pixel_size_config.py +9 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/seq_tester.py +4 -4
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_cli.py +10 -4
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_core.py +56 -4
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_events.py +2 -2
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_model.py +12 -8
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_sequencing.py +2 -1
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/unicore/test_unicore.py +7 -2
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/unicore/test_xy_stage.py +2 -0
- pymmcore_plus-0.13.4/src/pymmcore_plus/_pymmcore.py +0 -12
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/.gitignore +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/LICENSE +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/__init__.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/_benchmark.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/_build.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/_logger.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/__init__.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_adapter.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_config.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_config_group.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_device.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_property.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/events/_device_signal_view.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/events/_norm_slot.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/events/_prop_event_mixin.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/events/_protocol.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/events/_psygnal.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/events/_qsignals.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/__init__.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/__init__.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/_device_manager.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/_proxy.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/devices/__init__.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/devices/_device.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/devices/_stage.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/__init__.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/_protocol.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/_runner.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/_thread_relay.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/events/_protocol.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/events/_psygnal.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/events/_qsignals.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/handlers/_5d_writer_base.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/handlers/__init__.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/handlers/_img_sequence_writer.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/handlers/_ome_tiff_writer.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/handlers/_ome_zarr_writer.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/handlers/_util.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/metadata/__init__.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/metadata/serialize.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mocks.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/__init__.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_config_group.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_core_device.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_core_link.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_microscope.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_property.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/py.typed +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/__init__.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/conftest.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/io/test_image_sequence_writer.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/io/test_ome_tiff.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/io/test_zarr_writers.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/local_config.cfg +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_adapter_class.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_bench.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_config_group_class.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_device_class.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_mda.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_metadata.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_misc.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_pixel_config_class.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_property_class.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_slm_image.py +0 -0
- {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_thread_relay.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pymmcore-plus
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.6
|
|
4
4
|
Summary: pymmcore superset providing improved APIs, event handling, and a pure python acquisition engine
|
|
5
5
|
Project-URL: Source, https://github.com/pymmcore-plus/pymmcore-plus
|
|
6
6
|
Project-URL: Tracker, https://github.com/pymmcore-plus/pymmcore-plus/issues
|
|
@@ -30,7 +30,7 @@ Requires-Dist: platformdirs>=3.0.0
|
|
|
30
30
|
Requires-Dist: psygnal>=0.7
|
|
31
31
|
Requires-Dist: pymmcore>=10.7.0.71.0
|
|
32
32
|
Requires-Dist: rich>=10.2.0
|
|
33
|
-
Requires-Dist: tensorstore
|
|
33
|
+
Requires-Dist: tensorstore<=0.1.71
|
|
34
34
|
Requires-Dist: typer>=0.4.2
|
|
35
35
|
Requires-Dist: typing-extensions
|
|
36
36
|
Requires-Dist: useq-schema>=0.7.0
|
|
@@ -40,11 +40,12 @@ Requires-Dist: typer>=0.4.2; extra == 'cli'
|
|
|
40
40
|
Provides-Extra: dev
|
|
41
41
|
Requires-Dist: ipython; extra == 'dev'
|
|
42
42
|
Requires-Dist: mypy; extra == 'dev'
|
|
43
|
-
Requires-Dist: pdbpp; extra == 'dev'
|
|
43
|
+
Requires-Dist: pdbpp; (sys_platform != 'win32') and extra == 'dev'
|
|
44
44
|
Requires-Dist: pre-commit; extra == 'dev'
|
|
45
45
|
Requires-Dist: ruff; extra == 'dev'
|
|
46
46
|
Requires-Dist: tensorstore-stubs; extra == 'dev'
|
|
47
47
|
Provides-Extra: docs
|
|
48
|
+
Requires-Dist: mkdocs-autorefs==1.3.1; extra == 'docs'
|
|
48
49
|
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
49
50
|
Requires-Dist: mkdocs-typer==0.0.3; extra == 'docs'
|
|
50
51
|
Requires-Dist: mkdocs>=1.4; extra == 'docs'
|
|
@@ -62,8 +63,10 @@ Requires-Dist: pyside2>=5.15; extra == 'pyside2'
|
|
|
62
63
|
Provides-Extra: pyside6
|
|
63
64
|
Requires-Dist: pyside6<6.8,>=6.4.0; extra == 'pyside6'
|
|
64
65
|
Provides-Extra: test
|
|
66
|
+
Requires-Dist: mm-device-adapters; (sys_platform == 'darwin' and platform_machine == 'x86_64') and extra == 'test'
|
|
67
|
+
Requires-Dist: mm-device-adapters; (sys_platform == 'win32') and extra == 'test'
|
|
65
68
|
Requires-Dist: msgpack; extra == 'test'
|
|
66
|
-
Requires-Dist: msgspec;
|
|
69
|
+
Requires-Dist: msgspec; extra == 'test'
|
|
67
70
|
Requires-Dist: pytest-cov>=4; extra == 'test'
|
|
68
71
|
Requires-Dist: pytest-qt>=4; extra == 'test'
|
|
69
72
|
Requires-Dist: pytest>=7.3.2; extra == 'test'
|
|
@@ -99,7 +102,7 @@ environments**.
|
|
|
99
102
|
[CMMCorePlus
|
|
100
103
|
documentation](https://pymmcore-plus.github.io/pymmcore-plus/api/cmmcoreplus/)
|
|
101
104
|
for details.
|
|
102
|
-
- `pymmcore-plus` includes an [acquisition engine](https://pymmcore-plus.github.io/pymmcore-plus/guides/mda_engine/)
|
|
105
|
+
- `pymmcore-plus` includes an [acquisition engine](https://pymmcore-plus.github.io/pymmcore-plus/guides/mda_engine/)
|
|
103
106
|
that drives micro-manager for conventional multi-dimensional experiments. It accepts an
|
|
104
107
|
[MDASequence](https://pymmcore-plus.github.io/useq-schema/schema/sequence/)
|
|
105
108
|
from [useq-schema](https://pymmcore-plus.github.io/useq-schema/) for
|
|
@@ -112,7 +115,7 @@ environments**.
|
|
|
112
115
|
|
|
113
116
|
## Documentation
|
|
114
117
|
|
|
115
|
-
https://pymmcore-plus.github.io/pymmcore-plus
|
|
118
|
+
<https://pymmcore-plus.github.io/pymmcore-plus/>
|
|
116
119
|
|
|
117
120
|
## Why not just use `pymmcore` directly?
|
|
118
121
|
|
|
@@ -139,20 +142,22 @@ python users are accustomed to. This library:
|
|
|
139
142
|
|
|
140
143
|
## How does this relate to `Pycro-Manager`?
|
|
141
144
|
|
|
142
|
-
[Pycro-Manager](https://github.com/micro-manager/pycro-manager) is
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
communicates with the Java half using ZeroMQ messaging.
|
|
145
|
+
[Pycro-Manager](https://github.com/micro-manager/pycro-manager) is designed to
|
|
146
|
+
make it easier to work with and control the Java Micro-manager application
|
|
147
|
+
(MMStudio) using python. As such, it requires Java to be installed and for
|
|
148
|
+
MMStudio to be running a server in another process. The python half communicates
|
|
149
|
+
with the Java half using ZeroMQ messaging.
|
|
148
150
|
|
|
149
151
|
**In brief**: while `Pycro-Manager` provides a python API to control the Java
|
|
150
152
|
Micro-manager application (which in turn controls the C++ core), `pymmcore-plus`
|
|
151
153
|
provides a python API to control the C++ core directly, without the need for
|
|
152
|
-
Java in the loop.
|
|
153
|
-
pycro-manager you
|
|
154
|
-
and GUI application. With pymmcore-plus
|
|
155
|
-
have direct access to the memory buffers
|
|
154
|
+
Java in the loop. Each has its own advantages and disadvantages! With
|
|
155
|
+
pycro-manager you retain the entire existing micro-manager ecosystem
|
|
156
|
+
and GUI application. With pymmcore-plus, the entire thing is python: you
|
|
157
|
+
don't need to install Java, and you have direct access to the memory buffers
|
|
158
|
+
used by the C++ core. Work on recreating the gui application in python
|
|
159
|
+
being done in [`pymmcore-widgets`](https://github.com/pymmcore-plus/pymmcore-widgets)
|
|
160
|
+
and [`pymmcore-gui`](https://github.com/pymmcore-plus/pymmcore-gui).
|
|
156
161
|
|
|
157
162
|
## Quickstart
|
|
158
163
|
|
|
@@ -191,7 +196,7 @@ mmcore install
|
|
|
191
196
|
|
|
192
197
|
(you can also download these manually from [micro-manager.org](https://micro-manager.org/Micro-Manager_Nightly_Builds))
|
|
193
198
|
|
|
194
|
-
_See [installation documentation
|
|
199
|
+
_See [installation documentation](https://pymmcore-plus.github.io/pymmcore-plus/install/) for more details._
|
|
195
200
|
|
|
196
201
|
### Usage
|
|
197
202
|
|
|
@@ -22,7 +22,7 @@ environments**.
|
|
|
22
22
|
[CMMCorePlus
|
|
23
23
|
documentation](https://pymmcore-plus.github.io/pymmcore-plus/api/cmmcoreplus/)
|
|
24
24
|
for details.
|
|
25
|
-
- `pymmcore-plus` includes an [acquisition engine](https://pymmcore-plus.github.io/pymmcore-plus/guides/mda_engine/)
|
|
25
|
+
- `pymmcore-plus` includes an [acquisition engine](https://pymmcore-plus.github.io/pymmcore-plus/guides/mda_engine/)
|
|
26
26
|
that drives micro-manager for conventional multi-dimensional experiments. It accepts an
|
|
27
27
|
[MDASequence](https://pymmcore-plus.github.io/useq-schema/schema/sequence/)
|
|
28
28
|
from [useq-schema](https://pymmcore-plus.github.io/useq-schema/) for
|
|
@@ -35,7 +35,7 @@ environments**.
|
|
|
35
35
|
|
|
36
36
|
## Documentation
|
|
37
37
|
|
|
38
|
-
https://pymmcore-plus.github.io/pymmcore-plus
|
|
38
|
+
<https://pymmcore-plus.github.io/pymmcore-plus/>
|
|
39
39
|
|
|
40
40
|
## Why not just use `pymmcore` directly?
|
|
41
41
|
|
|
@@ -62,20 +62,22 @@ python users are accustomed to. This library:
|
|
|
62
62
|
|
|
63
63
|
## How does this relate to `Pycro-Manager`?
|
|
64
64
|
|
|
65
|
-
[Pycro-Manager](https://github.com/micro-manager/pycro-manager) is
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
communicates with the Java half using ZeroMQ messaging.
|
|
65
|
+
[Pycro-Manager](https://github.com/micro-manager/pycro-manager) is designed to
|
|
66
|
+
make it easier to work with and control the Java Micro-manager application
|
|
67
|
+
(MMStudio) using python. As such, it requires Java to be installed and for
|
|
68
|
+
MMStudio to be running a server in another process. The python half communicates
|
|
69
|
+
with the Java half using ZeroMQ messaging.
|
|
71
70
|
|
|
72
71
|
**In brief**: while `Pycro-Manager` provides a python API to control the Java
|
|
73
72
|
Micro-manager application (which in turn controls the C++ core), `pymmcore-plus`
|
|
74
73
|
provides a python API to control the C++ core directly, without the need for
|
|
75
|
-
Java in the loop.
|
|
76
|
-
pycro-manager you
|
|
77
|
-
and GUI application. With pymmcore-plus
|
|
78
|
-
have direct access to the memory buffers
|
|
74
|
+
Java in the loop. Each has its own advantages and disadvantages! With
|
|
75
|
+
pycro-manager you retain the entire existing micro-manager ecosystem
|
|
76
|
+
and GUI application. With pymmcore-plus, the entire thing is python: you
|
|
77
|
+
don't need to install Java, and you have direct access to the memory buffers
|
|
78
|
+
used by the C++ core. Work on recreating the gui application in python
|
|
79
|
+
being done in [`pymmcore-widgets`](https://github.com/pymmcore-plus/pymmcore-widgets)
|
|
80
|
+
and [`pymmcore-gui`](https://github.com/pymmcore-plus/pymmcore-gui).
|
|
79
81
|
|
|
80
82
|
## Quickstart
|
|
81
83
|
|
|
@@ -114,7 +116,7 @@ mmcore install
|
|
|
114
116
|
|
|
115
117
|
(you can also download these manually from [micro-manager.org](https://micro-manager.org/Micro-Manager_Nightly_Builds))
|
|
116
118
|
|
|
117
|
-
_See [installation documentation
|
|
119
|
+
_See [installation documentation](https://pymmcore-plus.github.io/pymmcore-plus/install/) for more details._
|
|
118
120
|
|
|
119
121
|
### Usage
|
|
120
122
|
|
|
@@ -39,9 +39,10 @@ dependencies = [
|
|
|
39
39
|
"numpy >=1.17.3",
|
|
40
40
|
"psygnal >=0.7",
|
|
41
41
|
"pymmcore >=10.7.0.71.0",
|
|
42
|
-
"typing-extensions",
|
|
42
|
+
"typing-extensions", # not actually required at runtime
|
|
43
43
|
"useq-schema >=0.7.0",
|
|
44
|
-
|
|
44
|
+
# until https://github.com/google/tensorstore/issues/217 is resolved
|
|
45
|
+
"tensorstore <= 0.1.71",
|
|
45
46
|
# cli requirements included by default for now
|
|
46
47
|
"typer >=0.4.2",
|
|
47
48
|
"rich >=10.2.0",
|
|
@@ -57,7 +58,7 @@ PySide6 = ["PySide6 >=6.4.0,<6.8"]
|
|
|
57
58
|
PyQt5 = ["PyQt5 >=5.15.4"]
|
|
58
59
|
PyQt6 = ["PyQt6 >=6.4.2,<6.8"]
|
|
59
60
|
test = [
|
|
60
|
-
"msgspec
|
|
61
|
+
"msgspec",
|
|
61
62
|
"msgpack",
|
|
62
63
|
"pytest-cov >=4",
|
|
63
64
|
"pytest-qt >=4",
|
|
@@ -68,12 +69,22 @@ test = [
|
|
|
68
69
|
"tifffile >=2021.6.14",
|
|
69
70
|
"zarr >=2.2,<3",
|
|
70
71
|
"xarray",
|
|
72
|
+
"mm-device-adapters; sys_platform == 'win32'",
|
|
73
|
+
"mm-device-adapters; sys_platform == 'darwin' and platform_machine == 'x86_64'",
|
|
74
|
+
]
|
|
75
|
+
dev = [
|
|
76
|
+
"ipython",
|
|
77
|
+
"mypy",
|
|
78
|
+
"pdbpp; sys_platform != 'win32'",
|
|
79
|
+
"pre-commit",
|
|
80
|
+
"ruff",
|
|
81
|
+
"tensorstore-stubs",
|
|
71
82
|
]
|
|
72
|
-
dev = ["ipython", "mypy", "pdbpp", "pre-commit", "ruff", "tensorstore-stubs"]
|
|
73
83
|
docs = [
|
|
74
84
|
"mkdocs >=1.4",
|
|
75
85
|
"mkdocs-material",
|
|
76
86
|
"mkdocstrings ==0.22.0",
|
|
87
|
+
"mkdocs-autorefs ==1.3.1",
|
|
77
88
|
"mkdocstrings-python ==1.1.2",
|
|
78
89
|
"mkdocs-typer ==0.0.3",
|
|
79
90
|
# "griffe @ git+https://github.com/tlambert03/griffe@recursion"
|
|
@@ -193,4 +204,4 @@ ignore = [
|
|
|
193
204
|
]
|
|
194
205
|
|
|
195
206
|
[tool.typos.default]
|
|
196
|
-
extend-ignore-identifiers-re = ["(?i)nd2?.*", "(?i)ome", "anager"]
|
|
207
|
+
extend-ignore-identifiers-re = ["(?i)nd2?.*", "(?i)ome", "anager", "ba"]
|
|
@@ -8,6 +8,7 @@ from contextlib import suppress
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Optional, Union, cast
|
|
10
10
|
|
|
11
|
+
from pymmcore_plus._util import get_device_interface_version
|
|
11
12
|
from pymmcore_plus.core._device import Device
|
|
12
13
|
from pymmcore_plus.core._mmcore_plus import CMMCorePlus
|
|
13
14
|
|
|
@@ -114,9 +115,15 @@ def _list() -> None:
|
|
|
114
115
|
for parent, items in found.items():
|
|
115
116
|
print(f":file_folder:[bold green] {parent}")
|
|
116
117
|
for item in items:
|
|
118
|
+
version = ""
|
|
119
|
+
for _lib in (parent / item).glob("*_dal_*"):
|
|
120
|
+
with suppress(Exception):
|
|
121
|
+
div = get_device_interface_version(_lib)
|
|
122
|
+
version = f" (Dev. Interface {div})"
|
|
123
|
+
break
|
|
117
124
|
bullet = " [bold yellow]*" if first else " •"
|
|
118
125
|
using = " [bold blue](active)" if first else ""
|
|
119
|
-
print(f"{bullet} [cyan]{item}{using}")
|
|
126
|
+
print(f"{bullet} [cyan]{item}{version}{using}")
|
|
120
127
|
first = False
|
|
121
128
|
else:
|
|
122
129
|
print(":x: [bold red]There are no pymmcore-plus Micro-Manager files.")
|
|
@@ -157,7 +164,7 @@ def install(
|
|
|
157
164
|
help="Installation directory.",
|
|
158
165
|
),
|
|
159
166
|
release: str = typer.Option(
|
|
160
|
-
"latest", "-r", "--release", help="Release date. e.g. 20210201"
|
|
167
|
+
"latest-compatible", "-r", "--release", help="Release date. e.g. 20210201"
|
|
161
168
|
),
|
|
162
169
|
plain_output: bool = typer.Option(
|
|
163
170
|
False,
|
|
@@ -289,7 +296,7 @@ def run(
|
|
|
289
296
|
mda.setdefault("channels", []).append(_c)
|
|
290
297
|
if channel_group is not None:
|
|
291
298
|
for c in mda.get("channels", []):
|
|
292
|
-
cast(dict, c)["group"] = channel_group
|
|
299
|
+
cast("dict", c)["group"] = channel_group
|
|
293
300
|
|
|
294
301
|
# this will raise if anything has gone wrong.
|
|
295
302
|
_mda = MDASequence(**mda)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Internal module to choose between pymmcore and pymmcore-nano."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import NamedTuple
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from pymmcore_nano import * # noqa F403
|
|
8
|
+
from pymmcore_nano import __version__
|
|
9
|
+
|
|
10
|
+
BACKEND = "pymmcore-nano"
|
|
11
|
+
NANO = True
|
|
12
|
+
except ImportError:
|
|
13
|
+
from pymmcore import * # noqa F403
|
|
14
|
+
from pymmcore import __version__
|
|
15
|
+
|
|
16
|
+
BACKEND = "pymmcore"
|
|
17
|
+
NANO = False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class VersionInfo(NamedTuple):
|
|
21
|
+
"""Version info for the backend."""
|
|
22
|
+
|
|
23
|
+
major: int
|
|
24
|
+
minor: int
|
|
25
|
+
micro: int
|
|
26
|
+
device_interface: int
|
|
27
|
+
build: int
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
version_info = VersionInfo(*(int(x) for x in re.findall(r"\d+", __version__)))
|
|
@@ -94,7 +94,7 @@ def find_micromanager(return_first: bool = True) -> str | None | list[str]:
|
|
|
94
94
|
"""
|
|
95
95
|
from ._logger import logger
|
|
96
96
|
|
|
97
|
-
# we use a dict here to avoid duplicates
|
|
97
|
+
# we use a dict here to avoid duplicates, while retaining order
|
|
98
98
|
full_list: dict[str, None] = {}
|
|
99
99
|
|
|
100
100
|
# environment variable takes precedence
|
|
@@ -114,13 +114,42 @@ def find_micromanager(return_first: bool = True) -> str | None | list[str]:
|
|
|
114
114
|
return path
|
|
115
115
|
full_list[path] = None
|
|
116
116
|
|
|
117
|
+
# then look for mm-device-adapters
|
|
118
|
+
with suppress(ImportError):
|
|
119
|
+
import mm_device_adapters
|
|
120
|
+
|
|
121
|
+
from . import _pymmcore
|
|
122
|
+
|
|
123
|
+
mm_dev_div = mm_device_adapters.__version__.split(".")[0]
|
|
124
|
+
pymm_div = str(_pymmcore.version_info.device_interface)
|
|
125
|
+
|
|
126
|
+
if pymm_div != mm_dev_div: # pragma: no cover
|
|
127
|
+
warnings.warn(
|
|
128
|
+
"mm-device-adapters installed, but its device interface "
|
|
129
|
+
f"version ({mm_dev_div}) "
|
|
130
|
+
f"does not match the device interface version of {_pymmcore.BACKEND}"
|
|
131
|
+
f"({pymm_div}). You may wish to run"
|
|
132
|
+
f" `pip install --force-reinstall mm-device-adapters=={pymm_div}`. "
|
|
133
|
+
"mm-device-adapters will be ignored.",
|
|
134
|
+
stacklevel=2,
|
|
135
|
+
)
|
|
136
|
+
else:
|
|
137
|
+
path = mm_device_adapters.device_adapter_path()
|
|
138
|
+
if return_first:
|
|
139
|
+
logger.debug("using MM path from mm-device-adapters: %s", path)
|
|
140
|
+
return str(path)
|
|
141
|
+
full_list[path] = None
|
|
142
|
+
|
|
117
143
|
# then look in user_data_dir
|
|
118
144
|
_folders = (p for p in USER_DATA_MM_PATH.glob("Micro-Manager*") if p.is_dir())
|
|
119
|
-
user_install
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
145
|
+
if user_install := sorted(_folders, reverse=True):
|
|
146
|
+
if return_first and (
|
|
147
|
+
first := next(
|
|
148
|
+
(x for x in user_install if _mm_path_has_compatible_div(x)), None
|
|
149
|
+
)
|
|
150
|
+
):
|
|
151
|
+
logger.debug("using MM path from user install: %s", first)
|
|
152
|
+
return str(first)
|
|
124
153
|
for x in user_install:
|
|
125
154
|
full_list[str(x)] = None
|
|
126
155
|
|
|
@@ -130,9 +159,13 @@ def find_micromanager(return_first: bool = True) -> str | None | list[str]:
|
|
|
130
159
|
p for p in PYMMCORE_PLUS_PATH.glob(f"**/Micro-Manager*{sfx}") if p.is_dir()
|
|
131
160
|
]
|
|
132
161
|
if local_install:
|
|
133
|
-
if return_first
|
|
134
|
-
|
|
135
|
-
|
|
162
|
+
if return_first and (
|
|
163
|
+
first := next(
|
|
164
|
+
(x for x in local_install if _mm_path_has_compatible_div(x)), None
|
|
165
|
+
)
|
|
166
|
+
): # pragma: no cover
|
|
167
|
+
logger.debug("using MM path from local install: %s", first)
|
|
168
|
+
return str(first)
|
|
136
169
|
for x in local_install:
|
|
137
170
|
full_list[str(x)] = None
|
|
138
171
|
|
|
@@ -153,8 +186,9 @@ def find_micromanager(return_first: bool = True) -> str | None | list[str]:
|
|
|
153
186
|
"could not find micromanager directory. Please run 'mmcore install'"
|
|
154
187
|
)
|
|
155
188
|
return None
|
|
156
|
-
|
|
157
|
-
|
|
189
|
+
if _mm_path_has_compatible_div(pth): # pragma: no cover
|
|
190
|
+
logger.debug("using MM path found in applications: %s", pth)
|
|
191
|
+
return str(pth)
|
|
158
192
|
if pth is not None:
|
|
159
193
|
full_list[str(pth)] = None
|
|
160
194
|
return list(full_list)
|
|
@@ -229,15 +263,15 @@ def _qt_app_is_running() -> bool:
|
|
|
229
263
|
return False # pragma: no cover
|
|
230
264
|
|
|
231
265
|
|
|
232
|
-
|
|
266
|
+
PYMM_SIGNALS_BACKEND = "PYMM_SIGNALS_BACKEND"
|
|
233
267
|
|
|
234
268
|
|
|
235
269
|
def signals_backend() -> Literal["qt", "psygnal"]:
|
|
236
270
|
"""Return the name of the event backend to use."""
|
|
237
|
-
env_var = os.environ.get(
|
|
271
|
+
env_var = os.environ.get(PYMM_SIGNALS_BACKEND, "auto").lower()
|
|
238
272
|
if env_var not in {"qt", "psygnal", "auto"}:
|
|
239
273
|
warnings.warn(
|
|
240
|
-
f"{
|
|
274
|
+
f"{PYMM_SIGNALS_BACKEND} must be one of ['qt', 'psygnal', 'auto']. "
|
|
241
275
|
f"not: {env_var!r}. Using 'auto'.",
|
|
242
276
|
stacklevel=1,
|
|
243
277
|
)
|
|
@@ -250,7 +284,7 @@ def signals_backend() -> Literal["qt", "psygnal"]:
|
|
|
250
284
|
if qt_app_running or list(_imported_qt_modules()):
|
|
251
285
|
return "qt"
|
|
252
286
|
warnings.warn(
|
|
253
|
-
f"{
|
|
287
|
+
f"{PYMM_SIGNALS_BACKEND} set to 'qt', but no Qt app is running. "
|
|
254
288
|
"Falling back to 'psygnal'.",
|
|
255
289
|
stacklevel=1,
|
|
256
290
|
)
|
|
@@ -618,3 +652,34 @@ def timestamp() -> str:
|
|
|
618
652
|
with suppress(Exception):
|
|
619
653
|
now = now.astimezone()
|
|
620
654
|
return now.isoformat()
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def get_device_interface_version(lib_path: str | Path) -> int:
|
|
658
|
+
"""Return the device interface version from the given library path."""
|
|
659
|
+
import ctypes
|
|
660
|
+
|
|
661
|
+
if sys.platform.startswith("win"):
|
|
662
|
+
lib = ctypes.WinDLL(lib_path)
|
|
663
|
+
else:
|
|
664
|
+
lib = ctypes.CDLL(lib_path)
|
|
665
|
+
|
|
666
|
+
try:
|
|
667
|
+
func = lib.GetDeviceInterfaceVersion
|
|
668
|
+
except AttributeError:
|
|
669
|
+
raise RuntimeError(
|
|
670
|
+
f"Function 'GetDeviceInterfaceVersion' not found in {lib_path}"
|
|
671
|
+
) from None
|
|
672
|
+
|
|
673
|
+
func.restype = ctypes.c_long
|
|
674
|
+
func.argtypes = []
|
|
675
|
+
return func() # type: ignore[no-any-return]
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
def _mm_path_has_compatible_div(folder: Path | str) -> bool:
|
|
679
|
+
from . import _pymmcore
|
|
680
|
+
|
|
681
|
+
div = _pymmcore.version_info.device_interface
|
|
682
|
+
for lib_path in Path(folder).glob("*mmgr_dal*"):
|
|
683
|
+
with suppress(Exception):
|
|
684
|
+
return get_device_interface_version(lib_path) == div
|
|
685
|
+
return False # pragma: no cover
|
|
@@ -94,6 +94,14 @@ class CFGCommand(str, Enum):
|
|
|
94
94
|
PixelSizeAffine = pymmcore.g_CFGCommand_PixelSizeAffine
|
|
95
95
|
ParentID = pymmcore.g_CFGCommand_ParentID
|
|
96
96
|
FocusDirection = pymmcore.g_CFGCommand_FocusDirection
|
|
97
|
+
|
|
98
|
+
if hasattr(pymmcore, "g_CFGCommand_PixelSizedxdz"):
|
|
99
|
+
PixelSize_dxdz = pymmcore.g_CFGCommand_PixelSizedxdz
|
|
100
|
+
if hasattr(pymmcore, "g_CFGCommand_PixelSizedydz"):
|
|
101
|
+
PixelSize_dydz = pymmcore.g_CFGCommand_PixelSizedydz
|
|
102
|
+
if hasattr(pymmcore, "g_CFGCommand_PixelSizeOptimalZUm"):
|
|
103
|
+
PixelSize_OptimalZUm = pymmcore.g_CFGCommand_PixelSizeOptimalZUm
|
|
104
|
+
|
|
97
105
|
#
|
|
98
106
|
FieldDelimiters = pymmcore.g_FieldDelimiters
|
|
99
107
|
|
|
@@ -20,8 +20,7 @@ class Metadata(pymmcore.Metadata):
|
|
|
20
20
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
21
21
|
super().__init__()
|
|
22
22
|
if args and isinstance(args[0], Mapping):
|
|
23
|
-
|
|
24
|
-
self[k] = v
|
|
23
|
+
kwargs = {**args[0], **kwargs}
|
|
25
24
|
for k, v in kwargs.items():
|
|
26
25
|
self[k] = v
|
|
27
26
|
|
|
@@ -76,15 +75,19 @@ class Metadata(pymmcore.Metadata):
|
|
|
76
75
|
return json.dumps(dict(self))
|
|
77
76
|
|
|
78
77
|
def keys(self) -> KeysView[str]:
|
|
79
|
-
return cast(KeysView, metadata_keys(self))
|
|
78
|
+
return cast("KeysView", metadata_keys(self))
|
|
80
79
|
|
|
81
80
|
def items(self) -> ItemsView[str, str]:
|
|
82
|
-
return cast(ItemsView, metadata_items(self))
|
|
81
|
+
return cast("ItemsView", metadata_items(self))
|
|
83
82
|
|
|
84
83
|
def values(self) -> ValuesView[str]:
|
|
85
|
-
return cast(ValuesView, metadata_values(self))
|
|
84
|
+
return cast("ValuesView", metadata_values(self))
|
|
86
85
|
|
|
87
86
|
|
|
88
87
|
metadata_keys = new_class("metadata_keys", (KeysView,), {})
|
|
89
88
|
metadata_items = new_class("metadata_items", (ItemsView,), {})
|
|
90
89
|
metadata_values = new_class("metadata_values", (ValuesView,), {})
|
|
90
|
+
|
|
91
|
+
# Register the new classes with the `collections.abc` module
|
|
92
|
+
# so that isistance() works as expected.
|
|
93
|
+
Mapping.register(Metadata)
|