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.
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 +830 -17
  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.0.dist-info}/METADATA +5 -3
  24. {pymmcore_plus-0.16.0.dist-info → pymmcore_plus-0.17.0.dist-info}/RECORD +27 -21
  25. {pymmcore_plus-0.16.0.dist-info → pymmcore_plus-0.17.0.dist-info}/WHEEL +1 -1
  26. {pymmcore_plus-0.16.0.dist-info → pymmcore_plus-0.17.0.dist-info}/entry_points.txt +0 -0
  27. {pymmcore_plus-0.16.0.dist-info → pymmcore_plus-0.17.0.dist-info}/licenses/LICENSE +0 -0
@@ -291,7 +291,7 @@ LABEL_COMPLETERS: dict[tuple[str, int], tuple[int, CoreLabelCompleter]] = {
291
291
  # fmt: on
292
292
 
293
293
 
294
- @context_matcher() # type: ignore[misc]
294
+ @context_matcher() # type: ignore[untyped-decorator]
295
295
  def cmmcoreplus_matcher(ctx: CompletionContext) -> SimpleMatcherResult:
296
296
  """
297
297
  Offer string completions for CMMCorePlus calls such as.
pymmcore_plus/_logger.py CHANGED
@@ -54,7 +54,7 @@ class CustomFormatter(logging.Formatter):
54
54
  return formatter.format(record)
55
55
 
56
56
 
57
- _FILE_FORMMATTER = logging.Formatter(
57
+ _FILE_FORMATTER = logging.Formatter(
58
58
  "%(asctime)s.%(msecs)03d tid0x%(thread)x [%(levelname)s,%(name)s] %(message)s",
59
59
  datefmt="%Y-%m-%dT%H:%M:%S",
60
60
  )
@@ -141,7 +141,7 @@ def configure_logging(
141
141
  log_file, maxBytes=file_rotation * 1_000_000, backupCount=file_retention
142
142
  )
143
143
  file_handler.setLevel(file_level)
144
- file_handler.setFormatter(_FILE_FORMMATTER)
144
+ file_handler.setFormatter(_FILE_FORMATTER)
145
145
  logger.addHandler(file_handler)
146
146
 
147
147
 
@@ -35,6 +35,12 @@ class Device:
35
35
  Device label assigned to this device.
36
36
  mmcore : CMMCorePlus
37
37
  CMMCorePlus instance that owns this device.
38
+ device_type : DeviceType or Device subclass, optional
39
+ The type of device to create. If not specified, the type will be inferred
40
+ from the core if the device is already loaded. If the device is not loaded,
41
+ an error will be raised. This parameter is mainly intended for usage when
42
+ calling from `CMMCorePlus.getDeviceObject()`. Otherwise, prefer using
43
+ `[SpecificDeviceSubclass].create()`.
38
44
 
39
45
  Examples
40
46
  --------
@@ -57,14 +63,39 @@ class Device:
57
63
  propertyChanged: PSignalInstance
58
64
 
59
65
  @classmethod
60
- def create(cls, device_label: str, mmcore: CMMCorePlus) -> Self:
61
- sub_cls = cls.get_subclass(device_label, mmcore)
66
+ def create(
67
+ cls,
68
+ device_label: str,
69
+ mmcore: CMMCorePlus,
70
+ device_type: type[Device] | DeviceType = DeviceType.Any,
71
+ ) -> Self:
72
+ if device_type in {DeviceType.Any, DeviceType.Unknown}:
73
+ try:
74
+ sub_cls = cls.get_subclass(device_label, mmcore)
75
+ except RuntimeError as e:
76
+ raise RuntimeError(
77
+ f"Could not determine device type for {device_label}. "
78
+ "If you are preloading a device object, "
79
+ "please specify `device_type` as a `pymmcore_plus.DeviceType`."
80
+ ) from e
81
+ else:
82
+ if isinstance(device_type, type) and issubclass(device_type, Device):
83
+ sub_cls = device_type
84
+ elif isinstance(device_type, DeviceType):
85
+ sub_cls = _TYPE_MAP[device_type]
86
+ else:
87
+ raise TypeError(
88
+ f"Invalid device_type: {device_type!r}. Must be a "
89
+ "pymmcore_plus `DeviceType` or `Device` subclass."
90
+ )
91
+
62
92
  # make sure it's an error to call this class method on a subclass with
63
93
  # a non-matching type
64
- if issubclass(sub_cls, cls):
65
- return sub_cls(device_label, mmcore)
66
- dev_type = mmcore.getDeviceType(device_label).name
67
- raise TypeError(f"Cannot cast {dev_type} {device_label!r} to {cls}")
94
+ if not issubclass(sub_cls, cls):
95
+ dev_type = mmcore.getDeviceType(device_label).name
96
+ raise TypeError(f"Cannot cast {dev_type} {device_label!r} to {cls}")
97
+
98
+ return sub_cls(device_label, mmcore)
68
99
 
69
100
  @classmethod
70
101
  def get_subclass(cls, device_label: str, mmcore: CMMCorePlus) -> type[Device]:
@@ -1268,18 +1268,7 @@ class CMMCorePlus(pymmcore.CMMCore):
1268
1268
  }
1269
1269
  }
1270
1270
  """
1271
- dev = _device.Device.create(device_label, mmcore=self)
1272
- if (isinstance(device_type, type) and not isinstance(dev, device_type)) or (
1273
- isinstance(device_type, DeviceType)
1274
- and device_type not in {DeviceType.Any, DeviceType.Unknown}
1275
- and dev.type() != device_type
1276
- ):
1277
- raise TypeError(
1278
- f"{device_type!r} requested but device with label "
1279
- f"{device_label!r} is a {dev.type()}."
1280
- )
1281
-
1282
- return dev
1271
+ return _device.Device.create(device_label, mmcore=self, device_type=device_type)
1283
1272
 
1284
1273
  def getConfigGroupObject(
1285
1274
  self, group_name: str, allow_missing: bool = False
@@ -2091,13 +2080,13 @@ class CMMCorePlus(pymmcore.CMMCore):
2091
2080
  super().deleteConfig(*args)
2092
2081
  self.events.configDeleted.emit(groupName, configName)
2093
2082
 
2094
- def deleteConfigGroup(self, group: str) -> None:
2083
+ def deleteConfigGroup(self, groupName: str) -> None:
2095
2084
  """Deletes an entire configuration `group`.
2096
2085
 
2097
2086
  **Why Override?** To emit a `configGroupDeleted` event.
2098
2087
  """
2099
- super().deleteConfigGroup(group)
2100
- self.events.configGroupDeleted.emit(group)
2088
+ super().deleteConfigGroup(groupName)
2089
+ self.events.configGroupDeleted.emit(groupName)
2101
2090
 
2102
2091
  @overload
2103
2092
  def defineConfig(self, groupName: str, configName: str) -> None: ...
@@ -149,7 +149,7 @@ class DeviceProperty:
149
149
  return self.core.getDeviceType(self.device)
150
150
 
151
151
  def allowedValues(self) -> tuple[str, ...]:
152
- """Return allowed values for this property, if contstrained."""
152
+ """Return allowed values for this property, if constrained."""
153
153
  # https://github.com/micro-manager/mmCoreAndDevices/issues/172
154
154
  allowed = self.core.getAllowedPropertyValues(self.device, self.name)
155
155
  if not allowed and self.deviceType() is DeviceType.StateDevice:
@@ -348,6 +348,8 @@ class EventCombiner:
348
348
  z_pos=first_event.z_pos,
349
349
  exposure=first_event.exposure,
350
350
  channel=first_event.channel,
351
+ min_start_time=first_event.min_start_time,
352
+ reset_event_timer=first_event.reset_event_timer,
351
353
  )
352
354
 
353
355
  # -------------- helper methods to query props & max lengths ----------------
@@ -0,0 +1,88 @@
1
+ """Simulated microscope sample for testing and development.
2
+
3
+ This module provides tools for creating virtual microscope samples that
4
+ integrate with CMMCorePlus. When a sample is installed on a core, image
5
+ acquisition returns rendered images based on the sample objects and
6
+ current microscope state (stage position, exposure, pixel size, etc.).
7
+
8
+ Examples
9
+ --------
10
+ Create a sample with some objects and use it with a core:
11
+
12
+ >>> from pymmcore_plus import CMMCorePlus
13
+ >>> from pymmcore_plus.experimental.simulate import (
14
+ ... Sample,
15
+ ... Point,
16
+ ... Line,
17
+ ... Rectangle,
18
+ ... RenderConfig,
19
+ ... )
20
+ >>>
21
+ >>> # Create core and load config
22
+ >>> core = CMMCorePlus.instance()
23
+ >>> core.loadSystemConfiguration()
24
+ >>>
25
+ >>> # Define sample objects (coordinates in microns)
26
+ >>> sample = Sample(
27
+ ... [
28
+ ... Point(0, 0, intensity=200, radius=5),
29
+ ... Point(50, 50, intensity=150, radius=3),
30
+ ... Line((0, 0), (100, 100), intensity=100),
31
+ ... Rectangle((20, 20), width=30, height=20, intensity=180, fill=True),
32
+ ... ]
33
+ ... )
34
+ >>>
35
+ >>> # Use as context manager
36
+ >>> with sample.patch(core):
37
+ ... core.snapImage()
38
+ ... img = core.getImage() # Returns rendered simulation
39
+ ... print(img.shape, img.dtype)
40
+
41
+ Custom render configuration:
42
+
43
+ >>> config = RenderConfig(
44
+ ... noise_std=5.0, # More noise
45
+ ... defocus_scale=0.2, # More blur with Z
46
+ ... shot_noise=False, # Disable shot noise
47
+ ... bit_depth=16, # 16-bit output
48
+ ... )
49
+ >>> sample = Sample([Point(0, 0)], config=config)
50
+
51
+ Manual install/uninstall:
52
+
53
+ >>> sample.install(core)
54
+ >>> # ... do stuff ...
55
+ >>> sample.uninstall()
56
+ """
57
+
58
+ from ._objects import (
59
+ Arc,
60
+ Bitmap,
61
+ Bounds,
62
+ Ellipse,
63
+ Line,
64
+ Point,
65
+ Polygon,
66
+ Rectangle,
67
+ RegularPolygon,
68
+ SampleObject,
69
+ rects_intersect,
70
+ )
71
+ from ._render import RenderConfig
72
+ from ._sample import Sample
73
+
74
+ __all__ = [
75
+ "Arc",
76
+ "Bitmap",
77
+ "Bounds",
78
+ "Ellipse",
79
+ "Line",
80
+ "Point",
81
+ "Polygon",
82
+ "Rectangle",
83
+ "RegularPolygon",
84
+ "RenderConfig",
85
+ "Sample",
86
+ "SampleObject",
87
+ "rects_intersect",
88
+ ]