pymmcore-plus 0.16.0__py3-none-any.whl → 0.17.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pymmcore_plus/_ipy_completion.py +1 -1
- pymmcore_plus/_logger.py +2 -2
- pymmcore_plus/core/_device.py +37 -6
- pymmcore_plus/core/_mmcore_plus.py +4 -15
- pymmcore_plus/core/_property.py +1 -1
- pymmcore_plus/core/_sequencing.py +2 -0
- pymmcore_plus/experimental/simulate/__init__.py +88 -0
- pymmcore_plus/experimental/simulate/_objects.py +670 -0
- pymmcore_plus/experimental/simulate/_render.py +510 -0
- pymmcore_plus/experimental/simulate/_sample.py +156 -0
- pymmcore_plus/experimental/unicore/__init__.py +2 -0
- pymmcore_plus/experimental/unicore/_device_manager.py +26 -0
- pymmcore_plus/experimental/unicore/core/_config.py +706 -0
- pymmcore_plus/experimental/unicore/core/_unicore.py +830 -17
- pymmcore_plus/experimental/unicore/devices/_device_base.py +13 -0
- pymmcore_plus/experimental/unicore/devices/_hub.py +50 -0
- pymmcore_plus/experimental/unicore/devices/_stage.py +46 -1
- pymmcore_plus/experimental/unicore/devices/_state.py +6 -0
- pymmcore_plus/mda/handlers/_5d_writer_base.py +16 -5
- pymmcore_plus/mda/handlers/_tensorstore_handler.py +7 -1
- pymmcore_plus/metadata/_ome.py +75 -21
- pymmcore_plus/metadata/functions.py +2 -1
- {pymmcore_plus-0.16.0.dist-info → pymmcore_plus-0.17.0.dist-info}/METADATA +5 -3
- {pymmcore_plus-0.16.0.dist-info → pymmcore_plus-0.17.0.dist-info}/RECORD +27 -21
- {pymmcore_plus-0.16.0.dist-info → pymmcore_plus-0.17.0.dist-info}/WHEEL +1 -1
- {pymmcore_plus-0.16.0.dist-info → pymmcore_plus-0.17.0.dist-info}/entry_points.txt +0 -0
- {pymmcore_plus-0.16.0.dist-info → pymmcore_plus-0.17.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -64,6 +64,7 @@ class Device(_Lockable, ABC):
|
|
|
64
64
|
{}, self._cls_prop_controllers
|
|
65
65
|
)
|
|
66
66
|
self._core_proxy_: CMMCoreProxy | None = None
|
|
67
|
+
self._parent_label_: str = "" # label of the parent hub device
|
|
67
68
|
|
|
68
69
|
@property
|
|
69
70
|
def core(self) -> CMMCoreProxy:
|
|
@@ -243,6 +244,18 @@ class Device(_Lockable, ABC):
|
|
|
243
244
|
"""Return `True` if the property is read-only."""
|
|
244
245
|
return self._get_prop_or_raise(prop_name).is_read_only
|
|
245
246
|
|
|
247
|
+
# PARENT HUB RELATIONSHIP
|
|
248
|
+
|
|
249
|
+
@final # may not be overridden
|
|
250
|
+
def get_parent_label(self) -> str:
|
|
251
|
+
"""Return the label of the parent hub device, or empty string if none."""
|
|
252
|
+
return self._parent_label_
|
|
253
|
+
|
|
254
|
+
@final # may not be overridden
|
|
255
|
+
def set_parent_label(self, parent_label: str) -> None:
|
|
256
|
+
"""Set the label of the parent hub device."""
|
|
257
|
+
self._parent_label_ = parent_label
|
|
258
|
+
|
|
246
259
|
|
|
247
260
|
SeqT = TypeVar("SeqT")
|
|
248
261
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, ClassVar, Literal
|
|
4
|
+
|
|
5
|
+
from pymmcore_plus.core._constants import DeviceType
|
|
6
|
+
|
|
7
|
+
from ._device_base import Device
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Sequence
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HubDevice(Device):
|
|
14
|
+
"""ABC for Hub devices that can have peripheral devices attached.
|
|
15
|
+
|
|
16
|
+
Hub devices represent a central device (e.g., a controller) that can have
|
|
17
|
+
multiple peripheral devices attached to it. Examples include multi-channel
|
|
18
|
+
controllers, or devices that manage multiple sub-devices.
|
|
19
|
+
|
|
20
|
+
To implement a Hub device, simply override `get_installed_peripherals()`:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
class MyHub(HubDevice):
|
|
24
|
+
def get_installed_peripherals(self) -> Sequence[tuple[str, str]]:
|
|
25
|
+
return [
|
|
26
|
+
("Motor1", "First motor controller"),
|
|
27
|
+
("Motor2", "Second motor controller"),
|
|
28
|
+
]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If your hub needs to perform expensive detection (e.g., scanning a bus),
|
|
32
|
+
implement caching inside your `get_installed_peripherals()` method.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
_TYPE: ClassVar[Literal[DeviceType.Hub]] = DeviceType.Hub
|
|
36
|
+
|
|
37
|
+
def get_installed_peripherals(self) -> Sequence[tuple[str, str]]:
|
|
38
|
+
"""Return information about installed peripheral devices.
|
|
39
|
+
|
|
40
|
+
Override this method to return a sequence of `tuple[str, str]` objects
|
|
41
|
+
describing all devices that can be loaded as peripherals of this hub.
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
Sequence[tuple[str, str]]
|
|
46
|
+
A sequence of (name, description) tuples for each available peripheral.
|
|
47
|
+
The name MUST be the name of a class, importable from the same module as
|
|
48
|
+
this hub.
|
|
49
|
+
"""
|
|
50
|
+
return ()
|
|
@@ -2,7 +2,7 @@ from abc import abstractmethod
|
|
|
2
2
|
from typing import ClassVar, Literal
|
|
3
3
|
|
|
4
4
|
from pymmcore_plus.core import DeviceType
|
|
5
|
-
from pymmcore_plus.core._constants import Keyword
|
|
5
|
+
from pymmcore_plus.core._constants import FocusDirection, Keyword
|
|
6
6
|
|
|
7
7
|
from ._device_base import SeqT, SequenceableDevice
|
|
8
8
|
|
|
@@ -38,6 +38,51 @@ class StageDevice(_BaseStage[float]):
|
|
|
38
38
|
def get_position_um(self) -> float:
|
|
39
39
|
"""Returns the current position of the stage in microns."""
|
|
40
40
|
|
|
41
|
+
def get_focus_direction(self) -> FocusDirection:
|
|
42
|
+
"""Returns the focus direction of the stage."""
|
|
43
|
+
return FocusDirection.Unknown
|
|
44
|
+
|
|
45
|
+
def set_focus_direction(self, sign: int) -> None:
|
|
46
|
+
"""Sets the focus direction of the stage."""
|
|
47
|
+
raise NotImplementedError( # pragma: no cover
|
|
48
|
+
"This device does not support setting focus direction"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def set_relative_position_um(self, d: float) -> None:
|
|
52
|
+
"""Move the stage by a relative amount.
|
|
53
|
+
|
|
54
|
+
Can be overridden for more efficient implementations.
|
|
55
|
+
"""
|
|
56
|
+
pos = self.get_position_um()
|
|
57
|
+
self.set_position_um(pos + d)
|
|
58
|
+
|
|
59
|
+
def set_adapter_origin_um(self, newZUm: float) -> None:
|
|
60
|
+
"""Enable software translation of coordinates.
|
|
61
|
+
|
|
62
|
+
The current position of the stage becomes Z = newZUm.
|
|
63
|
+
Only some stages support this functionality; it is recommended that
|
|
64
|
+
set_origin() be used instead where available.
|
|
65
|
+
"""
|
|
66
|
+
# Default implementation does nothing - subclasses can override
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
def is_linear_sequenceable(self) -> bool:
|
|
70
|
+
"""Return True if the stage supports linear sequences.
|
|
71
|
+
|
|
72
|
+
A linear sequence is defined by a step size and number of slices.
|
|
73
|
+
"""
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
def set_linear_sequence(self, dZ_um: float, nSlices: int) -> None:
|
|
77
|
+
"""Load a linear sequence defined by step size and number of slices."""
|
|
78
|
+
raise NotImplementedError( # pragma: no cover
|
|
79
|
+
"This device does not support linear sequences"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def is_continuous_focus_drive(self) -> bool:
|
|
83
|
+
"""Return True if positions can be set while continuous focus runs."""
|
|
84
|
+
return False
|
|
85
|
+
|
|
41
86
|
|
|
42
87
|
# TODO: consider if we can just subclass StageDevice instead of _BaseStage
|
|
43
88
|
class XYStageDevice(_BaseStage[tuple[float, float]]):
|
|
@@ -135,8 +135,14 @@ class StateDevice(Device):
|
|
|
135
135
|
# internal method to set the state, called by the property setter
|
|
136
136
|
# to keep the label and state property in sync
|
|
137
137
|
self.set_state(state) # call the device-specific method
|
|
138
|
+
self.core.events.propertyChanged.emit(
|
|
139
|
+
self.get_label(), Keyword.State.value, state
|
|
140
|
+
)
|
|
138
141
|
label = self._state_to_label.get(state, "")
|
|
139
142
|
self.set_property_value(Keyword.Label, label)
|
|
143
|
+
self.core.events.propertyChanged.emit(
|
|
144
|
+
self.get_label(), Keyword.Label.value, label
|
|
145
|
+
)
|
|
140
146
|
|
|
141
147
|
def _get_current_label(self) -> str:
|
|
142
148
|
# internal method to get the current label, called by the property getter
|
|
@@ -79,6 +79,9 @@ class _5DWriterBase(Generic[T]):
|
|
|
79
79
|
# list of {dim_name: size} map for each position in the sequence
|
|
80
80
|
self._position_sizes: list[dict[str, int]] = []
|
|
81
81
|
|
|
82
|
+
# map of position index to position key
|
|
83
|
+
self._position_key_map: dict[int, str] = {}
|
|
84
|
+
|
|
82
85
|
# actual timestamps for each frame
|
|
83
86
|
self._timestamps: list[float] = []
|
|
84
87
|
|
|
@@ -123,13 +126,21 @@ class _5DWriterBase(Generic[T]):
|
|
|
123
126
|
self.finalize_metadata()
|
|
124
127
|
self.frame_metadatas.clear()
|
|
125
128
|
|
|
126
|
-
def get_position_key(self,
|
|
127
|
-
"""Get the position key for a specific
|
|
129
|
+
def get_position_key(self, event: useq.MDAEvent) -> str:
|
|
130
|
+
"""Get the position key for a specific MDA event.
|
|
128
131
|
|
|
129
132
|
This key will be used for subclasses like Zarr that need a directory structure
|
|
130
133
|
for each position. And may also be used to index into `self.position_arrays`.
|
|
131
134
|
"""
|
|
132
|
-
|
|
135
|
+
pos_index = event.index.get("p", 0)
|
|
136
|
+
if pos_index in self._position_key_map:
|
|
137
|
+
return self._position_key_map[pos_index]
|
|
138
|
+
|
|
139
|
+
pos_key = event.pos_name
|
|
140
|
+
if pos_key is None:
|
|
141
|
+
pos_key = f"{POS_PREFIX}{pos_index}"
|
|
142
|
+
self._position_key_map[pos_index] = pos_key
|
|
143
|
+
return pos_key
|
|
133
144
|
|
|
134
145
|
def frameReady(
|
|
135
146
|
self, frame: np.ndarray, event: useq.MDAEvent, meta: FrameMetaV1
|
|
@@ -137,7 +148,7 @@ class _5DWriterBase(Generic[T]):
|
|
|
137
148
|
"""Write frame to the zarr array for the appropriate position."""
|
|
138
149
|
# get the position key to store the array in the group
|
|
139
150
|
p_index = event.index.get("p", 0)
|
|
140
|
-
key = self.get_position_key(
|
|
151
|
+
key = self.get_position_key(event)
|
|
141
152
|
pos_sizes = self.position_sizes[p_index]
|
|
142
153
|
if key in self.position_arrays:
|
|
143
154
|
ary = self.position_arrays[key]
|
|
@@ -281,7 +292,7 @@ class _5DWriterBase(Generic[T]):
|
|
|
281
292
|
raise IndexError(
|
|
282
293
|
f"Position index {p_index} out of range for {len(self.position_sizes)}"
|
|
283
294
|
) from e
|
|
284
|
-
data = self.position_arrays[self.
|
|
295
|
+
data = self.position_arrays[self._position_key_map[p_index]]
|
|
285
296
|
full = slice(None, None)
|
|
286
297
|
index = tuple(indexers.get(k, full) for k in sizes)
|
|
287
298
|
return data[index] # type: ignore
|
|
@@ -107,7 +107,13 @@ class TensorStoreHandler:
|
|
|
107
107
|
self._ts = tensorstore
|
|
108
108
|
|
|
109
109
|
self.ts_driver = driver
|
|
110
|
-
|
|
110
|
+
if path is not None:
|
|
111
|
+
self.kvstore: dict | str = {"driver": "file", "path": str(path)}
|
|
112
|
+
elif kvstore is not None:
|
|
113
|
+
self.kvstore = kvstore
|
|
114
|
+
else:
|
|
115
|
+
raise ValueError("Either path or kvstore must be provided.")
|
|
116
|
+
|
|
111
117
|
self.delete_existing = delete_existing
|
|
112
118
|
self.spec = spec
|
|
113
119
|
|
pymmcore_plus/metadata/_ome.py
CHANGED
|
@@ -17,6 +17,7 @@ from ome_types.model import (
|
|
|
17
17
|
PixelType,
|
|
18
18
|
Plane,
|
|
19
19
|
Plate,
|
|
20
|
+
TiffData,
|
|
20
21
|
UnitsLength,
|
|
21
22
|
UnitsTime,
|
|
22
23
|
Well,
|
|
@@ -100,16 +101,24 @@ class _PositionKey(NamedTuple):
|
|
|
100
101
|
g_index: int | None = None
|
|
101
102
|
|
|
102
103
|
def __str__(self) -> str:
|
|
103
|
-
p_name = self.name or f"Pos{self.p_index:04d}"
|
|
104
104
|
if self.g_index is not None:
|
|
105
|
-
|
|
105
|
+
# if it has a name, include it in the position string before grid
|
|
106
|
+
# (e.g. name_p0000_g0000)
|
|
107
|
+
if self.name:
|
|
108
|
+
return f"{self.name}_p{self.p_index:04d}_g{self.g_index:04d}"
|
|
109
|
+
# otherwise just use p and g indices (e.g. p0000_g0000)
|
|
110
|
+
return f"p{self.p_index:04d}_g{self.g_index:04d}"
|
|
106
111
|
else:
|
|
107
|
-
|
|
112
|
+
# if it has a name, include it in the position string (e.g. name_p0000)
|
|
113
|
+
if self.name:
|
|
114
|
+
return f"{self.name}_p{self.p_index:04d}"
|
|
115
|
+
# otherwise just use p index (e.g. p0000)
|
|
116
|
+
return f"p{self.p_index:04d}"
|
|
108
117
|
|
|
109
118
|
@property
|
|
110
119
|
def image_id(self) -> str:
|
|
111
120
|
if self.g_index is not None:
|
|
112
|
-
return f"{self.p_index}
|
|
121
|
+
return f"{self.p_index}:{self.g_index}"
|
|
113
122
|
return f"{self.p_index}"
|
|
114
123
|
|
|
115
124
|
|
|
@@ -235,15 +244,29 @@ def _extract_dimension_order_from_sequence(
|
|
|
235
244
|
) -> Pixels_DimensionOrder:
|
|
236
245
|
"""Extract axis order from a useq.MDASequence.
|
|
237
246
|
|
|
247
|
+
useq axis_order represents iteration order (outermost to innermost loop),
|
|
248
|
+
while OME DimensionOrder represents rasterization order (slowest to fastest
|
|
249
|
+
varying dimension). Since planes are stored in the order they're generated,
|
|
250
|
+
we need to reverse the useq axis order to get the OME dimension order.
|
|
251
|
+
|
|
252
|
+
For example, if useq axis_order="tpzc":
|
|
253
|
+
- Iteration: for t in times: for p in positions: for z in z_steps: for c in channels
|
|
254
|
+
- Plane storage: t0-z0-c0, t0-z0-c1, t0-z1-c0, t0-z1-c1, t1-z0-c0, ...
|
|
255
|
+
- This means C varies fastest, then Z, then T → OME order "XYCZT"
|
|
256
|
+
|
|
238
257
|
Returns
|
|
239
258
|
-------
|
|
240
259
|
A Pixels_DimensionOrder representing the dimension order compatible with OME
|
|
241
|
-
standards
|
|
242
|
-
(e.g., "XYCZT").
|
|
260
|
+
standards (e.g., "XYCZT").
|
|
243
261
|
"""
|
|
244
|
-
|
|
245
|
-
|
|
262
|
+
# Filter out 'p' and 'g' axes since they don't exist within a single OME Image
|
|
263
|
+
filtered_axes = [axis for axis in sequence.axis_order if axis not in {"p", "g"}]
|
|
264
|
+
|
|
265
|
+
# Reverse the order since useq is iteration order, OME is rasterization order
|
|
266
|
+
reversed_axes = filtered_axes[::-1]
|
|
267
|
+
dimension_order = "XY" + "".join(reversed_axes).upper()
|
|
246
268
|
|
|
269
|
+
# Ensure we have exactly 5 dimensions by adding missing ones
|
|
247
270
|
if len(dimension_order) != 5:
|
|
248
271
|
missing_axes = [axis for axis in "XYCZT" if axis not in dimension_order]
|
|
249
272
|
dimension_order += "".join(missing_axes)
|
|
@@ -375,8 +398,6 @@ def _build_pixels_object(
|
|
|
375
398
|
position_frames: list[FrameMetaV1],
|
|
376
399
|
) -> Pixels:
|
|
377
400
|
"""Build a Pixels object with the given parameters."""
|
|
378
|
-
from ome_types.model import MetadataOnly
|
|
379
|
-
|
|
380
401
|
return Pixels(
|
|
381
402
|
id=f"Pixels:{image_id}",
|
|
382
403
|
dimension_order=dimension_order,
|
|
@@ -391,11 +412,36 @@ def _build_pixels_object(
|
|
|
391
412
|
physical_size_y=dimension_info.pixel_size_um,
|
|
392
413
|
physical_size_y_unit=UnitsLength.MICROMETER,
|
|
393
414
|
channels=channels,
|
|
394
|
-
|
|
415
|
+
tiff_data_blocks=_build_tiff_data_list(position_frames),
|
|
395
416
|
planes=_build_plane_list(position_frames),
|
|
396
417
|
)
|
|
397
418
|
|
|
398
419
|
|
|
420
|
+
def _build_tiff_data_list(position_frames: list[FrameMetaV1]) -> list[TiffData]:
|
|
421
|
+
"""Build TiffData objects for frame metadata at a specific position."""
|
|
422
|
+
tiff_data_blocks = []
|
|
423
|
+
for frame_metadata in position_frames:
|
|
424
|
+
mda_event = _extract_mda_event(frame_metadata)
|
|
425
|
+
if mda_event is None: # pragma: no cover
|
|
426
|
+
continue
|
|
427
|
+
|
|
428
|
+
event_index = mda_event.index
|
|
429
|
+
z_index = event_index.get("z", 0)
|
|
430
|
+
c_index = event_index.get("c", 0)
|
|
431
|
+
t_index = event_index.get("t", 0)
|
|
432
|
+
|
|
433
|
+
# Create a TiffData block for this plane
|
|
434
|
+
tiff_data = TiffData(
|
|
435
|
+
first_z=z_index,
|
|
436
|
+
first_c=c_index,
|
|
437
|
+
first_t=t_index,
|
|
438
|
+
plane_count=1,
|
|
439
|
+
)
|
|
440
|
+
tiff_data_blocks.append(tiff_data)
|
|
441
|
+
|
|
442
|
+
return tiff_data_blocks
|
|
443
|
+
|
|
444
|
+
|
|
399
445
|
def _build_plane_list(position_frames: list[FrameMetaV1]) -> list[Plane]:
|
|
400
446
|
"""Build Plane objects for frame metadata at a specific position."""
|
|
401
447
|
planes = []
|
|
@@ -442,16 +488,22 @@ def _build_ome_plate(
|
|
|
442
488
|
# create a mapping from well name to acquisition indices
|
|
443
489
|
well_acquisition_map: dict[str, list[int]] = {}
|
|
444
490
|
for acquisition_index, position in enumerate(plate_plan.image_positions):
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
491
|
+
if (position_name := position.name) is not None:
|
|
492
|
+
# Extract base well name by removing FOV suffix ("A1_0000" -> "A1")
|
|
493
|
+
# This handles cases where well_points_plan creates multiple FOVs per well
|
|
494
|
+
base_well_name = position_name.split("_")[0]
|
|
495
|
+
|
|
496
|
+
if base_well_name not in well_acquisition_map:
|
|
497
|
+
well_acquisition_map[base_well_name] = []
|
|
498
|
+
|
|
499
|
+
well_acquisition_map[base_well_name].append(acquisition_index)
|
|
500
|
+
|
|
501
|
+
for well_index, ((row, col), name, pos) in enumerate(
|
|
502
|
+
zip(
|
|
503
|
+
plate_plan.selected_well_indices,
|
|
504
|
+
plate_plan.selected_well_names,
|
|
505
|
+
plate_plan.selected_well_positions,
|
|
506
|
+
)
|
|
455
507
|
):
|
|
456
508
|
# get all acquisition indices for this well
|
|
457
509
|
acquisition_indices = well_acquisition_map.get(name, [])
|
|
@@ -475,6 +527,7 @@ def _build_ome_plate(
|
|
|
475
527
|
|
|
476
528
|
wells.append(
|
|
477
529
|
Well(
|
|
530
|
+
id=f"Well:{well_index}",
|
|
478
531
|
row=row,
|
|
479
532
|
column=col,
|
|
480
533
|
well_samples=well_samples,
|
|
@@ -482,6 +535,7 @@ def _build_ome_plate(
|
|
|
482
535
|
)
|
|
483
536
|
|
|
484
537
|
return Plate(
|
|
538
|
+
id="Plate:0",
|
|
485
539
|
name=plate_plan.plate.name,
|
|
486
540
|
rows=plate_plan.plate.rows,
|
|
487
541
|
columns=plate_plan.plate.columns,
|
|
@@ -209,7 +209,8 @@ def image_infos(core: CMMCorePlus) -> tuple[ImageInfo, ...]:
|
|
|
209
209
|
infos.append(image_info(core))
|
|
210
210
|
finally:
|
|
211
211
|
# set the camera back to the originally selected device
|
|
212
|
-
|
|
212
|
+
with suppress(RuntimeError):
|
|
213
|
+
core.setCameraDevice(selected)
|
|
213
214
|
return tuple(infos)
|
|
214
215
|
|
|
215
216
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pymmcore-plus
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.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
|
|
@@ -35,12 +35,12 @@ Requires-Dist: pymmcore>=11.10.0.74.0
|
|
|
35
35
|
Requires-Dist: rich>=10.2.0
|
|
36
36
|
Requires-Dist: tensorstore!=0.1.72,>=0.1.67
|
|
37
37
|
Requires-Dist: tensorstore!=0.1.72,>=0.1.71; python_version >= '3.13'
|
|
38
|
-
Requires-Dist: typer>=0.
|
|
38
|
+
Requires-Dist: typer>=0.13.0
|
|
39
39
|
Requires-Dist: typing-extensions>=4
|
|
40
40
|
Requires-Dist: useq-schema>=0.7.2
|
|
41
41
|
Provides-Extra: cli
|
|
42
42
|
Requires-Dist: rich>=10.2.0; extra == 'cli'
|
|
43
|
-
Requires-Dist: typer>=0.
|
|
43
|
+
Requires-Dist: typer>=0.13.0; extra == 'cli'
|
|
44
44
|
Provides-Extra: io
|
|
45
45
|
Requires-Dist: tifffile>=2021.6.14; extra == 'io'
|
|
46
46
|
Requires-Dist: zarr<3,>=2.15; extra == 'io'
|
|
@@ -52,6 +52,8 @@ Provides-Extra: pyside2
|
|
|
52
52
|
Requires-Dist: pyside2>=5.15.2.1; extra == 'pyside2'
|
|
53
53
|
Provides-Extra: pyside6
|
|
54
54
|
Requires-Dist: pyside6==6.7.3; extra == 'pyside6'
|
|
55
|
+
Provides-Extra: simulate
|
|
56
|
+
Requires-Dist: pillow>=11.0; extra == 'simulate'
|
|
55
57
|
Description-Content-Type: text/markdown
|
|
56
58
|
|
|
57
59
|
# pymmcore-plus
|
|
@@ -4,8 +4,8 @@ pymmcore_plus/_benchmark.py,sha256=YJICxXleFQVbOluJdq4OujnIcTkkuMVzeB8GJ8nUv5I,6
|
|
|
4
4
|
pymmcore_plus/_build.py,sha256=RPTAuwCZWGL5IDJj4JZo1DIIouUsIqS3vnbPbG2_bRE,10993
|
|
5
5
|
pymmcore_plus/_cli.py,sha256=vjF02cLFt0hIJHN5jwhi7KKgqjsFJCE0Wihfg2_x7KQ,17410
|
|
6
6
|
pymmcore_plus/_discovery.py,sha256=rBsf_-H5nQ6MUOD8wPV0c0iOc46nU2RszmUxc7iziVs,12785
|
|
7
|
-
pymmcore_plus/_ipy_completion.py,sha256=
|
|
8
|
-
pymmcore_plus/_logger.py,sha256=
|
|
7
|
+
pymmcore_plus/_ipy_completion.py,sha256=po1DGirEdkTJDR3N2sqPb6P6FD6SSPEaQv0AZUPfPfY,17949
|
|
8
|
+
pymmcore_plus/_logger.py,sha256=cTzXN1SDU_F7DBLTUQhjJF35rlzFmH2CQDM6XGbdCXs,5419
|
|
9
9
|
pymmcore_plus/_pymmcore.py,sha256=hy0gfEhvQ84o7SFIB2nQp6A1w2L4U7VQrV5_qKsJoLQ,699
|
|
10
10
|
pymmcore_plus/_util.py,sha256=xskovMeMbFDpKcqEv-bADiwm37f-AmPpaPfTPPBjyyo,14278
|
|
11
11
|
pymmcore_plus/install.py,sha256=a7MqkqJFevXgouvLNflypCu7-Y9oe1KKOwImwhyIaO8,16000
|
|
@@ -17,11 +17,11 @@ pymmcore_plus/core/_adapter.py,sha256=eu2BhGe_dnoQrIsh-u3poxWXsiF2Y8pfbKIGWbUgOk
|
|
|
17
17
|
pymmcore_plus/core/_config.py,sha256=yWwOnW6f37lLt83MnodNce04az-g8YDjyo7BvMiTc8s,10672
|
|
18
18
|
pymmcore_plus/core/_config_group.py,sha256=R-o4xuPDBPQAC3s-mFsiKwHVKWR38L9qq_aoWdPrAq8,8542
|
|
19
19
|
pymmcore_plus/core/_constants.py,sha256=tpXCn4CdhevGabUwOSvn7klMRoiXiVPmsPlAofL6Ai4,14122
|
|
20
|
-
pymmcore_plus/core/_device.py,sha256=
|
|
20
|
+
pymmcore_plus/core/_device.py,sha256=YQypCWmYCjZsDFCS1_okSV5UMRWDwG0sQONvvg2GCXo,35019
|
|
21
21
|
pymmcore_plus/core/_metadata.py,sha256=L8x1gX_zXPz02BUqc7eqJM_Bey2G0RyX30SOBs2aBNc,2755
|
|
22
|
-
pymmcore_plus/core/_mmcore_plus.py,sha256=
|
|
23
|
-
pymmcore_plus/core/_property.py,sha256=
|
|
24
|
-
pymmcore_plus/core/_sequencing.py,sha256=
|
|
22
|
+
pymmcore_plus/core/_mmcore_plus.py,sha256=W5eVfBirgHCwUFeVx4eN-AznYI58AZQTzIFL5w7C0WQ,102733
|
|
23
|
+
pymmcore_plus/core/_property.py,sha256=NyDz8lE4SZ_TW69pWLCUkijVS-LsJzApPo8nek0dZbk,8513
|
|
24
|
+
pymmcore_plus/core/_sequencing.py,sha256=LyTYBVw8NAxf_csPWGpNVKV_9WLWJNBhBP17dpd9wUY,17003
|
|
25
25
|
pymmcore_plus/core/events/__init__.py,sha256=F8r10LEBLrAV8qfkXScSkpqfExdT2XoOx92OqSturpc,1078
|
|
26
26
|
pymmcore_plus/core/events/_deprecated.py,sha256=H_Sd63ZyV3Hkq8Imozi8OE7FIa_hhSRpEyhjLj4xTB8,2241
|
|
27
27
|
pymmcore_plus/core/events/_device_signal_view.py,sha256=NiM7qImggEsOEgfBJtxlr2mUIx5y184PLYIV_vgIo3w,1328
|
|
@@ -31,21 +31,27 @@ pymmcore_plus/core/events/_protocol.py,sha256=0EpuRrgbnG_abE8raixViU4QcJ59i2RWI7
|
|
|
31
31
|
pymmcore_plus/core/events/_psygnal.py,sha256=PogTZBA2Xd1PaKHtWpqkSqYOwRd4UM2XSHAHo7-UipI,2752
|
|
32
32
|
pymmcore_plus/core/events/_qsignals.py,sha256=aQfLz_E7E3gqE62KWmuyxekQB9N2rUZ9Dbd8c-TgV9U,4052
|
|
33
33
|
pymmcore_plus/experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
-
pymmcore_plus/experimental/
|
|
35
|
-
pymmcore_plus/experimental/
|
|
34
|
+
pymmcore_plus/experimental/simulate/__init__.py,sha256=G6xXILg9dHM9-4NSauxHzVvVViFb6N9ybYVf5gYdZ6w,2096
|
|
35
|
+
pymmcore_plus/experimental/simulate/_objects.py,sha256=fpdzLuDulSs9uTj14Ii44gDueY0saiHGIwM8ERaPyb4,19197
|
|
36
|
+
pymmcore_plus/experimental/simulate/_render.py,sha256=hI7S6mqvOE83hwwVYNhq7bh2ft4c0iqZLbEqq6uf_6s,18926
|
|
37
|
+
pymmcore_plus/experimental/simulate/_sample.py,sha256=4o7E9sZ3OLq_L069UhAck8MNJL-PaDS9pCf4-sKcKEs,4597
|
|
38
|
+
pymmcore_plus/experimental/unicore/__init__.py,sha256=nLaAFVNGlDN0cg0swLDi4BscPpu6fE62ncaHxKlNJ3E,735
|
|
39
|
+
pymmcore_plus/experimental/unicore/_device_manager.py,sha256=URsVj7NkYv6HRZenbLukGZfy3DO2CktyMOOtNY9i02U,7658
|
|
36
40
|
pymmcore_plus/experimental/unicore/_proxy.py,sha256=QPFnG9Mw6WCFSui8nUCabJTJL-Xyvyo-fbLM1Q949is,4703
|
|
37
41
|
pymmcore_plus/experimental/unicore/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
+
pymmcore_plus/experimental/unicore/core/_config.py,sha256=VvAuVitCuAPvEM_sVONduFFYQwrfkmuHEkwng6RcjRc,25052
|
|
38
43
|
pymmcore_plus/experimental/unicore/core/_sequence_buffer.py,sha256=uyTKacCZNgxmsssBoAt62TvZjtv892YaTojyIDicgRI,12050
|
|
39
|
-
pymmcore_plus/experimental/unicore/core/_unicore.py,sha256=
|
|
44
|
+
pymmcore_plus/experimental/unicore/core/_unicore.py,sha256=v-khGSmn_m5OqmCh31fLTUCLvIK4VfKRmb_imMd2esA,108436
|
|
40
45
|
pymmcore_plus/experimental/unicore/devices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
46
|
pymmcore_plus/experimental/unicore/devices/_camera.py,sha256=Ef0VATVVNldZoYlQv7Qykgw1I6G-jk2usk7qhjJFXJ8,8538
|
|
42
|
-
pymmcore_plus/experimental/unicore/devices/_device_base.py,sha256=
|
|
47
|
+
pymmcore_plus/experimental/unicore/devices/_device_base.py,sha256=j6OrLX_VhferFg50nRp7RCpJ4U_rwAGyx0rCh37AXaY,11359
|
|
43
48
|
pymmcore_plus/experimental/unicore/devices/_generic_device.py,sha256=mp5vHTh3pJI6PJjBc90198KvRJ2eE8GARdYrj9zs27k,317
|
|
49
|
+
pymmcore_plus/experimental/unicore/devices/_hub.py,sha256=fjBWK9A4UErsxNPhtE9dVbK23gG_3dYJ3mqt3AsNtTk,1705
|
|
44
50
|
pymmcore_plus/experimental/unicore/devices/_properties.py,sha256=O57Y0dGvYCe5D7FlO74J_n9Ov6yr0A02ccCo3M6aNyc,15610
|
|
45
51
|
pymmcore_plus/experimental/unicore/devices/_shutter.py,sha256=UwOkKMZ2j2npr1W8v2Kyp2fCPECHAU3RFlIoDMpRlts,744
|
|
46
52
|
pymmcore_plus/experimental/unicore/devices/_slm.py,sha256=L2axjzcNQlCSADkuYFwJlZFt2HvC3mCGfqk7VF72KJE,3018
|
|
47
|
-
pymmcore_plus/experimental/unicore/devices/_stage.py,sha256=
|
|
48
|
-
pymmcore_plus/experimental/unicore/devices/_state.py,sha256=
|
|
53
|
+
pymmcore_plus/experimental/unicore/devices/_stage.py,sha256=Bvvc0XV5GtYqytpCAbHBAA4v6KFvc5loOIjMJRXo69s,9609
|
|
54
|
+
pymmcore_plus/experimental/unicore/devices/_state.py,sha256=Q24CpBmM93JywpGTZLGKHUq5nf-OTTpC9mEFDZahRSU,6319
|
|
49
55
|
pymmcore_plus/mda/__init__.py,sha256=7VH-MqOcuK1JNSOG9HhES6Ac-Z-LuT8a0f2xPbGEt7w,344
|
|
50
56
|
pymmcore_plus/mda/_engine.py,sha256=0TmfM-y-fo2TlBTuAeaqZgSDFM3vUKqJxk7T_ze0cyE,38799
|
|
51
57
|
pymmcore_plus/mda/_protocol.py,sha256=10CDJ9H57oX1z0oqK3eShXyQhufHvvu3_8wdaCYpPIg,3254
|
|
@@ -55,16 +61,16 @@ pymmcore_plus/mda/events/__init__.py,sha256=Y3AermCe3megT48VMF9-6lGweAx1J13hAC_T
|
|
|
55
61
|
pymmcore_plus/mda/events/_protocol.py,sha256=9xs9sRaCGtOQcAPChsB_NxXs4g8eaEKuJHBzNRRtiII,1739
|
|
56
62
|
pymmcore_plus/mda/events/_psygnal.py,sha256=TdN1mFGpTPXmEs9iwFKSC1svv87PDZkT2JZvl0tEGrQ,640
|
|
57
63
|
pymmcore_plus/mda/events/_qsignals.py,sha256=tULQg-e_NX197DxJXaWHn1zLJ-4tzc9QyOAnsobEDtA,554
|
|
58
|
-
pymmcore_plus/mda/handlers/_5d_writer_base.py,sha256=
|
|
64
|
+
pymmcore_plus/mda/handlers/_5d_writer_base.py,sha256=_DFwCH3WjyggrO-VzGy9THADa4p1-k0zsLLtSstXnbw,12305
|
|
59
65
|
pymmcore_plus/mda/handlers/__init__.py,sha256=TbgpRdcs3BRdCf6uXJlwo_IIbxM6xXaLocKK1pyhU2Q,1286
|
|
60
66
|
pymmcore_plus/mda/handlers/_img_sequence_writer.py,sha256=XUJovvdWViTkn2VZr4XcovNIuBNZF4J4cCHIdwAs1WE,11639
|
|
61
67
|
pymmcore_plus/mda/handlers/_ome_tiff_writer.py,sha256=pqqdl3KQd0tH5Gp4rHVgYqqh2Y8iwoKRXTjwq1JLy1E,6239
|
|
62
68
|
pymmcore_plus/mda/handlers/_ome_zarr_writer.py,sha256=cKg3kJR7TId6M2qC1nJMLlxkv5vlfA5XEAlTIr9kt_E,12275
|
|
63
|
-
pymmcore_plus/mda/handlers/_tensorstore_handler.py,sha256=
|
|
69
|
+
pymmcore_plus/mda/handlers/_tensorstore_handler.py,sha256=QT3GeM_ojEsZ2mvfbsKHNgPSR-bvKQHD1a7ICN_gmds,15511
|
|
64
70
|
pymmcore_plus/mda/handlers/_util.py,sha256=pZydpKAXtQ_gjq5x1yNK1D0hfS7NUL2nH9ivOBg4abc,1600
|
|
65
71
|
pymmcore_plus/metadata/__init__.py,sha256=0o_v53kwR4U_RLlCnr7GD1G6OdFlVuUByIqXiaaM5uk,699
|
|
66
|
-
pymmcore_plus/metadata/_ome.py,sha256=
|
|
67
|
-
pymmcore_plus/metadata/functions.py,sha256=
|
|
72
|
+
pymmcore_plus/metadata/_ome.py,sha256=p9Gy3etqwGBVLFlxi3q68gYWdzPRE6N6wC-t3U4ygyc,18957
|
|
73
|
+
pymmcore_plus/metadata/functions.py,sha256=vFGnc_wj30hNPcCVX6pH2J8mKY8CCh6k8j8FbZ51F2k,12978
|
|
68
74
|
pymmcore_plus/metadata/schema.py,sha256=NxKujQChIXFT48OirNebankGaHNAD0GcA77tjkG4uGs,18390
|
|
69
75
|
pymmcore_plus/metadata/serialize.py,sha256=hpXJm0tzILELf6OYECMg0sQhuf-h25ob6_DDl-TUUME,3805
|
|
70
76
|
pymmcore_plus/model/__init__.py,sha256=zKZkkSpNK4ERu-VMdi9gvRrj1aXAjNaYxlYB5PdYSg0,479
|
|
@@ -76,8 +82,8 @@ pymmcore_plus/model/_device.py,sha256=AX3rO2gbY7AXJyMN3FfI_n2jl2V0IAPuBh7MiDA5Sq
|
|
|
76
82
|
pymmcore_plus/model/_microscope.py,sha256=69VV6cuevinOK_LhYEkQygHGesvCZefdn9YNt3mV618,11353
|
|
77
83
|
pymmcore_plus/model/_pixel_size_config.py,sha256=RXk8AAwARe8clsXue0GZfOTb1bxyXIsO0ibcDLHM4_s,3889
|
|
78
84
|
pymmcore_plus/model/_property.py,sha256=NQzNtnEzSCR9ogwx1cfi8X-qbJ_cBSJKdSBAaoKoPQ0,3720
|
|
79
|
-
pymmcore_plus-0.
|
|
80
|
-
pymmcore_plus-0.
|
|
81
|
-
pymmcore_plus-0.
|
|
82
|
-
pymmcore_plus-0.
|
|
83
|
-
pymmcore_plus-0.
|
|
85
|
+
pymmcore_plus-0.17.0.dist-info/METADATA,sha256=y5g8RnE8OMhJzGrA54IJL4O3LJ7ClWrulqGiWU0QG3M,8944
|
|
86
|
+
pymmcore_plus-0.17.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
87
|
+
pymmcore_plus-0.17.0.dist-info/entry_points.txt,sha256=NtFyndrQzBpUNJyil-8e5hMGke2utAf7mkGavTLcLOY,51
|
|
88
|
+
pymmcore_plus-0.17.0.dist-info/licenses/LICENSE,sha256=OHJjRpOPKKRc7FEnpehNWdR5LRBdBhUtIFG-ZI0dCEA,1522
|
|
89
|
+
pymmcore_plus-0.17.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|