pymmcore-plus 0.15.4__py3-none-any.whl → 0.16.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.
@@ -0,0 +1,499 @@
1
+ from __future__ import annotations
2
+
3
+ import uuid
4
+ from contextlib import suppress
5
+ from datetime import datetime
6
+ from typing import TYPE_CHECKING, NamedTuple
7
+
8
+ import useq
9
+ from ome_types.model import (
10
+ OME,
11
+ Channel,
12
+ Image,
13
+ ImageRef,
14
+ Instrument,
15
+ Pixels,
16
+ Pixels_DimensionOrder,
17
+ PixelType,
18
+ Plane,
19
+ Plate,
20
+ UnitsLength,
21
+ UnitsTime,
22
+ Well,
23
+ WellSample,
24
+ )
25
+
26
+ from pymmcore_plus.mda._runner import GeneratorMDASequence
27
+
28
+ if TYPE_CHECKING:
29
+ from pymmcore_plus.metadata.schema import ImageInfo
30
+
31
+ from .schema import FrameMetaV1, SummaryMetaV1
32
+
33
+
34
+ __all__ = ["create_ome_metadata"]
35
+
36
+
37
+ def create_ome_metadata(
38
+ summary_metadata: SummaryMetaV1, frame_metadata_list: list[FrameMetaV1]
39
+ ) -> OME:
40
+ """Create OME metadata from metadata saved as json by the core engine.
41
+
42
+ Parameters
43
+ ----------
44
+ summary_metadata : SummaryMetaV1
45
+ Summary metadata containing acquisition information.
46
+ frame_metadata_list : list[FrameMetaV1]
47
+ List of frame metadata for each acquired image.
48
+
49
+ Returns
50
+ -------
51
+ OME
52
+ The OME metadata as an `ome_types.OME` object.
53
+ """
54
+ _uuid = f"urn:uuid:{uuid.uuid4()}"
55
+ ome = OME(uuid=_uuid)
56
+
57
+ ome.instruments = instruments = _build_instrument_list(summary_metadata)
58
+
59
+ image_infos = summary_metadata.get("image_infos", ())
60
+ if not frame_metadata_list or not image_infos:
61
+ return ome
62
+
63
+ sequence = _extract_mda_sequence(summary_metadata, frame_metadata_list[0])
64
+ position_groups = _group_frames_by_position(frame_metadata_list)
65
+ images = _build_ome_images(
66
+ dimension_info=_extract_dimension_info(image_infos[0]),
67
+ sequence=sequence,
68
+ position_groups=position_groups,
69
+ acquisition_date=_extract_acquisition_date(summary_metadata),
70
+ )
71
+
72
+ plates = []
73
+ if (plate_plan := _extract_plate_plan(sequence)) is not None:
74
+ position_to_image_mapping = _create_position_to_image_mapping(position_groups)
75
+ plates = [_build_ome_plate(plate_plan, position_to_image_mapping)]
76
+
77
+ return OME(
78
+ uuid=_uuid,
79
+ images=images,
80
+ instruments=instruments,
81
+ plates=plates,
82
+ )
83
+
84
+
85
+ # =============================================================================
86
+ # Data Structures
87
+ # =============================================================================
88
+
89
+
90
+ class _DimensionInfo(NamedTuple):
91
+ pixel_size_um: float
92
+ dtype: str | None
93
+ height: int
94
+ width: int
95
+
96
+
97
+ class _PositionKey(NamedTuple):
98
+ name: str | None
99
+ p_index: int
100
+ g_index: int | None = None
101
+
102
+ def __str__(self) -> str:
103
+ p_name = self.name or f"Pos{self.p_index:04d}"
104
+ if self.g_index is not None:
105
+ return f"{p_name}_Grid{self.g_index:04d}_{self.p_index}"
106
+ else:
107
+ return f"{p_name}_{self.p_index}"
108
+
109
+ @property
110
+ def image_id(self) -> str:
111
+ if self.g_index is not None:
112
+ return f"{self.p_index}_{self.g_index}"
113
+ return f"{self.p_index}"
114
+
115
+
116
+ # =============================================================================
117
+ # Metadata Extraction Functions
118
+ # =============================================================================
119
+
120
+
121
+ def _extract_dimension_info(
122
+ image_info: ImageInfo,
123
+ ) -> _DimensionInfo:
124
+ """Extract pixel size (µm), data type, width, and height from image_infos."""
125
+ return _DimensionInfo(
126
+ pixel_size_um=image_info.get("pixel_size_um", 1.0),
127
+ dtype=image_info.get("dtype", None),
128
+ width=image_info.get("width", 0),
129
+ height=image_info.get("height", 0),
130
+ )
131
+
132
+
133
+ def _extract_acquisition_date(summary_metadata: SummaryMetaV1) -> datetime | None:
134
+ """Extract acquisition date from summary metadata."""
135
+ if (acquisition_time := summary_metadata.get("datetime")) is not None:
136
+ with suppress(ValueError, AttributeError):
137
+ return datetime.fromisoformat(acquisition_time.replace("Z", "+00:00"))
138
+ return None
139
+
140
+
141
+ def _extract_mda_sequence(
142
+ summary_metadata: SummaryMetaV1, single_frame_metadata: FrameMetaV1
143
+ ) -> useq.MDASequence | None:
144
+ """Extract the MDA sequence from summary metadata or frame metadata."""
145
+ if (sequence_data := summary_metadata.get("mda_sequence")) is not None:
146
+ return useq.MDASequence.model_validate(sequence_data)
147
+ if (mda_event := _extract_mda_event(single_frame_metadata)) is not None:
148
+ return mda_event.sequence
149
+ return None
150
+
151
+
152
+ def _extract_mda_event(frame_metadata: FrameMetaV1) -> useq.MDAEvent | None:
153
+ """Extract the useq.MDAEvent from frame metadata."""
154
+ if (mda_event_data := frame_metadata.get("mda_event")) is not None:
155
+ return useq.MDAEvent.model_validate(mda_event_data)
156
+ return None # pragma: no cover
157
+
158
+
159
+ def _extract_plate_plan(
160
+ sequence: useq.MDASequence | None,
161
+ ) -> useq.WellPlatePlan | None:
162
+ """Extract the plate plan from the MDA sequence if it exists."""
163
+ if sequence is None: # pragma: no cover
164
+ return None
165
+ stage_positions = sequence.stage_positions
166
+ if isinstance(stage_positions, useq.WellPlatePlan):
167
+ return stage_positions
168
+ return None
169
+
170
+
171
+ # =============================================================================
172
+ # Frame Grouping and Processing
173
+ # =============================================================================
174
+
175
+
176
+ def _group_frames_by_position(
177
+ frame_metadata_list: list[FrameMetaV1],
178
+ ) -> dict[_PositionKey, list[FrameMetaV1]]:
179
+ """Reorganize frame metadata by stage position index in a dictionary.
180
+
181
+ Handles the 'g' axis (grid) by converting it to separate positions,
182
+ since OME doesn't support the 'g' axis. Each grid position becomes
183
+ a separate OME Image with names like "Pos0000_Grid0000".
184
+
185
+ Returns
186
+ -------
187
+ dict[str, list[FrameMetaV1]]
188
+ mapping of position identifier (e.g. 'Pos0000_Grid0000')
189
+ to list of `FrameMetaV1`.
190
+ """
191
+ frames_by_position: dict[_PositionKey, list[FrameMetaV1]] = {}
192
+ for frame_metadata in frame_metadata_list:
193
+ if (mda_event := _extract_mda_event(frame_metadata)) is None:
194
+ continue # pragma: no cover
195
+
196
+ p_index = mda_event.index.get(useq.Axis.POSITION, 0) or 0
197
+ g_index = mda_event.index.get(useq.Axis.GRID, None)
198
+ key = _PositionKey(mda_event.pos_name, p_index, g_index)
199
+ pos_list = frames_by_position.setdefault(key, [])
200
+ pos_list.append(frame_metadata)
201
+ return frames_by_position
202
+
203
+
204
+ def _create_position_to_image_mapping(
205
+ position_groups: dict[_PositionKey, list[FrameMetaV1]],
206
+ ) -> dict[int, str]:
207
+ """Create a mapping from position index to image ID."""
208
+ position_to_image_mapping: dict[int, str] = {}
209
+
210
+ for position_key, position_frames in position_groups.items():
211
+ if position_frames:
212
+ mda_event = _extract_mda_event(position_frames[0])
213
+ if mda_event is not None:
214
+ position_index = mda_event.index.get("p", 0)
215
+ position_to_image_mapping[position_index] = position_key.image_id
216
+ return position_to_image_mapping
217
+
218
+
219
+ # =============================================================================
220
+ # Dimension Order and Pixel Information
221
+ # =============================================================================
222
+
223
+
224
+ def _determine_dimension_order(
225
+ sequence: useq.MDASequence | None,
226
+ ) -> Pixels_DimensionOrder | None:
227
+ """Determine the dimension order for pixels."""
228
+ if sequence is None or isinstance(sequence, GeneratorMDASequence):
229
+ return Pixels_DimensionOrder.XYTCZ
230
+ return _extract_dimension_order_from_sequence(sequence)
231
+
232
+
233
+ def _extract_dimension_order_from_sequence(
234
+ sequence: useq.MDASequence,
235
+ ) -> Pixels_DimensionOrder:
236
+ """Extract axis order from a useq.MDASequence.
237
+
238
+ Returns
239
+ -------
240
+ A Pixels_DimensionOrder representing the dimension order compatible with OME
241
+ standards
242
+ (e.g., "XYCZT").
243
+ """
244
+ filtered_axes = (axis for axis in sequence.axis_order if axis not in {"p", "g"})
245
+ dimension_order = "XY" + "".join(filtered_axes).upper()
246
+
247
+ if len(dimension_order) != 5:
248
+ missing_axes = [axis for axis in "XYCZT" if axis not in dimension_order]
249
+ dimension_order += "".join(missing_axes)
250
+
251
+ return Pixels_DimensionOrder(dimension_order)
252
+
253
+
254
+ def _extract_pixel_dimensions_and_channels(
255
+ sequence: useq.MDASequence | None,
256
+ position_frames: list[FrameMetaV1],
257
+ image_id: str,
258
+ ) -> tuple[tuple[int, int, int], list[Channel]]:
259
+ """Extract pixel dimensions and channels from sequence or frames."""
260
+ if sequence is None or isinstance(sequence, GeneratorMDASequence):
261
+ return _extract_pixel_info_from_frames(position_frames, image_id)
262
+ return _extract_pixel_info_from_sequence(sequence, image_id)
263
+
264
+
265
+ def _extract_pixel_info_from_frames(
266
+ position_metadata: list[FrameMetaV1],
267
+ image_id: str,
268
+ ) -> tuple[tuple[int, int, int], list[Channel]]:
269
+ """Extract pixel dimensions and channel information from frame metadata.
270
+
271
+ Returns
272
+ -------
273
+ A tuple containing the maximum (t, z, c) dimensions, and a list of channels.
274
+ """
275
+ max_t, max_z, max_c = 0, 0, 0
276
+ channels: dict[int, Channel] = {}
277
+
278
+ for frame_metadata in position_metadata:
279
+ mda_event = _extract_mda_event(frame_metadata)
280
+ if mda_event is None: # pragma: no cover
281
+ continue
282
+
283
+ t_index = mda_event.index.get("t", 0)
284
+ z_index = mda_event.index.get("z", 0)
285
+ c_index = mda_event.index.get("c", 0)
286
+
287
+ max_t = max(max_t, t_index)
288
+ max_z = max(max_z, z_index)
289
+ max_c = max(max_c, c_index)
290
+
291
+ if c_index not in channels and mda_event.channel is not None:
292
+ channels[c_index] = Channel(
293
+ id=f"Channel:{image_id}:{c_index}",
294
+ name=mda_event.channel.config,
295
+ samples_per_pixel=1,
296
+ )
297
+
298
+ sorted_channels = [channels[i] for i in sorted(channels.keys())]
299
+ return (max_t + 1, max_z + 1, max_c + 1), sorted_channels
300
+
301
+
302
+ def _extract_pixel_info_from_sequence(
303
+ sequence: useq.MDASequence,
304
+ image_id: str,
305
+ ) -> tuple[tuple[int, int, int], list[Channel]]:
306
+ """Extract pixel dimensions and channel information from MDA sequence."""
307
+ max_t = sequence.sizes.get("t", 1)
308
+ max_z = sequence.sizes.get("z", 1)
309
+ channels = [
310
+ Channel(
311
+ id=f"Channel:{image_id}:{index}",
312
+ name=channel.config,
313
+ samples_per_pixel=1,
314
+ )
315
+ for index, channel in enumerate(sequence.channels)
316
+ ]
317
+ return (max_t, max_z, len(channels)), channels
318
+
319
+
320
+ # =============================================================================
321
+ # OME Object Builders
322
+ # =============================================================================
323
+
324
+
325
+ def _build_ome_images(
326
+ dimension_info: _DimensionInfo,
327
+ sequence: useq.MDASequence | None,
328
+ position_groups: dict[_PositionKey, list[FrameMetaV1]],
329
+ acquisition_date: datetime | None,
330
+ ) -> list[Image]:
331
+ """Build OME Images from grouped frame metadata by position."""
332
+ images = []
333
+ for position_key, position_frames in position_groups.items():
334
+ image_id = position_key.image_id
335
+ position_name = str(position_key)
336
+
337
+ dimension_order = _determine_dimension_order(sequence)
338
+ if not dimension_order: # pragma: no cover
339
+ continue
340
+
341
+ size_info, channels = _extract_pixel_dimensions_and_channels(
342
+ sequence, position_frames, image_id
343
+ )
344
+ max_t, max_z, max_c = size_info
345
+
346
+ pixels = _build_pixels_object(
347
+ image_id,
348
+ dimension_order,
349
+ dimension_info,
350
+ max_t,
351
+ max_z,
352
+ max_c,
353
+ channels,
354
+ position_frames,
355
+ )
356
+
357
+ image = Image(
358
+ acquisition_date=acquisition_date,
359
+ id=f"Image:{image_id}",
360
+ name=position_name,
361
+ pixels=pixels,
362
+ )
363
+ images.append(image)
364
+ return images
365
+
366
+
367
+ def _build_pixels_object(
368
+ image_id: str,
369
+ dimension_order: Pixels_DimensionOrder,
370
+ dimension_info: _DimensionInfo,
371
+ max_t: int,
372
+ max_z: int,
373
+ max_c: int,
374
+ channels: list[Channel],
375
+ position_frames: list[FrameMetaV1],
376
+ ) -> Pixels:
377
+ """Build a Pixels object with the given parameters."""
378
+ from ome_types.model import MetadataOnly
379
+
380
+ return Pixels(
381
+ id=f"Pixels:{image_id}",
382
+ dimension_order=dimension_order,
383
+ size_x=dimension_info.width,
384
+ size_y=dimension_info.height,
385
+ size_z=max(max_z, 1),
386
+ size_c=max(max_c, 1),
387
+ size_t=max(max_t, 1),
388
+ type=PixelType(dimension_info.dtype),
389
+ physical_size_x=dimension_info.pixel_size_um,
390
+ physical_size_x_unit=UnitsLength.MICROMETER,
391
+ physical_size_y=dimension_info.pixel_size_um,
392
+ physical_size_y_unit=UnitsLength.MICROMETER,
393
+ channels=channels,
394
+ metadata_only=MetadataOnly(),
395
+ planes=_build_plane_list(position_frames),
396
+ )
397
+
398
+
399
+ def _build_plane_list(position_frames: list[FrameMetaV1]) -> list[Plane]:
400
+ """Build Plane objects for frame metadata at a specific position."""
401
+ planes = []
402
+ for frame_metadata in position_frames:
403
+ mda_event = _extract_mda_event(frame_metadata)
404
+ if mda_event is None: # pragma: no cover
405
+ continue
406
+
407
+ event_index = mda_event.index
408
+ z_index = event_index.get("z", 0)
409
+ c_index = event_index.get("c", 0)
410
+ t_index = event_index.get("t", 0)
411
+
412
+ runner_time_ms = frame_metadata.get("runner_time_ms", 0.0)
413
+ delta_t = runner_time_ms / 1000.0 if runner_time_ms > 0 else None
414
+ exposure_ms = frame_metadata.get("exposure_ms", 0.0)
415
+
416
+ plane = Plane(
417
+ the_z=z_index,
418
+ the_c=c_index,
419
+ the_t=t_index,
420
+ position_x=mda_event.x_pos,
421
+ position_x_unit=UnitsLength.MICROMETER,
422
+ position_y=mda_event.y_pos,
423
+ position_y_unit=UnitsLength.MICROMETER,
424
+ position_z=mda_event.z_pos,
425
+ position_z_unit=UnitsLength.MICROMETER,
426
+ delta_t=delta_t,
427
+ delta_t_unit=UnitsTime.SECOND,
428
+ exposure_time=exposure_ms,
429
+ exposure_time_unit=UnitsTime.MILLISECOND,
430
+ )
431
+
432
+ planes.append(plane)
433
+ return planes
434
+
435
+
436
+ def _build_ome_plate(
437
+ plate_plan: useq.WellPlatePlan, position_to_image_mapping: dict[int, str]
438
+ ) -> Plate:
439
+ """Create a Plate object from a useq.WellPlatePlan."""
440
+ wells: list[Well] = []
441
+
442
+ # create a mapping from well name to acquisition indices
443
+ well_acquisition_map: dict[str, list[int]] = {}
444
+ 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,
455
+ ):
456
+ # get all acquisition indices for this well
457
+ acquisition_indices = well_acquisition_map.get(name, [])
458
+
459
+ # create WellSample objects for each acquisition in this well
460
+ well_samples = []
461
+ for acq_index in acquisition_indices:
462
+ # Use the actual image ID from the mapping
463
+ image_id = position_to_image_mapping.get(acq_index, str(acq_index))
464
+ well_samples.append(
465
+ WellSample(
466
+ id=f"WellSample:{acq_index}",
467
+ position_x=pos.x,
468
+ position_y=pos.y,
469
+ position_x_unit=UnitsLength.MICROMETER,
470
+ position_y_unit=UnitsLength.MICROMETER,
471
+ index=acq_index,
472
+ image_ref=ImageRef(id=f"Image:{image_id}"),
473
+ )
474
+ )
475
+
476
+ wells.append(
477
+ Well(
478
+ row=row,
479
+ column=col,
480
+ well_samples=well_samples,
481
+ )
482
+ )
483
+
484
+ return Plate(
485
+ name=plate_plan.plate.name,
486
+ rows=plate_plan.plate.rows,
487
+ columns=plate_plan.plate.columns,
488
+ wells=wells,
489
+ well_origin_x=plate_plan.a1_center_xy[0],
490
+ well_origin_x_unit=UnitsLength.MICROMETER,
491
+ well_origin_y=plate_plan.a1_center_xy[1],
492
+ well_origin_y_unit=UnitsLength.MICROMETER,
493
+ )
494
+
495
+
496
+ def _build_instrument_list(summary_metadata: SummaryMetaV1) -> list[Instrument]:
497
+ """Build instrument list from summary metadata."""
498
+ # TODO
499
+ return []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pymmcore-plus
3
- Version: 0.15.4
3
+ Version: 0.16.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
@@ -28,9 +28,10 @@ Requires-Python: >=3.9
28
28
  Requires-Dist: numpy>=1.25.2
