pymmcore-plus 0.13.1__py3-none-any.whl → 0.13.2__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.
@@ -209,10 +209,15 @@ class PortType(IntEnum):
209
209
  HIDPort = pymmcore.HIDPort
210
210
 
211
211
 
212
+ # NB:
213
+ # do *not* use `pymmcore.FocusDirection...` enums here.
214
+ # the MMCore API does not use the device enums (which is what pymmcore exposes)
215
+ # but instead translates MM::FocusDirectionTowardSample into a different number:
216
+ # https://github.com/micro-manager/mmCoreAndDevices/tree/MMCore/MMCore.cpp#L2063-L2074
212
217
  class FocusDirection(IntEnum):
213
- Unknown = pymmcore.FocusDirectionUnknown
214
- TowardSample = pymmcore.FocusDirectionTowardSample
215
- AwayFromSample = pymmcore.FocusDirectionAwayFromSample
218
+ Unknown = 0
219
+ TowardSample = 1
220
+ AwayFromSample = -1
216
221
  # aliases
217
222
  FocusDirectionUnknown = Unknown
218
223
  FocusDirectionTowardSample = TowardSample
@@ -1,6 +1,6 @@
1
1
  from ._engine import MDAEngine
2
2
  from ._protocol import PMDAEngine
3
- from ._runner import MDARunner
3
+ from ._runner import MDARunner, SupportsFrameReady
4
4
  from ._thread_relay import mda_listeners_connected
5
5
  from .events import PMDASignaler
6
6
 
@@ -9,5 +9,6 @@ __all__ = [
9
9
  "MDARunner",
10
10
  "PMDAEngine",
11
11
  "PMDASignaler",
12
+ "SupportsFrameReady",
12
13
  "mda_listeners_connected",
13
14
  ]
@@ -5,8 +5,9 @@ import warnings
5
5
  from collections.abc import Iterable, Iterator, Sequence
6
6
  from contextlib import AbstractContextManager, nullcontext
7
7
  from pathlib import Path
8
- from typing import TYPE_CHECKING, Any
8
+ from typing import TYPE_CHECKING, Any, cast
9
9
  from unittest.mock import MagicMock
10
+ from weakref import WeakSet
10
11
 
11
12
  from useq import MDASequence
12
13
 
@@ -17,13 +18,40 @@ from ._thread_relay import mda_listeners_connected
17
18
  from .events import PMDASignaler, _get_auto_MDA_callback_class
18
19
 
19
20
  if TYPE_CHECKING:
20
- from typing import TypeAlias
21
+ from typing import Protocol, TypeAlias
21
22
 
23
+ import numpy as np
22
24
  from useq import MDAEvent
23
25
 
26
+ from pymmcore_plus.metadata.schema import FrameMetaV1
27
+
24
28
  from ._engine import MDAEngine
25
29
 
26
- SingleOutput: TypeAlias = Path | str | object
30
+ class FrameReady0(Protocol):
31
+ """Data handler with a no-argument `frameReady` method."""
32
+
33
+ def frameReady(self) -> Any: ...
34
+
35
+ class FrameReady1(Protocol):
36
+ """Data handler with a `frameReady` method that takes `(image,)` ."""
37
+
38
+ def frameReady(self, img: np.ndarray, /) -> Any: ...
39
+
40
+ class FrameReady2(Protocol):
41
+ """Data handler with a `frameReady` method that takes `(image, event)`."""
42
+
43
+ def frameReady(self, img: np.ndarray, event: MDAEvent, /) -> Any: ...
44
+
45
+ class FrameReady3(Protocol):
46
+ """Data handler with a `frameReady` method that takes `(image, event, meta)`."""
47
+
48
+ def frameReady(
49
+ self, img: np.ndarray, event: MDAEvent, meta: FrameMetaV1, /
50
+ ) -> Any: ...
51
+
52
+
53
+ SupportsFrameReady: TypeAlias = "FrameReady0 | FrameReady1 | FrameReady2 | FrameReady3"
54
+ SingleOutput: TypeAlias = "Path | str | SupportsFrameReady"
27
55
 
