pymmcore-plus 0.13.7__tar.gz → 0.15.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.
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/PKG-INFO +14 -37
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/pyproject.toml +57 -36
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/__init__.py +2 -0
- pymmcore_plus-0.15.0/src/pymmcore_plus/_accumulator.py +258 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/_pymmcore.py +4 -2
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/__init__.py +34 -1
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/_constants.py +21 -3
- pymmcore_plus-0.15.0/src/pymmcore_plus/core/_device.py +937 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/_mmcore_plus.py +260 -47
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/events/_protocol.py +49 -34
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/events/_psygnal.py +2 -2
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/experimental/unicore/__init__.py +7 -1
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/experimental/unicore/_proxy.py +20 -3
- pymmcore_plus-0.15.0/src/pymmcore_plus/experimental/unicore/core/_sequence_buffer.py +318 -0
- pymmcore_plus-0.15.0/src/pymmcore_plus/experimental/unicore/core/_unicore.py +1702 -0
- pymmcore_plus-0.15.0/src/pymmcore_plus/experimental/unicore/devices/_camera.py +196 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/experimental/unicore/devices/_device.py +54 -28
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/experimental/unicore/devices/_properties.py +8 -1
- pymmcore_plus-0.15.0/src/pymmcore_plus/experimental/unicore/devices/_slm.py +82 -0
- pymmcore_plus-0.15.0/src/pymmcore_plus/experimental/unicore/devices/_state.py +152 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/events/_protocol.py +8 -8
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/handlers/_tensorstore_handler.py +3 -1
- pymmcore_plus-0.15.0/src/pymmcore_plus/py.typed +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/conftest.py +11 -8
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/io/test_zarr_writers.py +6 -0
- pymmcore_plus-0.15.0/tests/test_accumulators.py +107 -0
- pymmcore_plus-0.15.0/tests/test_bench.py +191 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/test_cli.py +4 -2
- pymmcore_plus-0.15.0/tests/test_device_class.py +234 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/test_events.py +5 -4
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/test_sequencing.py +8 -1
- pymmcore_plus-0.15.0/tests/unicore/conftest.py +13 -0
- pymmcore_plus-0.15.0/tests/unicore/test_camera.py +266 -0
- pymmcore_plus-0.15.0/tests/unicore/test_sequence_buffer.py +577 -0
- pymmcore_plus-0.15.0/tests/unicore/test_slm.py +337 -0
- pymmcore_plus-0.15.0/tests/unicore/test_state.py +362 -0
- pymmcore_plus-0.13.7/src/pymmcore_plus/core/_device.py +0 -217
- pymmcore_plus-0.13.7/src/pymmcore_plus/experimental/unicore/_unicore.py +0 -703
- pymmcore_plus-0.13.7/tests/test_bench.py +0 -89
- pymmcore_plus-0.13.7/tests/test_device_class.py +0 -73
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/.gitignore +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/LICENSE +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/README.md +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/_benchmark.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/_build.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/_cli.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/_logger.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/_util.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/_adapter.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/_config.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/_config_group.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/_metadata.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/_property.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/_sequencing.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/events/__init__.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/events/_device_signal_view.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/events/_norm_slot.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/events/_prop_event_mixin.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/core/events/_qsignals.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/experimental/__init__.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/experimental/unicore/_device_manager.py +0 -0
- {pymmcore_plus-0.13.7/src/pymmcore_plus/experimental/unicore/devices → pymmcore_plus-0.15.0/src/pymmcore_plus/experimental/unicore/core}/__init__.py +0 -0
- /pymmcore_plus-0.13.7/src/pymmcore_plus/py.typed → /pymmcore_plus-0.15.0/src/pymmcore_plus/experimental/unicore/devices/__init__.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/experimental/unicore/devices/_stage.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/install.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/__init__.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/_engine.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/_protocol.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/_runner.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/_thread_relay.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/events/__init__.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/events/_psygnal.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/events/_qsignals.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/handlers/_5d_writer_base.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/handlers/__init__.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/handlers/_img_sequence_writer.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/handlers/_ome_tiff_writer.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/handlers/_ome_zarr_writer.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mda/handlers/_util.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/metadata/__init__.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/metadata/functions.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/metadata/schema.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/metadata/serialize.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/mocks.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/model/__init__.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/model/_config_file.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/model/_config_group.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/model/_core_device.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/model/_core_link.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/model/_device.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/model/_microscope.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/model/_pixel_size_config.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/model/_property.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/src/pymmcore_plus/seq_tester.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/__init__.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/io/test_image_sequence_writer.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/io/test_ome_tiff.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/local_config.cfg +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/test_adapter_class.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/test_config_group_class.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/test_core.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/test_mda.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/test_metadata.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/test_misc.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/test_model.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/test_pixel_config_class.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/test_property_class.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/test_slm_image.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/test_thread_relay.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/unicore/test_unicore.py +0 -0
- {pymmcore_plus-0.13.7 → pymmcore_plus-0.15.0}/tests/unicore/test_xy_stage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pymmcore-plus
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.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
|
|
@@ -25,55 +25,32 @@ Classifier: Topic :: System :: Hardware
|
|
|
25
25
|
Classifier: Topic :: System :: Hardware :: Hardware Drivers
|
|
26
26
|
Classifier: Topic :: Utilities
|
|
27
27
|
Requires-Python: >=3.9
|
|
28
|
-
Requires-Dist: numpy>=1.
|
|
28
|
+
Requires-Dist: numpy>=1.25.2
|
|
29
|
+
Requires-Dist: numpy>=1.26.0; python_version >= '3.12'
|
|
30
|
+
Requires-Dist: numpy>=2.1.0; python_version >= '3.13'
|
|
29
31
|
Requires-Dist: platformdirs>=3.0.0
|
|
30
|
-
Requires-Dist: psygnal>=0.
|
|
31
|
-
Requires-Dist: pymmcore>=
|
|
32
|
+
Requires-Dist: psygnal>=0.10
|
|
33
|
+
Requires-Dist: pymmcore>=11.2.1.71.0
|
|
32
34
|
Requires-Dist: rich>=10.2.0
|
|
33
|
-
Requires-Dist: tensorstore
|
|
35
|
+
Requires-Dist: tensorstore!=0.1.72,>=0.1.67
|
|
36
|
+
Requires-Dist: tensorstore!=0.1.72,>=0.1.71; python_version >= '3.13'
|
|
34
37
|
Requires-Dist: typer>=0.4.2
|
|
35
|
-
Requires-Dist: typing-extensions
|
|
36
|
-
Requires-Dist: useq-schema>=0.7.
|
|
38
|
+
Requires-Dist: typing-extensions>=4
|
|
39
|
+
Requires-Dist: useq-schema>=0.7.2
|
|
37
40
|
Provides-Extra: cli
|
|
38
41
|
Requires-Dist: rich>=10.2.0; extra == 'cli'
|
|
39
42
|
Requires-Dist: typer>=0.4.2; extra == 'cli'
|
|
40
|
-
Provides-Extra: dev
|
|
41
|
-
Requires-Dist: ipython; extra == 'dev'
|
|
42
|
-
Requires-Dist: mypy; extra == 'dev'
|
|
43
|
-
Requires-Dist: pdbpp; (sys_platform != 'win32') and extra == 'dev'
|
|
44
|
-
Requires-Dist: pre-commit; extra == 'dev'
|
|
45
|
-
Requires-Dist: ruff; extra == 'dev'
|
|
46
|
-
Requires-Dist: tensorstore-stubs; extra == 'dev'
|
|
47
|
-
Provides-Extra: docs
|
|
48
|
-
Requires-Dist: mkdocs-autorefs==1.3.1; extra == 'docs'
|
|
49
|
-
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
50
|
-
Requires-Dist: mkdocs-typer==0.0.3; extra == 'docs'
|
|
51
|
-
Requires-Dist: mkdocs>=1.4; extra == 'docs'
|
|
52
|
-
Requires-Dist: mkdocstrings-python==1.1.2; extra == 'docs'
|
|
53
|
-
Requires-Dist: mkdocstrings==0.22.0; extra == 'docs'
|
|
54
43
|
Provides-Extra: io
|
|
55
44
|
Requires-Dist: tifffile>=2021.6.14; extra == 'io'
|
|
56
|
-
Requires-Dist: zarr<3,>=2.
|
|
45
|
+
Requires-Dist: zarr<3,>=2.15; extra == 'io'
|
|
57
46
|
Provides-Extra: pyqt5
|
|
58
47
|
Requires-Dist: pyqt5>=5.15.4; extra == 'pyqt5'
|
|
59
48
|
Provides-Extra: pyqt6
|
|
60
|
-
Requires-Dist: pyqt6
|
|
49
|
+
Requires-Dist: pyqt6>=6.4.2; extra == 'pyqt6'
|
|
61
50
|
Provides-Extra: pyside2
|
|
62
|
-
Requires-Dist: pyside2>=5.15; extra == 'pyside2'
|
|
51
|
+
Requires-Dist: pyside2>=5.15.2.1; extra == 'pyside2'
|
|
63
52
|
Provides-Extra: pyside6
|
|
64
|
-
Requires-Dist: pyside6
|
|
65
|
-
Provides-Extra: test
|
|
66
|
-
Requires-Dist: msgpack; extra == 'test'
|
|
67
|
-
Requires-Dist: msgspec; extra == 'test'
|
|
68
|
-
Requires-Dist: pytest-cov>=4; extra == 'test'
|
|
69
|
-
Requires-Dist: pytest-qt>=4; extra == 'test'
|
|
70
|
-
Requires-Dist: pytest>=7.3.2; extra == 'test'
|
|
71
|
-
Requires-Dist: qtpy>=2; extra == 'test'
|
|
72
|
-
Requires-Dist: rich; extra == 'test'
|
|
73
|
-
Requires-Dist: tifffile>=2021.6.14; extra == 'test'
|
|
74
|
-
Requires-Dist: typer>=0.4.2; extra == 'test'
|
|
75
|
-
Requires-Dist: xarray; extra == 'test'
|
|
76
|
-
Requires-Dist: zarr<3,>=2.2; extra == 'test'
|
|
53
|
+
Requires-Dist: pyside6==6.7.3; extra == 'pyside6'
|
|
77
54
|
Description-Content-Type: text/markdown
|
|
78
55
|
|
|
79
56
|
# pymmcore-plus
|
|
@@ -36,13 +36,15 @@ classifiers = [
|
|
|
36
36
|
dynamic = ["version"]
|
|
37
37
|
dependencies = [
|
|
38
38
|
"platformdirs >=3.0.0",
|
|
39
|
-
"numpy >=1.
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
#
|
|
45
|
-
"
|
|
39
|
+
"numpy >=2.1.0; python_version >= '3.13'",
|
|
40
|
+
"numpy >=1.26.0; python_version >= '3.12'",
|
|
41
|
+
"numpy >=1.25.2",
|
|
42
|
+
"psygnal >=0.10",
|
|
43
|
+
"pymmcore >=11.2.1.71.0",
|
|
44
|
+
"typing-extensions >=4", # not actually required at runtime
|
|
45
|
+
"useq-schema >=0.7.2",
|
|
46
|
+
"tensorstore >=0.1.71,!=0.1.72; python_version >= '3.13'",
|
|
47
|
+
"tensorstore >=0.1.67,!=0.1.72",
|
|
46
48
|
# cli requirements included by default for now
|
|
47
49
|
"typer >=0.4.2",
|
|
48
50
|
"rich >=10.2.0",
|
|
@@ -52,40 +54,53 @@ dependencies = [
|
|
|
52
54
|
# https://peps.python.org/pep-0621/#dependencies-optional-dependencies
|
|
53
55
|
[project.optional-dependencies]
|
|
54
56
|
cli = ["typer >=0.4.2", "rich >=10.2.0"]
|
|
55
|
-
io = ["tifffile >=2021.6.14", "zarr >=2.
|
|
56
|
-
PySide2 = ["PySide2 >=5.15"]
|
|
57
|
-
PySide6 = ["PySide6
|
|
57
|
+
io = ["tifffile >=2021.6.14", "zarr >=2.15,<3"]
|
|
58
|
+
PySide2 = ["PySide2 >=5.15.2.1"]
|
|
59
|
+
PySide6 = ["PySide6 ==6.7.3"]
|
|
58
60
|
PyQt5 = ["PyQt5 >=5.15.4"]
|
|
59
|
-
PyQt6 = ["PyQt6 >=6.4.2
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"msgpack",
|
|
63
|
-
"pytest-cov >=4",
|
|
64
|
-
"pytest-qt >=4",
|
|
65
|
-
"pytest >=7.3.2",
|
|
66
|
-
"qtpy >=2",
|
|
67
|
-
"rich",
|
|
68
|
-
"typer >=0.4.2",
|
|
69
|
-
"tifffile >=2021.6.14",
|
|
70
|
-
"zarr >=2.2,<3",
|
|
71
|
-
"xarray",
|
|
72
|
-
]
|
|
73
|
-
dev = [
|
|
74
|
-
"ipython",
|
|
75
|
-
"mypy",
|
|
76
|
-
"pdbpp; sys_platform != 'win32'",
|
|
77
|
-
"pre-commit",
|
|
78
|
-
"ruff",
|
|
79
|
-
"tensorstore-stubs",
|
|
80
|
-
]
|
|
61
|
+
PyQt6 = ["PyQt6 >=6.4.2"]
|
|
62
|
+
|
|
63
|
+
[dependency-groups]
|
|
81
64
|
docs = [
|
|
82
65
|
"mkdocs >=1.4",
|
|
83
|
-
"mkdocs-material",
|
|
66
|
+
"mkdocs-material>=9.5",
|
|
84
67
|
"mkdocstrings ==0.22.0",
|
|
85
68
|
"mkdocs-autorefs ==1.3.1",
|
|
86
69
|
"mkdocstrings-python ==1.1.2",
|
|
87
70
|
"mkdocs-typer ==0.0.3",
|
|
88
|
-
|
|
71
|
+
]
|
|
72
|
+
test = [
|
|
73
|
+
"pymmcore-plus[io]",
|
|
74
|
+
"msgspec >= 0.19",
|
|
75
|
+
"msgpack >=1",
|
|
76
|
+
"pytest-cov >=5",
|
|
77
|
+
"pytest >=8",
|
|
78
|
+
"xarray >=2024.1",
|
|
79
|
+
"pymmcore >=11.5.0.73",
|
|
80
|
+
]
|
|
81
|
+
test-codspeed = [{ include-group = "test" }, "pytest-codspeed >=3.2.0"]
|
|
82
|
+
test-qt = [{ include-group = 'test' }, "pytest-qt >=4.4", "qtpy >=2"]
|
|
83
|
+
PyQt6 = [{ include-group = 'test-qt' }, "pymmcore-plus[PyQt6]"]
|
|
84
|
+
PySide6 = [{ include-group = 'test-qt' }, "pymmcore-plus[PySide6]"]
|
|
85
|
+
PyQt5 = [{ include-group = 'test-qt' }, "pymmcore-plus[PyQt5]"]
|
|
86
|
+
PySide2 = [{ include-group = 'test-qt' }, "pymmcore-plus[PySide2]"]
|
|
87
|
+
dev = [
|
|
88
|
+
# { include-group = "docs" },
|
|
89
|
+
{ include-group = "PyQt6" },
|
|
90
|
+
"ipython>=8.18.1",
|
|
91
|
+
"pdbpp>=0.11.6 ; sys_platform != 'win32'",
|
|
92
|
+
"mypy>=1.14.1",
|
|
93
|
+
"pre-commit>=4.1.0",
|
|
94
|
+
"ruff>=0.9.4",
|
|
95
|
+
"pydantic >2.7.4; python_version >= '3.13'",
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
[tool.uv.sources]
|
|
99
|
+
pymmcore-plus = { workspace = true }
|
|
100
|
+
|
|
101
|
+
[tool.uv]
|
|
102
|
+
override-dependencies = [
|
|
103
|
+
"griffe @ git+https://github.com/tlambert03/griffe@recursion#egg=griffe",
|
|
89
104
|
]
|
|
90
105
|
|
|
91
106
|
[project.urls]
|
|
@@ -115,6 +130,8 @@ sources = ["src"]
|
|
|
115
130
|
[tool.ruff]
|
|
116
131
|
line-length = 88
|
|
117
132
|
target-version = "py39"
|
|
133
|
+
fix = true
|
|
134
|
+
unsafe-fixes = true
|
|
118
135
|
|
|
119
136
|
[tool.ruff.lint]
|
|
120
137
|
pydocstyle = { convention = "numpy" }
|
|
@@ -140,7 +157,7 @@ ignore = [
|
|
|
140
157
|
]
|
|
141
158
|
|
|
142
159
|
[tool.ruff.lint.per-file-ignores]
|
|
143
|
-
"tests
|
|
160
|
+
"tests/**/*.py" = ["D", "SLF"]
|
|
144
161
|
"examples/*.py" = ["D"]
|
|
145
162
|
"_cli.py" = ["B008"]
|
|
146
163
|
"docs/*.py" = ["A", "D"]
|
|
@@ -153,7 +170,11 @@ docstring-code-format = true
|
|
|
153
170
|
[tool.pytest.ini_options]
|
|
154
171
|
minversion = "6.0"
|
|
155
172
|
testpaths = ["tests"]
|
|
156
|
-
filterwarnings = [
|
|
173
|
+
filterwarnings = [
|
|
174
|
+
"error",
|
|
175
|
+
"ignore:Failed to disconnect::pytestqt",
|
|
176
|
+
"ignore:numpy.core.multiarray is deprecated",
|
|
177
|
+
]
|
|
157
178
|
markers = ["run_last: mark a test to run last"]
|
|
158
179
|
|
|
159
180
|
# https://mypy.readthedocs.io/en/stable/config_file.html
|
|
@@ -8,6 +8,7 @@ except PackageNotFoundError: # pragma: no cover
|
|
|
8
8
|
__version__ = "unknown"
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
from ._accumulator import AbstractChangeAccumulator
|
|
11
12
|
from ._logger import configure_logging
|
|
12
13
|
from ._util import find_micromanager, use_micromanager
|
|
13
14
|
from .core import (
|
|
@@ -34,6 +35,7 @@ from .core.events import CMMCoreSignaler, PCoreSignaler
|
|
|
34
35
|
from .mda._runner import GeneratorMDASequence
|
|
35
36
|
|
|
36
37
|
__all__ = [
|
|
38
|
+
"AbstractChangeAccumulator",
|
|
37
39
|
"ActionType",
|
|
38
40
|
"CFGCommand",
|
|
39
41
|
"CFGGroup",
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Accumulate `setX` calls to a device value or property."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import abc
|
|
6
|
+
import sys
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from collections.abc import Sequence
|
|
9
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal, TypeVar
|
|
10
|
+
|
|
11
|
+
import psygnal
|
|
12
|
+
|
|
13
|
+
from pymmcore_plus.core._constants import DeviceType
|
|
14
|
+
from pymmcore_plus.core._mmcore_plus import CMMCorePlus
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from typing_extensions import Self
|
|
18
|
+
T = TypeVar("T")
|
|
19
|
+
DT = TypeVar("DT", bound=DeviceType)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AbstractChangeAccumulator(ABC, Generic[T]):
|
|
23
|
+
"""Abstract base class for accumulating a series of `setX` calls to a device.
|
|
24
|
+
|
|
25
|
+
A `ChangeAccumulator`` is a class that accumulates a series of `setX` calls to a
|
|
26
|
+
device, retaining an internal target value, and emitting a signal when the device
|
|
27
|
+
has reached its target and is idle. It can be shared by multiple players (e.g.
|
|
28
|
+
widgets, or other classes) that want to control the same device, and allows them all
|
|
29
|
+
to issue relative/absolute moves, and be notified when the device is idle.
|
|
30
|
+
|
|
31
|
+
A common use case is to accumulate setPosition calls made to a stage device, where
|
|
32
|
+
you might want to accumulate a series of relative moves, and snap an image only when
|
|
33
|
+
the stage is idle after reaching its target position.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
finished = psygnal.Signal()
|
|
37
|
+
"""Signal emitted when the device has reached its target and is idle."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, zero: T) -> None:
|
|
40
|
+
self._zero = zero
|
|
41
|
+
self._reset()
|
|
42
|
+
|
|
43
|
+
def _reset(self) -> None:
|
|
44
|
+
self._base: T | None = None
|
|
45
|
+
self._delta: T | None = None
|
|
46
|
+
|
|
47
|
+
# ------------------------ Public API ------------------------
|
|
48
|
+
|
|
49
|
+
def add_relative(self, delta: T) -> None:
|
|
50
|
+
"""Add a relative value to the target."""
|
|
51
|
+
if self._delta is None:
|
|
52
|
+
self._base = self._get_value()
|
|
53
|
+
self._delta = delta
|
|
54
|
+
else:
|
|
55
|
+
self._delta = self._add(self._delta, delta)
|
|
56
|
+
self._issue_move()
|
|
57
|
+
|
|
58
|
+
def set_absolute(self, target: T) -> None:
|
|
59
|
+
"""Assign an absolute value to the target.
|
|
60
|
+
|
|
61
|
+
This will reset the accumulated state and issue a move to the target position.
|
|
62
|
+
After the move finishes, new `move_relative()` calls are interpreted
|
|
63
|
+
relative to *target*.
|
|
64
|
+
"""
|
|
65
|
+
self._base = target # anchor for later relatives
|
|
66
|
+
self._delta = self._zero # target == base + delta
|
|
67
|
+
self._issue_move()
|
|
68
|
+
|
|
69
|
+
def poll_done(self) -> bool:
|
|
70
|
+
"""Check if the device is done moving.
|
|
71
|
+
|
|
72
|
+
This should be called repeatedly by some event loop driver.
|
|
73
|
+
|
|
74
|
+
Returns True exactly once when:
|
|
75
|
+
1. The device is idle (not busy) AND
|
|
76
|
+
2. The last issued move command has been completed
|
|
77
|
+
|
|
78
|
+
After returning True it resets its state and will return False until the next
|
|
79
|
+
move_relative() call.
|
|
80
|
+
"""
|
|
81
|
+
# if we have no base or delta, we're not moving
|
|
82
|
+
if self._delta is None:
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
# if the device is busy, we're not done
|
|
86
|
+
if self._is_busy():
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
# no new work, we're done
|
|
90
|
+
self._reset()
|
|
91
|
+
self.finished.emit()
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def is_moving(self) -> bool:
|
|
96
|
+
"""Returns True if the device is moving."""
|
|
97
|
+
return self._delta is not None
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def target(self) -> T | None:
|
|
101
|
+
"""The target position of the stage. Or None if not moving."""
|
|
102
|
+
if self._base is None or self._delta is None:
|
|
103
|
+
return None
|
|
104
|
+
return self._add(self._base, self._delta)
|
|
105
|
+
|
|
106
|
+
# ------------------------ Public API ------------------------
|
|
107
|
+
|
|
108
|
+
def _issue_move(self) -> None:
|
|
109
|
+
# self._base and self._delta are guaranteed to be not None here
|
|
110
|
+
target = self._add(self._base, self._delta) # type: ignore[arg-type]
|
|
111
|
+
# issue the move command
|
|
112
|
+
try:
|
|
113
|
+
self._set_value(target)
|
|
114
|
+
except Exception: # pragma: no cover
|
|
115
|
+
from pymmcore_plus._logger import logger
|
|
116
|
+
|
|
117
|
+
logger.exception(f"Error setting {type(self)} to {target}")
|
|
118
|
+
|
|
119
|
+
# ------------------------ Abstract methods ------------------------
|
|
120
|
+
|
|
121
|
+
@abstractmethod
|
|
122
|
+
def _get_value(self) -> T:
|
|
123
|
+
"""Get the current position of the device."""
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
def _set_value(self, value: T) -> None:
|
|
127
|
+
"""Set the position of the device."""
|
|
128
|
+
|
|
129
|
+
@abstractmethod
|
|
130
|
+
def _add(self, a: T, b: T) -> T:
|
|
131
|
+
"""Add two values together.
|
|
132
|
+
|
|
133
|
+
Provided for more complex types like sequences.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
@abstractmethod
|
|
137
|
+
def _is_busy(self) -> bool:
|
|
138
|
+
"""Return True if the device is busy."""
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class FloatChangeAccumulator(AbstractChangeAccumulator[float]):
|
|
142
|
+
def __init__(self) -> None:
|
|
143
|
+
super().__init__(zero=0.0)
|
|
144
|
+
|
|
145
|
+
def _add(self, a: float, b: float) -> float:
|
|
146
|
+
return a + b
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
ZIP_STRICT = {"strict": True} if sys.version_info >= (3, 10) else {}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class SequenceChangeAccumulator(AbstractChangeAccumulator[Sequence[float]]):
|
|
153
|
+
def __init__(self, sequence_length: int) -> None:
|
|
154
|
+
self.sequence_length = sequence_length
|
|
155
|
+
super().__init__(zero=[0.0] * sequence_length)
|
|
156
|
+
|
|
157
|
+
def _add(self, a: Sequence[float], b: Sequence[float]) -> Sequence[float]:
|
|
158
|
+
return [x + y for x, y in zip(a, b, **ZIP_STRICT)]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class DeviceAccumulator(abc.ABC, Generic[DT]):
|
|
162
|
+
def __init__(
|
|
163
|
+
self,
|
|
164
|
+
*,
|
|
165
|
+
device_label: str,
|
|
166
|
+
mmcore: CMMCorePlus | None = None,
|
|
167
|
+
**kwargs: Any,
|
|
168
|
+
) -> None:
|
|
169
|
+
self._mmcore = mmcore or CMMCorePlus.instance()
|
|
170
|
+
dev_type = self._device_type()
|
|
171
|
+
if not self._mmcore.getDeviceType(device_label) == dev_type: # pragma: no cover
|
|
172
|
+
raise ValueError(
|
|
173
|
+
f"Cannot create {self.__class__.__name__}. "
|
|
174
|
+
f"Device {device_label!r} is not a {dev_type.name}. "
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
self._device_label = device_label
|
|
178
|
+
super().__init__(**kwargs)
|
|
179
|
+
|
|
180
|
+
def _is_busy(self) -> bool:
|
|
181
|
+
return self._mmcore.deviceBusy(self._device_label)
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
@abstractmethod
|
|
185
|
+
def _device_type(cls) -> DT:
|
|
186
|
+
"""Return the device type for this class."""
|
|
187
|
+
|
|
188
|
+
_CACHE: ClassVar[dict[tuple[int, str], DeviceAccumulator]] = {}
|
|
189
|
+
|
|
190
|
+
@classmethod
|
|
191
|
+
def get_cached(cls, device: str, mmcore: CMMCorePlus | None = None) -> Self:
|
|
192
|
+
"""Get a cached instance of the class for the given (device, core) pair.
|
|
193
|
+
|
|
194
|
+
This is intended to be called on the subclass for the device type you want to
|
|
195
|
+
create. For example, if you want to create a `PositionChangeAccumulator` for a
|
|
196
|
+
`StageDevice`, you would call: `PositionChangeAccumulator.get_cached(device)`.
|
|
197
|
+
|
|
198
|
+
But it may also be called on the base class, in which case it will still return
|
|
199
|
+
the correct subclass instance, but you will not have type safety on the return
|
|
200
|
+
type.
|
|
201
|
+
"""
|
|
202
|
+
mmcore = mmcore or CMMCorePlus.instance()
|
|
203
|
+
cache_key = (id(mmcore), device)
|
|
204
|
+
device_type = mmcore.getDeviceType(device)
|
|
205
|
+
if cache_key not in DeviceAccumulator._CACHE:
|
|
206
|
+
if device_type == cls._device_type():
|
|
207
|
+
cls._CACHE[cache_key] = cls(device_label=device, mmcore=mmcore)
|
|
208
|
+
else:
|
|
209
|
+
for sub in cls.__subclasses__():
|
|
210
|
+
if sub._device_type() == device_type: # noqa: SLF001
|
|
211
|
+
cls._CACHE[cache_key] = sub(device_label=device, mmcore=mmcore)
|
|
212
|
+
break
|
|
213
|
+
else:
|
|
214
|
+
raise ValueError(
|
|
215
|
+
"No matching DeviceTypeMixin subclass found for device type "
|
|
216
|
+
f"{device_type.name} (for device {device!r})."
|
|
217
|
+
)
|
|
218
|
+
obj = cls._CACHE[cache_key]
|
|
219
|
+
if not isinstance(obj, cls):
|
|
220
|
+
raise TypeError(
|
|
221
|
+
f"Cannot create {cls.__name__} for {device!r}. "
|
|
222
|
+
f"Device is a {device_type!r}, not a {cls._device_type().name}. "
|
|
223
|
+
)
|
|
224
|
+
return obj
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class PositionChangeAccumulator(DeviceAccumulator, FloatChangeAccumulator):
|
|
228
|
+
"""Accumulator for single axis stage devices."""
|
|
229
|
+
|
|
230
|
+
def __init__(self, device_label: str, mmcore: CMMCorePlus | None = None) -> None:
|
|
231
|
+
super().__init__(device_label=device_label, mmcore=mmcore)
|
|
232
|
+
|
|
233
|
+
@classmethod
|
|
234
|
+
def _device_type(cls) -> Literal[DeviceType.StageDevice]:
|
|
235
|
+
return DeviceType.StageDevice
|
|
236
|
+
|
|
237
|
+
def _get_value(self) -> float:
|
|
238
|
+
return self._mmcore.getPosition(self._device_label)
|
|
239
|
+
|
|
240
|
+
def _set_value(self, value: float) -> None:
|
|
241
|
+
self._mmcore.setPosition(self._device_label, value)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class XYPositionChangeAccumulator(DeviceAccumulator, SequenceChangeAccumulator):
|
|
245
|
+
"""Accumulator for XY stage devices."""
|
|
246
|
+
|
|
247
|
+
def __init__(self, device_label: str, mmcore: CMMCorePlus | None = None) -> None:
|
|
248
|
+
super().__init__(device_label=device_label, mmcore=mmcore, sequence_length=2)
|
|
249
|
+
|
|
250
|
+
@classmethod
|
|
251
|
+
def _device_type(cls) -> Literal[DeviceType.XYStageDevice]:
|
|
252
|
+
return DeviceType.XYStageDevice
|
|
253
|
+
|
|
254
|
+
def _get_value(self) -> Sequence[float]:
|
|
255
|
+
return self._mmcore.getXYPosition(self._device_label)
|
|
256
|
+
|
|
257
|
+
def _set_value(self, value: Sequence[float]) -> None:
|
|
258
|
+
self._mmcore.setXYPosition(self._device_label, *value)
|
|
@@ -24,7 +24,9 @@ class VersionInfo(NamedTuple):
|
|
|
24
24
|
minor: int
|
|
25
25
|
micro: int
|
|
26
26
|
device_interface: int
|
|
27
|
-
build: int
|
|
27
|
+
build: int = 0
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
# pass no more than 5 parts to VersionInfo
|
|
31
|
+
numbers = re.findall(r"\d+", __version__)[:5]
|
|
32
|
+
version_info = VersionInfo(*(int(i) for i in numbers))
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
__all__ = [
|
|
2
2
|
"ActionType",
|
|
3
|
+
"AutoFocusDevice",
|
|
3
4
|
"CFGCommand",
|
|
4
5
|
"CFGGroup",
|
|
5
6
|
"CMMCorePlus",
|
|
7
|
+
"CameraDevice",
|
|
6
8
|
"ConfigGroup",
|
|
7
9
|
"Configuration",
|
|
10
|
+
"CoreDevice",
|
|
11
|
+
"Device",
|
|
8
12
|
"Device",
|
|
9
13
|
"DeviceAdapter",
|
|
10
14
|
"DeviceDetectionStatus",
|
|
@@ -13,12 +17,24 @@ __all__ = [
|
|
|
13
17
|
"DeviceProperty",
|
|
14
18
|
"DeviceType",
|
|
15
19
|
"FocusDirection",
|
|
20
|
+
"GalvoDevice",
|
|
21
|
+
"GenericDevice",
|
|
22
|
+
"HubDevice",
|
|
23
|
+
"ImageProcessorDevice",
|
|
16
24
|
"Keyword",
|
|
25
|
+
"MagnifierDevice",
|
|
17
26
|
"Metadata",
|
|
18
27
|
"PixelFormat",
|
|
19
28
|
"PortType",
|
|
20
29
|
"PropertyType",
|
|
30
|
+
"SLMDevice",
|
|
21
31
|
"SequencedEvent",
|
|
32
|
+
"SerialDevice",
|
|
33
|
+
"ShutterDevice",
|
|
34
|
+
"SignalIODevice",
|
|
35
|
+
"StageDevice",
|
|
36
|
+
"StateDevice",
|
|
37
|
+
"XYStageDevice",
|
|
22
38
|
"iter_sequenced_events",
|
|
23
39
|
]
|
|
24
40
|
|
|
@@ -39,7 +55,24 @@ from ._constants import (
|
|
|
39
55
|
PortType,
|
|
40
56
|
PropertyType,
|
|
41
57
|
)
|
|
42
|
-
from ._device import
|
|
58
|
+
from ._device import (
|
|
59
|
+
AutoFocusDevice,
|
|
60
|
+
CameraDevice,
|
|
61
|
+
CoreDevice,
|
|
62
|
+
Device,
|
|
63
|
+
GalvoDevice,
|
|
64
|
+
GenericDevice,
|
|
65
|
+
HubDevice,
|
|
66
|
+
ImageProcessorDevice,
|
|
67
|
+
MagnifierDevice,
|
|
68
|
+
SerialDevice,
|
|
69
|
+
ShutterDevice,
|
|
70
|
+
SignalIODevice,
|
|
71
|
+
SLMDevice,
|
|
72
|
+
StageDevice,
|
|
73
|
+
StateDevice,
|
|
74
|
+
XYStageDevice,
|
|
75
|
+
)
|
|
43
76
|
from ._metadata import Metadata
|
|
44
77
|
from ._mmcore_plus import CMMCorePlus
|
|
45
78
|
from ._property import DeviceProperty
|
|
@@ -13,6 +13,7 @@ import pymmcore_plus._pymmcore as pymmcore
|
|
|
13
13
|
class Keyword(str, Enum):
|
|
14
14
|
Name = pymmcore.g_Keyword_Name
|
|
15
15
|
Description = pymmcore.g_Keyword_Description
|
|
16
|
+
|
|
16
17
|
CameraName = pymmcore.g_Keyword_CameraName
|
|
17
18
|
CameraID = pymmcore.g_Keyword_CameraID
|
|
18
19
|
CameraChannelName = pymmcore.g_Keyword_CameraChannelName
|
|
@@ -31,6 +32,7 @@ class Keyword(str, Enum):
|
|
|
31
32
|
Offset = pymmcore.g_Keyword_Offset
|
|
32
33
|
CCDTemperature = pymmcore.g_Keyword_CCDTemperature
|
|
33
34
|
CCDTemperatureSetPoint = pymmcore.g_Keyword_CCDTemperatureSetPoint
|
|
35
|
+
|
|
34
36
|
State = pymmcore.g_Keyword_State
|
|
35
37
|
Label = pymmcore.g_Keyword_Label
|
|
36
38
|
Position = pymmcore.g_Keyword_Position
|
|
@@ -69,12 +71,15 @@ class Keyword(str, Enum):
|
|
|
69
71
|
HubID = pymmcore.g_Keyword_HubID
|
|
70
72
|
|
|
71
73
|
# image annotations
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
Metadata_CameraLabel = pymmcore.g_Keyword_Metadata_CameraLabel
|
|
75
|
+
Metadata_Exposure = pymmcore.g_Keyword_Metadata_Exposure
|
|
76
|
+
Metadata_Height = pymmcore.g_Keyword_Metadata_Height
|
|
74
77
|
Metadata_ImageNumber = pymmcore.g_Keyword_Metadata_ImageNumber
|
|
75
78
|
Metadata_ROI_X = pymmcore.g_Keyword_Metadata_ROI_X
|
|
76
79
|
Metadata_ROI_Y = pymmcore.g_Keyword_Metadata_ROI_Y
|
|
80
|
+
Metadata_Score = pymmcore.g_Keyword_Metadata_Score
|
|
77
81
|
Metadata_TimeInCore = pymmcore.g_Keyword_Metadata_TimeInCore
|
|
82
|
+
Metadata_Width = pymmcore.g_Keyword_Metadata_Width
|
|
78
83
|
|
|
79
84
|
def __str__(self) -> str:
|
|
80
85
|
return str(self.value)
|
|
@@ -165,7 +170,9 @@ class PropertyType(IntEnum):
|
|
|
165
170
|
String = pymmcore.String
|
|
166
171
|
Float = pymmcore.Float
|
|
167
172
|
Integer = pymmcore.Integer
|
|
173
|
+
|
|
168
174
|
Boolean = auto() # not supported in pymmcore
|
|
175
|
+
Enum = auto() # not supported in pymmcore
|
|
169
176
|
|
|
170
177
|
def to_python(self) -> type | None:
|
|
171
178
|
return {0: None, 1: str, 2: float, 3: int}[self]
|
|
@@ -183,7 +190,16 @@ class PropertyType(IntEnum):
|
|
|
183
190
|
if value is None:
|
|
184
191
|
return PropertyType.Undef
|
|
185
192
|
if isinstance(value, str):
|
|
186
|
-
|
|
193
|
+
if value.lower() in ("int", "integer"):
|
|
194
|
+
return PropertyType.Integer
|
|
195
|
+
if value.lower() in ("float", "double"):
|
|
196
|
+
return PropertyType.Float
|
|
197
|
+
if value.lower() in ("bool", "boolean"):
|
|
198
|
+
return PropertyType.Boolean
|
|
199
|
+
if value.lower() in ("string", "str"):
|
|
200
|
+
return PropertyType.String
|
|
201
|
+
if value.lower() in ("enum", "enumeration"):
|
|
202
|
+
return PropertyType.Enum
|
|
187
203
|
if isinstance(value, type):
|
|
188
204
|
if value is float:
|
|
189
205
|
return PropertyType.Float
|
|
@@ -193,6 +209,8 @@ class PropertyType(IntEnum):
|
|
|
193
209
|
return PropertyType.String
|
|
194
210
|
elif value is bool:
|
|
195
211
|
return PropertyType.Boolean
|
|
212
|
+
elif issubclass(value, Enum):
|
|
213
|
+
return PropertyType.Enum
|
|
196
214
|
|
|
197
215
|
raise TypeError(
|
|
198
216
|
f"Property type must be a PropertyType enum member, "
|