pymmcore-plus 0.9.3__tar.gz → 0.13.0__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 (99) hide show
  1. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/.gitignore +2 -1
  2. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/PKG-INFO +22 -12
  3. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/README.md +2 -3
  4. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/pyproject.toml +26 -13
  5. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/__init__.py +7 -4
  6. pymmcore_plus-0.13.0/src/pymmcore_plus/_benchmark.py +203 -0
  7. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/_build.py +6 -1
  8. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/_cli.py +131 -31
  9. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/_logger.py +19 -10
  10. pymmcore_plus-0.13.0/src/pymmcore_plus/_pymmcore.py +12 -0
  11. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/_util.py +139 -32
  12. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/__init__.py +5 -0
  13. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/_config.py +6 -4
  14. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/_config_group.py +4 -3
  15. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/_constants.py +135 -10
  16. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/_device.py +4 -4
  17. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/_metadata.py +3 -3
  18. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/_mmcore_plus.py +254 -170
  19. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/_property.py +6 -6
  20. pymmcore_plus-0.13.0/src/pymmcore_plus/core/_sequencing.py +434 -0
  21. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/events/__init__.py +6 -6
  22. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/events/_device_signal_view.py +8 -6
  23. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/events/_norm_slot.py +2 -4
  24. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/events/_prop_event_mixin.py +7 -4
  25. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/events/_protocol.py +5 -2
  26. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/events/_psygnal.py +2 -2
  27. pymmcore_plus-0.13.0/src/pymmcore_plus/experimental/unicore/__init__.py +14 -0
  28. pymmcore_plus-0.13.0/src/pymmcore_plus/experimental/unicore/_device_manager.py +173 -0
  29. pymmcore_plus-0.13.0/src/pymmcore_plus/experimental/unicore/_proxy.py +127 -0
  30. pymmcore_plus-0.13.0/src/pymmcore_plus/experimental/unicore/_unicore.py +703 -0
  31. pymmcore_plus-0.13.0/src/pymmcore_plus/experimental/unicore/devices/__init__.py +0 -0
  32. pymmcore_plus-0.13.0/src/pymmcore_plus/experimental/unicore/devices/_device.py +269 -0
  33. pymmcore_plus-0.13.0/src/pymmcore_plus/experimental/unicore/devices/_properties.py +400 -0
  34. pymmcore_plus-0.13.0/src/pymmcore_plus/experimental/unicore/devices/_stage.py +221 -0
  35. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/install.py +16 -11
  36. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/mda/_engine.py +320 -148
  37. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/mda/_protocol.py +6 -4
  38. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/mda/_runner.py +62 -51
  39. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/mda/_thread_relay.py +5 -3
  40. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/mda/events/__init__.py +2 -2
  41. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/mda/events/_protocol.py +10 -2
  42. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/mda/events/_psygnal.py +2 -2
  43. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/mda/handlers/_5d_writer_base.py +106 -15
  44. pymmcore_plus-0.13.0/src/pymmcore_plus/mda/handlers/__init__.py +11 -0
  45. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/mda/handlers/_img_sequence_writer.py +11 -6
  46. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/mda/handlers/_ome_tiff_writer.py +8 -4
  47. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/mda/handlers/_ome_zarr_writer.py +82 -9
  48. pymmcore_plus-0.13.0/src/pymmcore_plus/mda/handlers/_tensorstore_handler.py +374 -0
  49. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/mda/handlers/_util.py +1 -1
  50. pymmcore_plus-0.13.0/src/pymmcore_plus/metadata/__init__.py +36 -0
  51. pymmcore_plus-0.13.0/src/pymmcore_plus/metadata/functions.py +353 -0
  52. pymmcore_plus-0.13.0/src/pymmcore_plus/metadata/schema.py +472 -0
  53. pymmcore_plus-0.13.0/src/pymmcore_plus/metadata/serialize.py +120 -0
  54. pymmcore_plus-0.13.0/src/pymmcore_plus/mocks.py +51 -0
  55. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/model/_config_file.py +5 -6
  56. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/model/_config_group.py +29 -2
  57. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/model/_core_device.py +12 -1
  58. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/model/_core_link.py +2 -1
  59. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/model/_device.py +39 -8
  60. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/model/_microscope.py +39 -3
  61. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/model/_pixel_size_config.py +27 -4
  62. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/model/_property.py +13 -3
  63. pymmcore_plus-0.13.0/src/pymmcore_plus/py.typed +0 -0
  64. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/seq_tester.py +1 -1
  65. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/conftest.py +49 -10
  66. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/io/test_image_sequence_writer.py +1 -0
  67. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/io/test_ome_tiff.py +1 -0
  68. pymmcore_plus-0.13.0/tests/io/test_zarr_writers.py +247 -0
  69. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/test_adapter_class.py +1 -1
  70. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/test_bench.py +15 -0
  71. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/test_cli.py +70 -20
  72. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/test_config_group_class.py +1 -0
  73. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/test_core.py +51 -111
  74. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/test_device_class.py +1 -0
  75. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/test_events.py +67 -115
  76. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/test_mda.py +81 -22
  77. pymmcore_plus-0.13.0/tests/test_metadata.py +123 -0
  78. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/test_misc.py +1 -0
  79. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/test_model.py +21 -1
  80. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/test_pixel_config_class.py +1 -1
  81. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/test_property_class.py +1 -0
  82. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/test_sequencing.py +20 -23
  83. pymmcore_plus-0.13.0/tests/test_slm_image.py +68 -0
  84. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/test_thread_relay.py +1 -0
  85. pymmcore_plus-0.13.0/tests/unicore/test_unicore.py +286 -0
  86. pymmcore_plus-0.13.0/tests/unicore/test_xy_stage.py +199 -0
  87. pymmcore_plus-0.9.3/src/pymmcore_plus/core/_sequencing.py +0 -297
  88. pymmcore_plus-0.9.3/src/pymmcore_plus/core/_state.py +0 -244
  89. pymmcore_plus-0.9.3/src/pymmcore_plus/mda/handlers/__init__.py +0 -5
  90. pymmcore_plus-0.9.3/tests/io/test_ome_zarr.py +0 -109
  91. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/LICENSE +0 -0
  92. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/_adapter.py +0 -0
  93. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/core/events/_qsignals.py +0 -0
  94. pymmcore_plus-0.9.3/src/pymmcore_plus/py.typed → pymmcore_plus-0.13.0/src/pymmcore_plus/experimental/__init__.py +0 -0
  95. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/mda/__init__.py +1 -1
  96. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/mda/events/_qsignals.py +0 -0
  97. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/src/pymmcore_plus/model/__init__.py +0 -0
  98. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/__init__.py +0 -0
  99. {pymmcore_plus-0.9.3 → pymmcore_plus-0.13.0}/tests/local_config.cfg +0 -0