28
56
  MSG = (
29
57
  "This sequence is a placeholder for a generator of events with unknown "
@@ -67,7 +95,7 @@ class MDARunner:
67
95
  self._paused = False
68
96
  self._paused_time: float = 0
69
97
  self._pause_interval: float = 0.1 # sec to wait between checking pause state
70
-
98
+ self._handlers: WeakSet[SupportsFrameReady] = WeakSet()
71
99
  self._canceled = False
72
100
  self._sequence: MDASequence | None = None
73
101
  # timer for the full sequence, reset only once at the beginning of the sequence
@@ -189,6 +217,10 @@ class MDARunner:
189
217
  - A handler object that implements the `DataHandler` protocol, currently
190
218
  meaning it has a `frameReady` method. See `mda_listeners_connected`
191
219
  for more details.
220
+
221
+ During the course of the sequence, the `get_output_handlers` method can be
222
+ used to get the currently connected output handlers (including those that
223
+ were created automatically based on file paths).
192
224
  """
193
225
  error = None
194
226
  sequence = events if isinstance(events, MDASequence) else GeneratorMDASequence()
@@ -206,6 +238,31 @@ class MDARunner:
206
238
  if error is not None:
207
239
  raise error
208
240
 
241
+ def get_output_handlers(self) -> tuple[SupportsFrameReady, ...]:
242
+ """Return the data handlers that are currently connected.
243
+
244
+ Output handlers are connected by passing them to the `output` parameter of the
245
+ `run` method; the run method accepts objects with a `frameReady` method *or*
246
+ strings representing paths. If a string is passed, a handler will be created
247
+ internally.
248
+
249
+ This method returns a tuple of currently connected handlers, including those
250
+ that were explicitly passed to `run()`, as well as those that were created based
251
+ on file paths. Internally, handlers are held by weak references, so if you want
252
+ the handler to persist, you must keep a reference to it. The only guaranteed
253
+ API that the handler will have is the `frameReady` method, but it could be any
254
+ user-defined object that implements that method.
255
+
256
+ Handlers are cleared each time `run()` is called, (but not at the end
257
+ of the sequence).
258
+
259
+ Returns
260
+ -------
261
+ tuple[SupportsFrameReady, ...]
262
+ Tuple of objects that (minimally) support the `frameReady` method.
263
+ """
264
+ return tuple(self._handlers)
265
+
209
266
  def seconds_elapsed(self) -> float:
210
267
  """Return the number of seconds since the start of the acquisition."""
211
268
  return time.perf_counter() - self._sequence_t0
@@ -228,48 +285,31 @@ class MDARunner:
228
285
  if isinstance(output, (str, Path)) or not isinstance(output, Sequence):
229
286
  output = [output]
230
287
 
231
- # convert all items to handler objects
232
- handlers: list[Any] = []
288
+ # convert all items to handler objects, preserving order
289
+ _handlers: list[SupportsFrameReady] = []
233
290
  for item in output:
234
291
  if isinstance(item, (str, Path)):
235
- handlers.append(self._handler_for_path(item))
292
+ _handlers.append(self._handler_for_path(item))
236
293
  else:
237
- # TODO: better check for valid handler protocol
238
- # quick hack for now.
239
- if not hasattr(item, "frameReady"):
294
+ if not callable(getattr(item, "frameReady", None)):
240
295
  raise TypeError(
241
- "Output handlers must have a frameReady method. "
296
+ "Output handlers must have a callable frameReady method. "
242
297
  f"Got {item} with type {type(item)}."
243
298
  )
244
- handlers.append(item)
299
+ _handlers.append(item)
245
300
 
246
- return mda_listeners_connected(*handlers, mda_events=self._signals)
301
+ self._handlers.clear()
302
+ self._handlers.update(_handlers)
303
+ return mda_listeners_connected(*_handlers, mda_events=self._signals)
247
304
 
248
- def _handler_for_path(self, path: str | Path) -> object:
305
+ def _handler_for_path(self, path: str | Path) -> SupportsFrameReady:
249
306
  """Convert a string or Path into a handler object.
250
307
 
251
308
  This method picks from the built-in handlers based on the extension of the path.
252
309
  """
253
- path = str(Path(path).expanduser().resolve())
254
- if path.endswith(".zarr"):
255
- from pymmcore_plus.mda.handlers import OMEZarrWriter
256
-
257
- return OMEZarrWriter(path)
258
-
259
- if path.endswith((".tiff", ".tif")):
260
- from pymmcore_plus.mda.handlers import OMETiffWriter
261
-
262
- return OMETiffWriter(path)
263
-
264
- # FIXME: ugly hack for the moment to represent a non-existent directory
265
- # there are many features that ImageSequenceWriter supports, and it's unclear
266
- # how to infer them all from a single string.
267
- if not (Path(path).suffix or Path(path).exists()):
268
- from pymmcore_plus.mda.handlers import ImageSequenceWriter
269
-
270
- return ImageSequenceWriter(path)
310
+ from pymmcore_plus.mda.handlers import handler_for_path
271
311
 
272
- raise ValueError(f"Could not infer a writer handler for path: '{path}'")
312
+ return cast("SupportsFrameReady", handler_for_path(path))
273
313
 
274
314
  def _run(self, engine: PMDAEngine, events: Iterable[MDAEvent]) -> None:
275
315
  """Main execution of events, inside the try/except block of `run`."""
@@ -1,3 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
1
5
  from ._img_sequence_writer import ImageSequenceWriter
2
6
  from ._ome_tiff_writer import OMETiffWriter
3
7
  from ._ome_zarr_writer import OMEZarrWriter
@@ -8,4 +12,29 @@ __all__ = [
8
12
  "OMETiffWriter",
9
13
  "OMEZarrWriter",
10
14
  "TensorStoreHandler",
15
+ "handler_for_path",
11
16
  ]
17
+
18
+
19
+ def handler_for_path(path: str | Path) -> object:
20
+ """Convert a string or Path into a handler object.
21
+
22
+ This method picks from the built-in handlers based on the extension of the path.
23
+ """
24
+ if str(path).rstrip("/").rstrip(":").lower() == "memory":
25
+ return TensorStoreHandler(kvstore="memory://")
26
+
27
+ path = str(Path(path).expanduser().resolve())
28
+ if path.endswith(".zarr"):
29
+ return OMEZarrWriter(path)
30
+
31
+ if path.endswith((".tiff", ".tif")):
32
+ return OMETiffWriter(path)
33
+
34
+ # FIXME: ugly hack for the moment to represent a non-existent directory
35
+ # there are many features that ImageSequenceWriter supports, and it's unclear
36
+ # how to infer them all from a single string.
37
+ if not (Path(path).suffix or Path(path).exists()):
38
+ return ImageSequenceWriter(path)
39
+
40
+ raise ValueError(f"Could not infer a writer handler for path: '{path}'")
@@ -22,6 +22,8 @@ if TYPE_CHECKING:
22
22
  import useq
23
23
  from typing_extensions import TypeAlias # py310
24
24
 
25
+ from pymmcore_plus.metadata.schema import FrameMetaV1
26
+
25
27
  ImgWriter: TypeAlias = Callable[[str, npt.NDArray], Any]
26
28
 
27
29
  FRAME_KEY = "frame"
@@ -118,7 +120,7 @@ class ImageSequenceWriter:
118
120
  shutil.rmtree(self._directory)
119
121
 
120
122
  # ongoing dict of frame meta... stored for easy rewrite without reading
121
- self._frame_metadata: dict[str, dict] = {}
123
+ self._frame_metadata: dict[str, FrameMetaV1] = {}
122
124
  self._frame_meta_file = self._directory.joinpath(self.FRAME_META_PATH)
123
125
  self._seq_meta_file = self._directory.joinpath(self.SEQ_META_PATH)
124
126
 
@@ -186,7 +188,9 @@ class ImageSequenceWriter:
186
188
  # write final frame metadata to disk
187
189
  self._frame_meta_file.write_bytes(json_dumps(self._frame_metadata, indent=2))
188
190
 
189
- def frameReady(self, frame: np.ndarray, event: useq.MDAEvent, meta: dict) -> None:
191
+ def frameReady(
192
+ self, frame: np.ndarray, event: useq.MDAEvent, meta: FrameMetaV1, /
193
+ ) -> None:
190
194
  """Write a frame to disk."""
191
195
  frame_idx = next(self._counter)
192
196
  if self._name_template:
@@ -111,6 +111,8 @@ class TensorStoreHandler:
111
111
  self.delete_existing = delete_existing
112
112
  self.spec = spec
113
113
 
114
+ self._current_sequence: useq.MDASequence | None = None
115
+
114
116
  # storage of individual frame metadata
115
117
  # maps position key to list of frame metadata
116
118
  self.frame_metadatas: list[tuple[useq.MDAEvent, FrameMetaV1]] = []
@@ -176,13 +178,25 @@ class TensorStoreHandler:
176
178
 
177
179
  return cls(path=path, **kwargs)
178
180
 
179
- def sequenceStarted(self, seq: useq.MDASequence, meta: SummaryMetaV1) -> None:
180
- """On sequence started, simply store the sequence."""
181
+ def reset(self, sequence: useq.MDASequence) -> None:
182
+ """Reset state to prepare for new `sequence`."""
181
183
  self._frame_index = 0
182
184
  self._store = None
183
185
  self._futures.clear()
184
186
  self.frame_metadatas.clear()
185
- self.current_sequence = seq
187
+ self._current_sequence = sequence
188
+
189
+ @property
190
+ def current_sequence(self) -> useq.MDASequence | None:
191
+ """Return current sequence, or none.
192
+
193
+ Use `.reset()` to initialize the handler for a new sequence.
194
+ """
195
+ return self._current_sequence
196
+
197
+ def sequenceStarted(self, seq: useq.MDASequence, meta: SummaryMetaV1) -> None:
198
+ """On sequence started, simply store the sequence."""
199
+ self.reset(seq)
186
200
 
187
201
  def sequenceFinished(self, seq: useq.MDASequence) -> None:
188
202
  """On sequence finished, clear the current sequence."""
@@ -199,7 +213,7 @@ class TensorStoreHandler:
199
213
  self.finalize_metadata()
200
214
 
201
215
  def frameReady(
202
- self, frame: np.ndarray, event: useq.MDAEvent, meta: FrameMetaV1
216
+ self, frame: np.ndarray, event: useq.MDAEvent, meta: FrameMetaV1, /
203
217
  ) -> None:
204
218
  """Write frame to the zarr array for the appropriate position."""
205
219
  if self._store is None:
@@ -209,18 +209,28 @@ def run_command(line: str, scope: Microscope) -> None:
209
209
  return
210
210
 
211
211
  exec_cmd, expected_n_args = COMMAND_EXECUTORS[command]
212
+ should_raise = command in SHOULD_RAISE
212
213
 
213
214
  if (nargs := len(args) + 1) not in expected_n_args:
214
215
  exp_str = " or ".join(map(str, expected_n_args))
215
- raise ValueError(
216
+ msg = (
216
217
  f"Invalid configuration line encountered for command {cmd_name}. "
217
218
  f"Expected {exp_str} arguments, got {nargs}: {line!r}"
218
219
  )
220
+ if should_raise:
221
+ raise ValueError(msg)
222
+ else:
223
+ warnings.warn(msg, RuntimeWarning, stacklevel=2)
224
+ return
219
225
 
220
226
  try:
221
227
  exec_cmd(scope, args)
222
228
  except Exception as exc:
223
- raise ValueError(f"Error executing command {line!r}: {exc}") from exc
229
+ if should_raise:
230
+ raise ValueError(f"Error executing command {line!r}: {exc}") from exc
231
+ warnings.warn(
232
+ f"Failed to execute command {line!r}: {exc}", RuntimeWarning, stacklevel=2
233
+ )
224
234
 
225
235
 
226
236
  def _exec_Device(scope: Microscope, args: Sequence[str]) -> None:
@@ -352,3 +362,6 @@ COMMAND_EXECUTORS: dict[CFGCommand, tuple[Executor, set[int]]] = {
352
362
  CFGCommand.ParentID: (_exec_ParentID, {3}),
353
363
  CFGCommand.FocusDirection: (_exec_FocusDirection, {3}),
354
364
  }
365
+
366
+ # Commands that should raise when fail
367
+ SHOULD_RAISE = {CFGCommand.Device, CFGCommand.Property}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pymmcore-plus
3
- Version: 0.13.1
3
+ Version: 0.13.2
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
@@ -13,7 +13,7 @@ pymmcore_plus/core/__init__.py,sha256=rYHv5JQVMVDlwYD1wodCc5L9ZbpVld1C_swGx4CRog
13
13
  pymmcore_plus/core/_adapter.py,sha256=eu2BhGe_dnoQrIsh-u3poxWXsiF2Y8pfbKIGWbUgOk8,2857
14
14
  pymmcore_plus/core/_config.py,sha256=yWwOnW6f37lLt83MnodNce04az-g8YDjyo7BvMiTc8s,10672
15
15
  pymmcore_plus/core/_config_group.py,sha256=R-o4xuPDBPQAC3s-mFsiKwHVKWR38L9qq_aoWdPrAq8,8542
16
- pymmcore_plus/core/_constants.py,sha256=ugXgCKVHt9E2s8outgA1cVgoB2Ea9Io4mcgxu5o_UAA,12617
16
+ pymmcore_plus/core/_constants.py,sha256=6foxGbek3tgnUHYUtQ7NCqwIIqqGYW1W56HjrhZqsA0,12829
17
17
  pymmcore_plus/core/_device.py,sha256=Pz9Ekhss2c9IBA3B7WyMU2cCwc19Dp_dGVhMkzqUaIE,7762
18
18
  pymmcore_plus/core/_metadata.py,sha256=3vb3d36XgNnUY44dpZ4Ccw0tvn4KCinZ8zrnDllmABI,2645
19
19
  pymmcore_plus/core/_mmcore_plus.py,sha256=8tjNxpSRfDLmYbNllLgJ2RVbKLpeUsOMb0hw-MPR9BI,91621
@@ -35,28 +35,28 @@ pymmcore_plus/experimental/unicore/devices/__init__.py,sha256=47DEQpj8HBSa-_TImW
35
35
  pymmcore_plus/experimental/unicore/devices/_device.py,sha256=PfX4BSpVMPXyNCNWkZ0Xy1-72ZZdME5zm7NgtCRu8ts,9751
36
36
  pymmcore_plus/experimental/unicore/devices/_properties.py,sha256=yqVyoXb3VSbHahN2mXOIgeKOS7pUQeiJIZ_Y2d55dTI,15387
37
37
  pymmcore_plus/experimental/unicore/devices/_stage.py,sha256=Ab4uibYq1cjIBtwcthCxH2FudGq9UMjub-qVeRpApQY,7892
38
- pymmcore_plus/mda/__init__.py,sha256=RXI4ubqV71lLTFVrj62UpGzXVwOOQfJSiXR0k5tRXdk,298
38
+ pymmcore_plus/mda/__init__.py,sha256=7VH-MqOcuK1JNSOG9HhES6Ac-Z-LuT8a0f2xPbGEt7w,344
39
39
  pymmcore_plus/mda/_engine.py,sha256=m-GsTMCIX2CgyUZb32Cyp52fh3WcUW--evk_yLANu-E,29534
40
40
  pymmcore_plus/mda/_protocol.py,sha256=10CDJ9H57oX1z0oqK3eShXyQhufHvvu3_8wdaCYpPIg,3254
41
- pymmcore_plus/mda/_runner.py,sha256=IeZ34e4rcjRoPayCYJo_xn33yB4kPFltt23ntHE9TVI,16679
41
+ pymmcore_plus/mda/_runner.py,sha256=NSOhpll6_WxDLO19FTs19dASJcHcOoVOCy7q_QzX_Ms,18523
42
42
  pymmcore_plus/mda/_thread_relay.py,sha256=Ww-9gyvLEzwRhnpL1dpze71wL7IRlhH8K3Q1dmJIxgs,6193
43
43
  pymmcore_plus/mda/events/__init__.py,sha256=rHTyhQZJ54dz-KtetvN22GvAY2ilR03x8v4H0qUR070,1191
44
44
  pymmcore_plus/mda/events/_protocol.py,sha256=9Q7LjYOgEWQGS8gHMV97UXM9bhoVW2OeyoPyNsQbwzw,1659
45
45
  pymmcore_plus/mda/events/_psygnal.py,sha256=TdN1mFGpTPXmEs9iwFKSC1svv87PDZkT2JZvl0tEGrQ,640
46
46
  pymmcore_plus/mda/events/_qsignals.py,sha256=tULQg-e_NX197DxJXaWHn1zLJ-4tzc9QyOAnsobEDtA,554
47
47
  pymmcore_plus/mda/handlers/_5d_writer_base.py,sha256=c9cA0n8DOBoZcy9asue5eV7jW8hFVC0XEewroFgDNHA,11925
48
- pymmcore_plus/mda/handlers/__init__.py,sha256=LdO3jf47fb4gXmbP9I8f5xC9Mrfd_-shf86OAaN9LdA,305
49
- pymmcore_plus/mda/handlers/_img_sequence_writer.py,sha256=fAj6CB90RXYJ2jJIRDFxZgQb-TfwuRPWGCPFopCijRI,11549
48
+ pymmcore_plus/mda/handlers/__init__.py,sha256=TbgpRdcs3BRdCf6uXJlwo_IIbxM6xXaLocKK1pyhU2Q,1286
49
+ pymmcore_plus/mda/handlers/_img_sequence_writer.py,sha256=XUJovvdWViTkn2VZr4XcovNIuBNZF4J4cCHIdwAs1WE,11639
50
50
  pymmcore_plus/mda/handlers/_ome_tiff_writer.py,sha256=pqqdl3KQd0tH5Gp4rHVgYqqh2Y8iwoKRXTjwq1JLy1E,6239
51
51
  pymmcore_plus/mda/handlers/_ome_zarr_writer.py,sha256=cKg3kJR7TId6M2qC1nJMLlxkv5vlfA5XEAlTIr9kt_E,12275
52
- pymmcore_plus/mda/handlers/_tensorstore_handler.py,sha256=_xaUkDEYoTO29u4iHPQK8ygcCy8CSCXYi1xu5QF8B8E,14829
52
+ pymmcore_plus/mda/handlers/_tensorstore_handler.py,sha256=_Hqfgc2I8n97KPT7quU0p4tqSlomtLbTO-e78bIB6hA,15280
53
53
  pymmcore_plus/mda/handlers/_util.py,sha256=pZydpKAXtQ_gjq5x1yNK1D0hfS7NUL2nH9ivOBg4abc,1600
54
54
  pymmcore_plus/metadata/__init__.py,sha256=0o_v53kwR4U_RLlCnr7GD1G6OdFlVuUByIqXiaaM5uk,699
55
55
  pymmcore_plus/metadata/functions.py,sha256=EjwB-6UO8c8AUriawhbE7x6ZAR1vJAxc72iYqyes5PQ,12506
56
56
  pymmcore_plus/metadata/schema.py,sha256=j7nMwjCBXaAC0zKA2OsF201dsOB_3b2ggjqIa7EiVPQ,17368
57
57
  pymmcore_plus/metadata/serialize.py,sha256=hpXJm0tzILELf6OYECMg0sQhuf-h25ob6_DDl-TUUME,3805
58
58
  pymmcore_plus/model/__init__.py,sha256=zKZkkSpNK4ERu-VMdi9gvRrj1aXAjNaYxlYB5PdYSg0,479
59
- pymmcore_plus/model/_config_file.py,sha256=Cw3bF1DLQUbMXVz59ZEUjfSaXcw8Lz6StIagReIKB-k,13280
59
+ pymmcore_plus/model/_config_file.py,sha256=nCAFh5dA7kYpoWTIwzoG4CHbdLwCYBBDGSOvZosFCFw,13711
60
60
  pymmcore_plus/model/_config_group.py,sha256=vL_-EWH-Nsb8xTgFqpYIFaJzBk_RDBFchBnQ61DMSvI,3407
61
61
  pymmcore_plus/model/_core_device.py,sha256=viwMgrCTZn1XYIyjC8w4xj1XAmoowZmCb93isGbG8BE,2722
62
62
  pymmcore_plus/model/_core_link.py,sha256=dsbT0gncfa3TAORSaWUrZR9rcI_nOLX9e5BTmyo-UYo,2737
@@ -64,8 +64,8 @@ pymmcore_plus/model/_device.py,sha256=-0s3NkonDoaMrNy_hn5EDz-c4o33ZiJSQkV_kdBteo
64
64
  pymmcore_plus/model/_microscope.py,sha256=69VV6cuevinOK_LhYEkQygHGesvCZefdn9YNt3mV618,11353
65
65
  pymmcore_plus/model/_pixel_size_config.py,sha256=smoOmT54nSkg52RaSQzTFG0YwyMR_SEq_lkS-JyJW9U,3514
66
66
  pymmcore_plus/model/_property.py,sha256=NQzNtnEzSCR9ogwx1cfi8X-qbJ_cBSJKdSBAaoKoPQ0,3720
67
- pymmcore_plus-0.13.1.dist-info/METADATA,sha256=TJaJKDATvzOOeJtqyDBP9Xsp1RZwLZQNM0TUdRvriEA,9594
68
- pymmcore_plus-0.13.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
69
- pymmcore_plus-0.13.1.dist-info/entry_points.txt,sha256=NtFyndrQzBpUNJyil-8e5hMGke2utAf7mkGavTLcLOY,51
70
- pymmcore_plus-0.13.1.dist-info/licenses/LICENSE,sha256=OHJjRpOPKKRc7FEnpehNWdR5LRBdBhUtIFG-ZI0dCEA,1522
71
- pymmcore_plus-0.13.1.dist-info/RECORD,,
67
+ pymmcore_plus-0.13.2.dist-info/METADATA,sha256=4z6G5lpau1oQJUFSejzKr88yNTWOzStR50mRZvaN6IU,9594
68
+ pymmcore_plus-0.13.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
69
+ pymmcore_plus-0.13.2.dist-info/entry_points.txt,sha256=NtFyndrQzBpUNJyil-8e5hMGke2utAf7mkGavTLcLOY,51
70
+ pymmcore_plus-0.13.2.dist-info/licenses/LICENSE,sha256=OHJjRpOPKKRc7FEnpehNWdR5LRBdBhUtIFG-ZI0dCEA,1522
71
+ pymmcore_plus-0.13.2.dist-info/RECORD,,