29
29
  Requires-Dist: numpy>=1.26.0; python_version >= '3.12'
30
30
  Requires-Dist: numpy>=2.1.0; python_version >= '3.13'
31
+ Requires-Dist: ome-types>=0.6.0
31
32
  Requires-Dist: platformdirs>=3.0.0
32
33
  Requires-Dist: psygnal>=0.10
33
- Requires-Dist: pymmcore>=11.9.0.73.0
34
+ Requires-Dist: pymmcore>=11.10.0.74.0
34
35
  Requires-Dist: rich>=10.2.0
35
36
  Requires-Dist: tensorstore!=0.1.72,>=0.1.67
36
37
  Requires-Dist: tensorstore!=0.1.72,>=0.1.71; python_version >= '3.13'
@@ -1,13 +1,14 @@
1
- pymmcore_plus/__init__.py,sha256=trTkp3YbM8nahgsl1_N8snC5bs0GY7CX1v1ssVgBh2c,2243
2
- pymmcore_plus/_accumulator.py,sha256=WUgCrLW0U8_kWXfmhSRrC17ubWZNQNckkGVBiTp72F8,9205
1
+ pymmcore_plus/__init__.py,sha256=Z5CdLIfBVp1Oe-J-uW2VBzjBFas3GPMRea8OSPJYePM,3030
2
+ pymmcore_plus/_accumulator.py,sha256=kQzVAt-lS6DAokJz-nbB7oQBxb23bjT46kCiM_y7rJE,9853
3
3
  pymmcore_plus/_benchmark.py,sha256=YJICxXleFQVbOluJdq4OujnIcTkkuMVzeB8GJ8nUv5I,6011