@@ -139,10 +139,11 @@ cython_debug/
139
139
 
140
140
  Micro-Manager-*
141
141
  .vscode
142
+ .idea
142
143
 
143
144
  docs/_includes/_cmmcore_members.md
144
145
  docs/_includes/_cmmcore_table.md
145
146
  docs/_includes/_cmmcoreplus_members.md
146
147
 
147
- example_*.ome.tiff
148
+ example_*.some.tiff
148
149
  example.zarr
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pymmcore-plus
3
- Version: 0.9.3
3
+ Version: 0.13.0
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
@@ -16,24 +16,24 @@ Classifier: License :: OSI Approved :: BSD License
16
16
  Classifier: Operating System :: OS Independent
17
17
  Classifier: Programming Language :: Python
18
18
  Classifier: Programming Language :: Python :: 3
19
- Classifier: Programming Language :: Python :: 3.8
20
19
  Classifier: Programming Language :: Python :: 3.9
21
20
  Classifier: Programming Language :: Python :: 3.10
22
21
  Classifier: Programming Language :: Python :: 3.11
23
22
  Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
24
  Classifier: Topic :: System :: Hardware
25
25
  Classifier: Topic :: System :: Hardware :: Hardware Drivers
26
26
  Classifier: Topic :: Utilities
27
- Requires-Python: >=3.8
27
+ Requires-Python: >=3.9
28
28
  Requires-Dist: numpy>=1.17.3
29
29
  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
34
  Requires-Dist: typer>=0.4.2
34
35
  Requires-Dist: typing-extensions
