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.
Files changed (96) hide show
  1. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/PKG-INFO +22 -17
  2. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/README.md +15 -13
  3. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/pyproject.toml +16 -5
  4. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/_cli.py +10 -3
  5. pymmcore_plus-0.13.6/src/pymmcore_plus/_pymmcore.py +30 -0
  6. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/_util.py +80 -15
  7. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_constants.py +8 -0
  8. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_metadata.py +8 -5
  9. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_mmcore_plus.py +68 -23
  10. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_sequencing.py +5 -2
  11. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/events/__init__.py +1 -1
  12. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/_unicore.py +1 -1
  13. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/devices/_properties.py +1 -1
  14. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/install.py +68 -7
  15. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/_engine.py +55 -19
  16. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/events/__init__.py +1 -1
  17. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/handlers/_tensorstore_handler.py +1 -1
  18. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/metadata/functions.py +9 -0
  19. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/metadata/schema.py +19 -0
  20. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_config_file.py +16 -3
  21. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_device.py +6 -0
  22. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_pixel_size_config.py +9 -0
  23. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/seq_tester.py +4 -4
  24. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_cli.py +10 -4
  25. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_core.py +56 -4
  26. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_events.py +2 -2
  27. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_model.py +12 -8
  28. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_sequencing.py +2 -1
  29. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/unicore/test_unicore.py +7 -2
  30. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/unicore/test_xy_stage.py +2 -0
  31. pymmcore_plus-0.13.4/src/pymmcore_plus/_pymmcore.py +0 -12
  32. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/.gitignore +0 -0
  33. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/LICENSE +0 -0
  34. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/__init__.py +0 -0
  35. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/_benchmark.py +0 -0
  36. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/_build.py +0 -0
  37. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/_logger.py +0 -0
  38. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/__init__.py +0 -0
  39. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_adapter.py +0 -0
  40. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_config.py +0 -0
  41. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_config_group.py +0 -0
  42. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_device.py +0 -0
  43. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/_property.py +0 -0
  44. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/events/_device_signal_view.py +0 -0
  45. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/events/_norm_slot.py +0 -0
  46. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/events/_prop_event_mixin.py +0 -0
  47. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/events/_protocol.py +0 -0
  48. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/events/_psygnal.py +0 -0
  49. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/core/events/_qsignals.py +0 -0
  50. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/__init__.py +0 -0
  51. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/__init__.py +0 -0
  52. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/_device_manager.py +0 -0
  53. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/_proxy.py +0 -0
  54. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/devices/__init__.py +0 -0
  55. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/devices/_device.py +0 -0
  56. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/experimental/unicore/devices/_stage.py +0 -0
  57. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/__init__.py +0 -0
  58. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/_protocol.py +0 -0
  59. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/_runner.py +0 -0
  60. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/_thread_relay.py +0 -0
  61. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/events/_protocol.py +0 -0
  62. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/events/_psygnal.py +0 -0
  63. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/events/_qsignals.py +0 -0
  64. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/handlers/_5d_writer_base.py +0 -0
  65. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/handlers/__init__.py +0 -0
  66. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/handlers/_img_sequence_writer.py +0 -0
  67. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/handlers/_ome_tiff_writer.py +0 -0
  68. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/handlers/_ome_zarr_writer.py +0 -0
  69. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mda/handlers/_util.py +0 -0
  70. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/metadata/__init__.py +0 -0
  71. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/metadata/serialize.py +0 -0
  72. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/mocks.py +0 -0
  73. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/__init__.py +0 -0
  74. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_config_group.py +0 -0
  75. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_core_device.py +0 -0
  76. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_core_link.py +0 -0
  77. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_microscope.py +0 -0
  78. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/model/_property.py +0 -0
  79. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/src/pymmcore_plus/py.typed +0 -0
  80. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/__init__.py +0 -0
  81. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/conftest.py +0 -0
  82. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/io/test_image_sequence_writer.py +0 -0
  83. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/io/test_ome_tiff.py +0 -0
  84. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/io/test_zarr_writers.py +0 -0
  85. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/local_config.cfg +0 -0
  86. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_adapter_class.py +0 -0
  87. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_bench.py +0 -0
  88. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_config_group_class.py +0 -0
  89. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_device_class.py +0 -0
  90. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_mda.py +0 -0
  91. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_metadata.py +0 -0
  92. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_misc.py +0 -0
  93. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_pixel_config_class.py +0 -0
  94. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_property_class.py +0 -0
  95. {pymmcore_plus-0.13.4 → pymmcore_plus-0.13.6}/tests/test_slm_image.py +0 -0
  96. {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.4
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; python_version < '3.13'
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; (python_version < '3.13') and extra == 'test'
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 an impressive
143
- library written by Henry Pinkard designed to make it easier to work with and
144
- control the Java Micro-manager application using python. As such, it requires
145
- Java to be installed and running in the background (either via the micro-manager
146
- GUI application directly, or via a headless process). The python half
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. Each has its own advantages and disadvantages! With
153
- pycro-manager you immediately get the entire existing micro-manager ecosystem
154
- and GUI application. With pymmcore-plus you don't need to install Java, and you
155
- have direct access to the memory buffers used by the C++ core.
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 ](https://pymmcore-plus.github.io/pymmcore-plus/install/) for more details._
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 an impressive
66
- library written by Henry Pinkard designed to make it easier to work with and
67
- control the Java Micro-manager application using python. As such, it requires
68
- Java to be installed and running in the background (either via the micro-manager
69
- GUI application directly, or via a headless process). The python half
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. Each has its own advantages and disadvantages! With
76
- pycro-manager you immediately get the entire existing micro-manager ecosystem
77
- and GUI application. With pymmcore-plus you don't need to install Java, and you
78
- have direct access to the memory buffers used by the C++ core.
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 ](https://pymmcore-plus.github.io/pymmcore-plus/install/) for more details._
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", # not actually required at runtime
42
+ "typing-extensions", # not actually required at runtime
43
43
  "useq-schema >=0.7.0",
44
- "tensorstore; python_version < '3.13'",
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; python_version < '3.13'",
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 = sorted(_folders, reverse=True)
120
- if user_install:
121
- if return_first:
122
- logger.debug("using MM path from user install: %s", user_install[0])
123
- return str(user_install[0])
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
- logger.debug("using MM path from local install: %s", local_install[0])
135
- return str(local_install[0])
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
- logger.debug("using MM path found in applications: %s", pth)
157
- return str(pth)
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
- MMCORE_PLUS_SIGNALS_BACKEND = "MMCORE_PLUS_SIGNALS_BACKEND"
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(MMCORE_PLUS_SIGNALS_BACKEND, "auto").lower()
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"{MMCORE_PLUS_SIGNALS_BACKEND} must be one of ['qt', 'psygnal', 'auto']. "
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"{MMCORE_PLUS_SIGNALS_BACKEND} set to 'qt', but no Qt app is running. "
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
- for k, v in args[0].items():
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)