pymmcore-plus 0.16.0__py3-none-any.whl → 0.17.1__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.
Files changed (27) hide show
  1. pymmcore_plus/_ipy_completion.py +1 -1
  2. pymmcore_plus/_logger.py +2 -2
  3. pymmcore_plus/core/_device.py +37 -6
  4. pymmcore_plus/core/_mmcore_plus.py +4 -15
  5. pymmcore_plus/core/_property.py +1 -1
  6. pymmcore_plus/core/_sequencing.py +2 -0
  7. pymmcore_plus/experimental/simulate/__init__.py +88 -0
  8. pymmcore_plus/experimental/simulate/_objects.py +670 -0
  9. pymmcore_plus/experimental/simulate/_render.py +510 -0
  10. pymmcore_plus/experimental/simulate/_sample.py +156 -0
  11. pymmcore_plus/experimental/unicore/__init__.py +2 -0
  12. pymmcore_plus/experimental/unicore/_device_manager.py +26 -0
  13. pymmcore_plus/experimental/unicore/core/_config.py +706 -0
  14. pymmcore_plus/experimental/unicore/core/_unicore.py +832 -20
  15. pymmcore_plus/experimental/unicore/devices/_device_base.py +13 -0
  16. pymmcore_plus/experimental/unicore/devices/_hub.py +50 -0
  17. pymmcore_plus/experimental/unicore/devices/_stage.py +46 -1
  18. pymmcore_plus/experimental/unicore/devices/_state.py +6 -0
  19. pymmcore_plus/mda/handlers/_5d_writer_base.py +16 -5
  20. pymmcore_plus/mda/handlers/_tensorstore_handler.py +7 -1
  21. pymmcore_plus/metadata/_ome.py +75 -21
  22. pymmcore_plus/metadata/functions.py +2 -1
  23. {pymmcore_plus-0.16.0.dist-info → pymmcore_plus-0.17.1.dist-info}/METADATA +5 -3
  24. {pymmcore_plus-0.16.0.dist-info → pymmcore_plus-0.17.1.dist-info}/RECORD +27 -21
  25. {pymmcore_plus-0.16.0.dist-info → pymmcore_plus-0.17.1.dist-info}/WHEEL +1 -1
  26. {pymmcore_plus-0.16.0.dist-info → pymmcore_plus-0.17.1.dist-info}/entry_points.txt +0 -0
  27. {pymmcore_plus-0.16.0.dist-info → pymmcore_plus-0.17.1.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, position_index: int) -> str:
127
- """Get the position key for a specific position index.
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
- return f"{POS_PREFIX}{position_index}"
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(p_index)
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.get_position_key(p_index)]
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
- self.kvstore = f"file://{path}" if path is not None else kvstore
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
 
@@ -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
- return f"{p_name}_Grid{self.g_index:04d}_{self.p_index}"
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
- return f"{p_name}_{self.p_index}"
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}_{self.g_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
- filtered_axes = (axis for axis in sequence.axis_order if axis not in {"p", "g"})
245
- dimension_order = "XY" + "".join(filtered_axes).upper()
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
- metadata_only=MetadataOnly(),
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
- well_name = position.name
446
- if well_name is not None:
447
- if well_name not in well_acquisition_map:
448
- well_acquisition_map[well_name] = []
449
- well_acquisition_map[well_name].append(acquisition_index)
450
-
451
- for (row, col), name, pos in zip(
452
- plate_plan.selected_well_indices,
453
- plate_plan.selected_well_names,
454
- plate_plan.selected_well_positions,
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
- core.setCameraDevice(selected)
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.16.0
3
+ Version: 0.17.1
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.4.2
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.4.2; extra == 'cli'
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=K__bKTJZukzz0RhNr3KZ2urpQGxQrW8ySUQtHDoKfLA,17936
8
- pymmcore_plus/_logger.py,sha256=Fzwk8k6zvkhMKajx_vrVjbR-D2o6mKtYivG2Jgyflmc,5421
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=x-fZcWu7KGScbxj5CPQomdFMYkvnFWJXGUJuYEfcYNc,33623
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=3Ij6wWpTm2vcYKwdXY29pRKdrTkTd60va4hZMxAEU7c,103150
23
- pymmcore_plus/core/_property.py,sha256=fiqxke6Y_3wsXrDTzbGJ4jioj_M-syWPL-7fufUPNow,8514
24
- pymmcore_plus/core/_sequencing.py,sha256=pOydKOsHjQOlYGp51hrSjHz02s4NoSEzQwTTSh3R4_Q,16887
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/unicore/__init__.py,sha256=2jjw8K3DZ8NSY5mFn4YBTrYp78Zc9EbGIZCiyG7PujQ,682
35
- pymmcore_plus/experimental/unicore/_device_manager.py,sha256=nhL6YtvmghWRLST8QPNfBIA5A1dfgvxOZE-PARI1_PQ,6820
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=oTvkpFlgRILMBx_fSlABw_E9jHia1OX_00D4Ss9qoDQ,72544
44
+ pymmcore_plus/experimental/unicore/core/_unicore.py,sha256=YclLF9CiUMgyd2MMiHgTqS6N62FZvX6mNii88YC_5m0,108330
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=rmxzP-Cghn9bOA0m3I2a-7CspJAsp3OwTY6mv3bYzE0,10870
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=NO96VqeLTdtEWBQ3fPerdQhvGK6Nmu57ZKSq5g9jMiA,7897
48
- pymmcore_plus/experimental/unicore/devices/_state.py,sha256=EpZAI9jYEQ9hU-H873ow9xY1u0oEXCs5lq6Op8-e-Xs,6091
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=c9cA0n8DOBoZcy9asue5eV7jW8hFVC0XEewroFgDNHA,11925
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=YrKwahypq1yoXP3cOcsJTGF-f4LOLrp4KuNHYzPBK64,15321
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=tSY2SUKA1PNAexyde2fEX1a1owsbU_rsHx64H2ywR4s,16513
67
- pymmcore_plus/metadata/functions.py,sha256=Nw2zMbJx0c6aJs6I_uaLGz6cop0IIPfRZOR-qx-SQbc,12937
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.16.0.dist-info/METADATA,sha256=ob6RhMldyjqQmKL35L2rBZHCftp_npNd1PKLv6m5bQM,8868
80
- pymmcore_plus-0.16.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
81
- pymmcore_plus-0.16.0.dist-info/entry_points.txt,sha256=NtFyndrQzBpUNJyil-8e5hMGke2utAf7mkGavTLcLOY,51
82
- pymmcore_plus-0.16.0.dist-info/licenses/LICENSE,sha256=OHJjRpOPKKRc7FEnpehNWdR5LRBdBhUtIFG-ZI0dCEA,1522
83
- pymmcore_plus-0.16.0.dist-info/RECORD,,
85
+ pymmcore_plus-0.17.1.dist-info/METADATA,sha256=FlmOwbeFAHJnI1uft8vklgTG9EI_VsBIMiUhALOIJ9Q,8944
86
+ pymmcore_plus-0.17.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
87
+ pymmcore_plus-0.17.1.dist-info/entry_points.txt,sha256=NtFyndrQzBpUNJyil-8e5hMGke2utAf7mkGavTLcLOY,51
88
+ pymmcore_plus-0.17.1.dist-info/licenses/LICENSE,sha256=OHJjRpOPKKRc7FEnpehNWdR5LRBdBhUtIFG-ZI0dCEA,1522
89
+ pymmcore_plus-0.17.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any