4
4
  pymmcore_plus/_build.py,sha256=RPTAuwCZWGL5IDJj4JZo1DIIouUsIqS3vnbPbG2_bRE,10993
5
- pymmcore_plus/_cli.py,sha256=wmqlG8UGBNiTefRppSnRHwnD3w-5DAhN168lIBujbRA,16831
5
+ pymmcore_plus/_cli.py,sha256=vjF02cLFt0hIJHN5jwhi7KKgqjsFJCE0Wihfg2_x7KQ,17410
6
+ pymmcore_plus/_discovery.py,sha256=rBsf_-H5nQ6MUOD8wPV0c0iOc46nU2RszmUxc7iziVs,12785
6
7
  pymmcore_plus/_ipy_completion.py,sha256=K__bKTJZukzz0RhNr3KZ2urpQGxQrW8ySUQtHDoKfLA,17936
7
- pymmcore_plus/_logger.py,sha256=d7ldqxY0rGWORKdIzNUiFc9BW6cFBx57kHWtXyY1HE0,5416
8
+ pymmcore_plus/_logger.py,sha256=Fzwk8k6zvkhMKajx_vrVjbR-D2o6mKtYivG2Jgyflmc,5421
8
9
  pymmcore_plus/_pymmcore.py,sha256=hy0gfEhvQ84o7SFIB2nQp6A1w2L4U7VQrV5_qKsJoLQ,699