35
- Requires-Dist: useq-schema>=0.4.7
36
- Requires-Dist: wrapt>=1.14
36
+ Requires-Dist: useq-schema>=0.6.2
37
37
  Provides-Extra: cli
38
38
  Requires-Dist: rich>=10.2.0; extra == 'cli'
39
39
  Requires-Dist: typer>=0.4.2; extra == 'cli'
@@ -43,17 +43,27 @@ Requires-Dist: mypy; extra == 'dev'
43
43
  Requires-Dist: pdbpp; extra == 'dev'
44
44
  Requires-Dist: pre-commit; extra == 'dev'
45
45
  Requires-Dist: ruff; extra == 'dev'
46
+ Requires-Dist: tensorstore-stubs; extra == 'dev'
46
47
  Provides-Extra: docs
47
48
  Requires-Dist: mkdocs-material; extra == 'docs'
49
+ Requires-Dist: mkdocs-typer==0.0.3; extra == 'docs'
48
50
  Requires-Dist: mkdocs>=1.4; extra == 'docs'
49
51
  Requires-Dist: mkdocstrings-python==1.1.2; extra == 'docs'
50
52
  Requires-Dist: mkdocstrings==0.22.0; extra == 'docs'
51
53
  Provides-Extra: io
52
54
  Requires-Dist: tifffile>=2021.6.14; extra == 'io'
53
- Requires-Dist: zarr>=2.2; extra == 'io'
55
+ Requires-Dist: zarr<3,>=2.2; extra == 'io'
56
+ Provides-Extra: pyqt5
57
+ Requires-Dist: pyqt5>=5.15.4; extra == 'pyqt5'
58
+ Provides-Extra: pyqt6
59
+ Requires-Dist: pyqt6<6.8,>=6.4.2; extra == 'pyqt6'
60
+ Provides-Extra: pyside2
61
+ Requires-Dist: pyside2>=5.15; extra == 'pyside2'
62
+ Provides-Extra: pyside6
63
+ Requires-Dist: pyside6<6.8,>=6.4.0; extra == 'pyside6'
54
64
  Provides-Extra: test
55
65
  Requires-Dist: msgpack; extra == 'test'
56
- Requires-Dist: pyside6; extra == 'test'
66
+ Requires-Dist: msgspec; (python_version < '3.13') and extra == 'test'
57
67
  Requires-Dist: pytest-cov>=4; extra == 'test'
58
68
  Requires-Dist: pytest-qt>=4; extra == 'test'
59
69
  Requires-Dist: pytest>=7.3.2; extra == 'test'
@@ -61,7 +71,8 @@ Requires-Dist: qtpy>=2; extra == 'test'
61
71
  Requires-Dist: rich; extra == 'test'
62
72
  Requires-Dist: tifffile>=2021.6.14; extra == 'test'
63
73
  Requires-Dist: typer>=0.4.2; extra == 'test'
64
- Requires-Dist: zarr>=2.2; extra == 'test'
74
+ Requires-Dist: xarray; extra == 'test'
75
+ Requires-Dist: zarr<3,>=2.2; extra == 'test'
65
76
  Description-Content-Type: text/markdown
66
77
 
67
78
  # pymmcore-plus