9
- pymmcore_plus/_util.py,sha256=VGcb_nQu6BBWZsQihed0TrCHdTRADKlBo_qSnIbeF0Q,22814
10
- pymmcore_plus/install.py,sha256=t05GmuxphIVkOr1CuFLAL6NmzL5ELWiXl7aivtQSoOo,10788
10
+ pymmcore_plus/_util.py,sha256=xskovMeMbFDpKcqEv-bADiwm37f-AmPpaPfTPPBjyyo,14278
11
+ pymmcore_plus/install.py,sha256=a7MqkqJFevXgouvLNflypCu7-Y9oe1KKOwImwhyIaO8,16000
11
12
  pymmcore_plus/mocks.py,sha256=jNUfmffD1OArtIvEmqWsy7GCrtTpssVF03flH8cEYx8,1867
12
13
  pymmcore_plus/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
14
  pymmcore_plus/seq_tester.py,sha256=ielLx2ZUJrOXVCojk64UXTeKDoARxt8QkQjt5AE5Gng,3776
@@ -16,14 +17,14 @@ pymmcore_plus/core/_adapter.py,sha256=eu2BhGe_dnoQrIsh-u3poxWXsiF2Y8pfbKIGWbUgOk
16
17
  pymmcore_plus/core/_config.py,sha256=yWwOnW6f37lLt83MnodNce04az-g8YDjyo7BvMiTc8s,10672
17
18
  pymmcore_plus/core/_config_group.py,sha256=R-o4xuPDBPQAC3s-mFsiKwHVKWR38L9qq_aoWdPrAq8,8542
18
19
  pymmcore_plus/core/_constants.py,sha256=tpXCn4CdhevGabUwOSvn7klMRoiXiVPmsPlAofL6Ai4,14122
19
- pymmcore_plus/core/_device.py,sha256=v58TcROSuvxE5ALBjzS474Dx430e0iMA61OuBSnc2rE,33197
20
+ pymmcore_plus/core/_device.py,sha256=x-fZcWu7KGScbxj5CPQomdFMYkvnFWJXGUJuYEfcYNc,33623
20
21
  pymmcore_plus/core/_metadata.py,sha256=L8x1gX_zXPz02BUqc7eqJM_Bey2G0RyX30SOBs2aBNc,2755
21
- pymmcore_plus/core/_mmcore_plus.py,sha256=_5d73MYRG4nOdyJkdL7BvFX8bDmWwbjfN5sCU_vxj8A,102842
22
- pymmcore_plus/core/_property.py,sha256=QsQEzqOAedR24zEJ1Ge4kwScfT_7NOApVcgz6QxBJrI,8265
22
+ pymmcore_plus/core/_mmcore_plus.py,sha256=3Ij6wWpTm2vcYKwdXY29pRKdrTkTd60va4hZMxAEU7c,103150
23
+ pymmcore_plus/core/_property.py,sha256=fiqxke6Y_3wsXrDTzbGJ4jioj_M-syWPL-7fufUPNow,8514
23
24
  pymmcore_plus/core/_sequencing.py,sha256=pOydKOsHjQOlYGp51hrSjHz02s4NoSEzQwTTSh3R4_Q,16887