@@ -70,7 +81,7 @@ Description-Content-Type: text/markdown
70
81
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pymmcore-plus)](https://pypi.org/project/pymmcore-plus)
71
82
  [![PyPI](https://img.shields.io/pypi/v/pymmcore-plus.svg?color=green)](https://pypi.org/project/pymmcore-plus)
72
83
  [![Conda](https://img.shields.io/conda/vn/conda-forge/pymmcore-plus)](https://anaconda.org/conda-forge/pymmcore-plus)
73
- [![CI](https://github.com/pymmcore-plus/pymmcore-plus/actions/workflows/test_and_deploy.yml/badge.svg)](https://github.com/pymmcore-plus/pymmcore-plus/actions/workflows/test_and_deploy.yml)
84
+ [![CI](https://github.com/pymmcore-plus/pymmcore-plus/actions/workflows/ci.yml/badge.svg)](https://github.com/pymmcore-plus/pymmcore-plus/actions/workflows/ci.yml)
74
85
  [![docs](https://github.com/pymmcore-plus/pymmcore-plus/actions/workflows/docs.yml/badge.svg)](https://pymmcore-plus.github.io/pymmcore-plus/)
75
86
  [![codecov](https://codecov.io/gh/pymmcore-plus/pymmcore-plus/branch/main/graph/badge.svg)](https://codecov.io/gh/pymmcore-plus/pymmcore-plus)
76
87
  [![Benchmarks](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/pymmcore-plus/pymmcore-plus)
@@ -141,8 +152,7 @@ provides a python API to control the C++ core directly, without the need for
141
152
  Java in the loop. Each has its own advantages and disadvantages! With
142
153
  pycro-manager you immediately get the entire existing micro-manager ecosystem
143
154
  and GUI application. With pymmcore-plus you don't need to install Java, and you
144
- have direct access to the memory buffers used by the C++ core, but the GUI
145
- side of things is far less mature.
155
+ have direct access to the memory buffers used by the C++ core.
146
156
 
147
157
  ## Quickstart
148
158
 
@@ -4,7 +4,7 @@
4
4
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pymmcore-plus)](https://pypi.org/project/pymmcore-plus)
5
5
  [![PyPI](https://img.shields.io/pypi/v/pymmcore-plus.svg?color=green)](https://pypi.org/project/pymmcore-plus)
6
6
  [![Conda](https://img.shields.io/conda/vn/conda-forge/pymmcore-plus)](https://anaconda.org/conda-forge/pymmcore-plus)
7
- [![CI](https://github.com/pymmcore-plus/pymmcore-plus/actions/workflows/test_and_deploy.yml/badge.svg)](https://github.com/pymmcore-plus/pymmcore-plus/actions/workflows/test_and_deploy.yml)
7
+ [![CI](https://github.com/pymmcore-plus/pymmcore-plus/actions/workflows/ci.yml/badge.svg)](https://github.com/pymmcore-plus/pymmcore-plus/actions/workflows/ci.yml)
8
8
  [![docs](https://github.com/pymmcore-plus/pymmcore-plus/actions/workflows/docs.yml/badge.svg)](https://pymmcore-plus.github.io/pymmcore-plus/)
9
9
  [![codecov](https://codecov.io/gh/pymmcore-plus/pymmcore-plus/branch/main/graph/badge.svg)](https://codecov.io/gh/pymmcore-plus/pymmcore-plus)
10
10
  [![Benchmarks](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/pymmcore-plus/pymmcore-plus)
@@ -75,8 +75,7 @@ provides a python API to control the C++ core directly, without the need for
75
75
  Java in the loop. Each has its own advantages and disadvantages! With
76
76
  pycro-manager you immediately get the entire existing micro-manager ecosystem
77
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, but the GUI
79
- side of things is far less mature.
78
+ have direct access to the memory buffers used by the C++ core.
80
79
 
81
80
  ## Quickstart
82
81
 
@@ -9,7 +9,7 @@ name = "pymmcore-plus"
9
9
  description = "pymmcore superset providing improved APIs, event handling, and a pure python acquisition engine"
10
10
  keywords = ["microscope", "micro-manager", "smart-microscopy"]
11
11
  readme = "README.md"
12
- requires-python = ">=3.8"
12
+ requires-python = ">=3.9"
13
13
  license = { text = "BSD 3-Clause License" }
14
14
  authors = [
15
15
  { name = "Talley Lambert", email = "talley.lambert@gmail.com" },
@@ -24,11 +24,11 @@ classifiers = [
24
24
  "Operating System :: OS Independent",
25
25
  "Programming Language :: Python",
26
26
  "Programming Language :: Python :: 3",
27
- "Programming Language :: Python :: 3.8",
28
27
  "Programming Language :: Python :: 3.9",
29
28
  "Programming Language :: Python :: 3.10",
30
29
  "Programming Language :: Python :: 3.11",
31
30
  "Programming Language :: Python :: 3.12",
31
+ "Programming Language :: Python :: 3.13",
32
32
  "Topic :: System :: Hardware",
33
33
  "Topic :: System :: Hardware :: Hardware Drivers",
34
34
  "Topic :: Utilities",
@@ -39,9 +39,9 @@ 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
43
- "useq-schema >=0.4.7",
44
- "wrapt >=1.14",
42
+ "typing-extensions", # not actually required at runtime
43
+ "useq-schema >=0.6.2",
44
+ "tensorstore; python_version < '3.13'",
45
45
  # cli requirements included by default for now
46
46
  "typer >=0.4.2",
47
47
  "rich >=10.2.0",
@@ -51,10 +51,14 @@ dependencies = [
51
51
  # https://peps.python.org/pep-0621/#dependencies-optional-dependencies
52
52
  [project.optional-dependencies]
53
53
  cli = ["typer >=0.4.2", "rich >=10.2.0"]
54
- io = ["tifffile >=2021.6.14", "zarr >=2.2"]
54
+ io = ["tifffile >=2021.6.14", "zarr >=2.2,<3"]
55
+ PySide2 = ["PySide2 >=5.15"]
56
+ PySide6 = ["PySide6 >=6.4.0,<6.8"]
57
+ PyQt5 = ["PyQt5 >=5.15.4"]
58
+ PyQt6 = ["PyQt6 >=6.4.2,<6.8"]
55
59
  test = [
60
+ "msgspec; python_version < '3.13'",
56
61
  "msgpack",
57
- "PySide6",
58
62
  "pytest-cov >=4",
59
63
  "pytest-qt >=4",
60
64
  "pytest >=7.3.2",
@@ -62,14 +66,16 @@ test = [
62
66
  "rich",
63
67
  "typer >=0.4.2",
64
68
  "tifffile >=2021.6.14",
65
- "zarr >=2.2",
69
+ "zarr >=2.2,<3",
70
+ "xarray",
66
71
  ]
67
- dev = ["ipython", "mypy", "pdbpp", "pre-commit", "ruff"]
72
+ dev = ["ipython", "mypy", "pdbpp", "pre-commit", "ruff", "tensorstore-stubs"]
68
73
  docs = [
69
74
  "mkdocs >=1.4",
70
75
  "mkdocs-material",
71
76
  "mkdocstrings ==0.22.0",
72
77
  "mkdocstrings-python ==1.1.2",
78
+ "mkdocs-typer ==0.0.3",
73
79
  # "griffe @ git+https://github.com/tlambert03/griffe@recursion"
74
80
  ]
75
81
 
@@ -96,10 +102,10 @@ include = ["/src", "/tests"]
96
102
  only-include = ["src"]
97
103
  sources = ["src"]
98
104
 
99
- # https://beta.ruff.rs/docs/rules/
105
+ # https://docs.astral.sh/ruff/rules/
100
106
  [tool.ruff]
101
107
  line-length = 88
102
- target-version = "py38"
108
+ target-version = "py39"
103
109
 
104
110
  [tool.ruff.lint]
105
111
  pydocstyle = { convention = "numpy" }
@@ -115,7 +121,7 @@ select = [
115
121
  "A001", # flake8-builtins
116
122
  "RUF", # ruff-specific rules
117
123
  "TID", # tidy
118
- "TCH", # typecheck
124
+ "TC", # typecheck
119
125
  "SLF", # private-access
120
126
  ]
121
127
  ignore = [
@@ -138,7 +144,8 @@ docstring-code-format = true
138
144
  [tool.pytest.ini_options]
139
145
  minversion = "6.0"
140
146
  testpaths = ["tests"]
141
- filterwarnings = ["error"]
147
+ filterwarnings = ["error", "ignore:Failed to disconnect::pytestqt"]
148
+ markers = ["run_last: mark a test to run last"]
142
149
 
143
150
  # https://mypy.readthedocs.io/en/stable/config_file.html
144
151
  [tool.mypy]
@@ -149,6 +156,9 @@ disallow_subclassing_any = false
149
156
  show_error_codes = true
150
157
  pretty = true
151
158
  plugins = "pydantic.mypy"
159
+ # see https://github.com/python/mypy/issues/5374 and related discussions
160
+ # it causes more pain than it solves
161
+ disable_error_code = ["type-abstract"]
152
162
 
153
163
  [[tool.mypy.overrides]]
154
164
  module = ["tests.*"]
@@ -181,3 +191,6 @@ ignore = [
181
191
  "tests/**/*",
182
192
  "tox.ini",
183
193
  ]
194
+
195
+ [tool.typos.default]
196
+ extend-ignore-identifiers-re = ["(?i)nd2?.*", "(?i)ome", "anager"]
@@ -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)
@@ -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 Iterator, Sequence
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"