24
25
  pymmcore_plus/core/events/__init__.py,sha256=F8r10LEBLrAV8qfkXScSkpqfExdT2XoOx92OqSturpc,1078
25
26
  pymmcore_plus/core/events/_deprecated.py,sha256=H_Sd63ZyV3Hkq8Imozi8OE7FIa_hhSRpEyhjLj4xTB8,2241
26
- pymmcore_plus/core/events/_device_signal_view.py,sha256=t-NfBdg3E4rms4vDFxkkR5XtrpLxaBT7mfPwkpIsbVk,1079
27
+ pymmcore_plus/core/events/_device_signal_view.py,sha256=NiM7qImggEsOEgfBJtxlr2mUIx5y184PLYIV_vgIo3w,1328
27
28
  pymmcore_plus/core/events/_norm_slot.py,sha256=8DCBoLHGh7cbB1OB19IJYwL6sFBFmkD8IakfBOvFbw8,2907
28
29
  pymmcore_plus/core/events/_prop_event_mixin.py,sha256=FvJJnpEKrOR-_Sp3-NNCwFoUUHwmNKiHruo0Y1vybsY,4042
29
30
  pymmcore_plus/core/events/_protocol.py,sha256=0EpuRrgbnG_abE8raixViU4QcJ59i2RWI7kwKIeZiBs,9072
@@ -31,11 +32,11 @@ pymmcore_plus/core/events/_psygnal.py,sha256=PogTZBA2Xd1PaKHtWpqkSqYOwRd4UM2XSHA
31
32
  pymmcore_plus/core/events/_qsignals.py,sha256=aQfLz_E7E3gqE62KWmuyxekQB9N2rUZ9Dbd8c-TgV9U,4052
32
33
  pymmcore_plus/experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
34
  pymmcore_plus/experimental/unicore/__init__.py,sha256=2jjw8K3DZ8NSY5mFn4YBTrYp78Zc9EbGIZCiyG7PujQ,682
34
- pymmcore_plus/experimental/unicore/_device_manager.py,sha256=I0kbIsxxT3tyeeSElK4l4kUd16hbw81fJv3UZCQPW2Q,6423
35
+ pymmcore_plus/experimental/unicore/_device_manager.py,sha256=nhL6YtvmghWRLST8QPNfBIA5A1dfgvxOZE-PARI1_PQ,6820
35
36
  pymmcore_plus/experimental/unicore/_proxy.py,sha256=QPFnG9Mw6WCFSui8nUCabJTJL-Xyvyo-fbLM1Q949is,4703
36
37
  pymmcore_plus/experimental/unicore/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
38
  pymmcore_plus/experimental/unicore/core/_sequence_buffer.py,sha256=uyTKacCZNgxmsssBoAt62TvZjtv892YaTojyIDicgRI,12050
38
- pymmcore_plus/experimental/unicore/core/_unicore.py,sha256=6invaoXaCpvPKqPGa2g5IKXjP5C6hKs0hDuihPs4lfc,72387
39
+ pymmcore_plus/experimental/unicore/core/_unicore.py,sha256=oTvkpFlgRILMBx_fSlABw_E9jHia1OX_00D4Ss9qoDQ,72544
39
40
  pymmcore_plus/experimental/unicore/devices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
41
  pymmcore_plus/experimental/unicore/devices/_camera.py,sha256=Ef0VATVVNldZoYlQv7Qykgw1I6G-jk2usk7qhjJFXJ8,8538
41
42
  pymmcore_plus/experimental/unicore/devices/_device_base.py,sha256=rmxzP-Cghn9bOA0m3I2a-7CspJAsp3OwTY6mv3bYzE0,10870
@@ -46,7 +47,7 @@ pymmcore_plus/experimental/unicore/devices/_slm.py,sha256=L2axjzcNQlCSADkuYFwJlZ
46
47
  pymmcore_plus/experimental/unicore/devices/_stage.py,sha256=NO96VqeLTdtEWBQ3fPerdQhvGK6Nmu57ZKSq5g9jMiA,7897
47
48
  pymmcore_plus/experimental/unicore/devices/_state.py,sha256=EpZAI9jYEQ9hU-H873ow9xY1u0oEXCs5lq6Op8-e-Xs,6091
48
49
  pymmcore_plus/mda/__init__.py,sha256=7VH-MqOcuK1JNSOG9HhES6Ac-Z-LuT8a0f2xPbGEt7w,344
49
- pymmcore_plus/mda/_engine.py,sha256=U83PnmbaWChxUCEskKqHVy-Xa8KAZAMuvwPSVTAwsKg,31205
50
+ pymmcore_plus/mda/_engine.py,sha256=0TmfM-y-fo2TlBTuAeaqZgSDFM3vUKqJxk7T_ze0cyE,38799
50
51
  pymmcore_plus/mda/_protocol.py,sha256=10CDJ9H57oX1z0oqK3eShXyQhufHvvu3_8wdaCYpPIg,3254
51
52
  pymmcore_plus/mda/_runner.py,sha256=NSOhpll6_WxDLO19FTs19dASJcHcOoVOCy7q_QzX_Ms,18523
52
53
  pymmcore_plus/mda/_thread_relay.py,sha256=Ww-9gyvLEzwRhnpL1dpze71wL7IRlhH8K3Q1dmJIxgs,6193
@@ -62,6 +63,7 @@ pymmcore_plus/mda/handlers/_ome_zarr_writer.py,sha256=cKg3kJR7TId6M2qC1nJMLlxkv5
62
63
  pymmcore_plus/mda/handlers/_tensorstore_handler.py,sha256=YrKwahypq1yoXP3cOcsJTGF-f4LOLrp4KuNHYzPBK64,15321
63
64
  pymmcore_plus/mda/handlers/_util.py,sha256=pZydpKAXtQ_gjq5x1yNK1D0hfS7NUL2nH9ivOBg4abc,1600
64
65
  pymmcore_plus/metadata/__init__.py,sha256=0o_v53kwR4U_RLlCnr7GD1G6OdFlVuUByIqXiaaM5uk,699
66
+ pymmcore_plus/metadata/_ome.py,sha256=tSY2SUKA1PNAexyde2fEX1a1owsbU_rsHx64H2ywR4s,16513
65
67
  pymmcore_plus/metadata/functions.py,sha256=Nw2zMbJx0c6aJs6I_uaLGz6cop0IIPfRZOR-qx-SQbc,12937
66
68
  pymmcore_plus/metadata/schema.py,sha256=NxKujQChIXFT48OirNebankGaHNAD0GcA77tjkG4uGs,18390
67
69
  pymmcore_plus/metadata/serialize.py,sha256=hpXJm0tzILELf6OYECMg0sQhuf-h25ob6_DDl-TUUME,3805
@@ -74,8 +76,8 @@ pymmcore_plus/model/_device.py,sha256=AX3rO2gbY7AXJyMN3FfI_n2jl2V0IAPuBh7MiDA5Sq
74
76
  pymmcore_plus/model/_microscope.py,sha256=69VV6cuevinOK_LhYEkQygHGesvCZefdn9YNt3mV618,11353
75
77
  pymmcore_plus/model/_pixel_size_config.py,sha256=RXk8AAwARe8clsXue0GZfOTb1bxyXIsO0ibcDLHM4_s,3889
76
78
  pymmcore_plus/model/_property.py,sha256=NQzNtnEzSCR9ogwx1cfi8X-qbJ_cBSJKdSBAaoKoPQ0,3720
77
- pymmcore_plus-0.15.4.dist-info/METADATA,sha256=-3XrqRBjgLgw1NqqiSnXjoLYIw0nteZgXIeOYpVl1Pg,8835
78
- pymmcore_plus-0.15.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
79
- pymmcore_plus-0.15.4.dist-info/entry_points.txt,sha256=NtFyndrQzBpUNJyil-8e5hMGke2utAf7mkGavTLcLOY,51
80
- pymmcore_plus-0.15.4.dist-info/licenses/LICENSE,sha256=OHJjRpOPKKRc7FEnpehNWdR5LRBdBhUtIFG-ZI0dCEA,1522
81
- pymmcore_plus-0.15.4.dist-info/RECORD,,
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,,