polystore 0.2.7__tar.gz → 0.2.8__tar.gz
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.
- {polystore-0.2.7/src/polystore.egg-info → polystore-0.2.8}/PKG-INFO +1 -1
- {polystore-0.2.7 → polystore-0.2.8}/pyproject.toml +1 -1
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/__init__.py +2 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/omero_address.py +221 -25
- {polystore-0.2.7 → polystore-0.2.8/src/polystore.egg-info}/PKG-INFO +1 -1
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_omero_address.py +37 -0
- {polystore-0.2.7 → polystore-0.2.8}/LICENSE +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/README.md +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/setup.cfg +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/array_payload.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/async_init.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/atomic.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/backend_registry.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/base.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/bioformats_java.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/bioformats_storage.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/config.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/constants.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/disk.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/exceptions.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/fiji_stream.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/filemanager.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/formats.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/imagej_distribution.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/imagej_runtime.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/lazy_imports.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/memory.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/metadata_migration.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/metadata_writer.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/napari_stream.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/ome_zarr_storage.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/omero_local.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/omero_tables.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/omero_text.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/roi.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/roi_converters.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/__init__.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/_streaming_backend.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/base.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/handlers/__init__.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/handlers/fiji_images.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/handlers/fiji_rois.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/identity.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/receivers/__init__.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/receivers/batching/__init__.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/receivers/core/__init__.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/receivers/core/batch_engine.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/receivers/core/contracts.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/receivers/core/window_projection.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/receivers/fiji/__init__.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/receivers/fiji/fiji_batch_processor.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/receivers/napari/__init__.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/receivers/napari/layer_key.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/receivers/napari/napari_batch_processor.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/viewer_transport.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming_constants.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/utils.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/virtual_workspace.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/zarr.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/zarr_batch.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore/zmq_config.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore.egg-info/SOURCES.txt +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore.egg-info/dependency_links.txt +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore.egg-info/requires.txt +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/src/polystore.egg-info/top_level.txt +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_array_payload.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_atomic_lock.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_backend_registry.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_basic.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_bioformats_java.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_bioformats_storage.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_disk_backend.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_disk_coverage.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_disk_more.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_filemanager_backends.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_filemanager_extended.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_imagej_distribution.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_imagej_runtime.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_lazy_package_exports.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_memory_backend.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_memory_coverage.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_memory_type_authority.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_napari_batch_processor.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_ome_zarr_storage.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_omero_image_plane_batch.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_omero_tables.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_omero_text.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_png_format.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_roi.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_streaming_identity.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_streaming_metadata.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_streaming_receiver_core.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_streaming_shared_memory.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_viewer_transport.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_zarr_backend.py +0 -0
- {polystore-0.2.7 → polystore-0.2.8}/tests/test_zarr_batch.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "polystore"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.8"
|
|
8
8
|
description = "Framework-agnostic multi-backend storage abstraction for ML and scientific computing"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -71,6 +71,7 @@ from .metadata_writer import (
|
|
|
71
71
|
from .omero_address import (
|
|
72
72
|
OMEROAddressComponent,
|
|
73
73
|
OMEROPlaneAddress,
|
|
74
|
+
OMEROPlaneFilenameTemplate,
|
|
74
75
|
OMEROWellAddress,
|
|
75
76
|
)
|
|
76
77
|
from .roi import (
|
|
@@ -161,6 +162,7 @@ __all__ = [
|
|
|
161
162
|
"resolve_subdirectory_path",
|
|
162
163
|
"OMEROAddressComponent",
|
|
163
164
|
"OMEROPlaneAddress",
|
|
165
|
+
"OMEROPlaneFilenameTemplate",
|
|
164
166
|
"OMEROWellAddress",
|
|
165
167
|
"detect_legacy_format",
|
|
166
168
|
"migrate_legacy_metadata",
|
|
@@ -14,6 +14,39 @@ from typing import cast
|
|
|
14
14
|
OMEROAddressValue = str | int
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
@dataclass(frozen=True, slots=True)
|
|
18
|
+
class OMEROAddressTemplateField:
|
|
19
|
+
"""One symbolic field in an OMERO plane filename template."""
|
|
20
|
+
|
|
21
|
+
name: str
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def capture_pattern(cls) -> str:
|
|
25
|
+
"""Return the filename grammar for a symbolic template field."""
|
|
26
|
+
|
|
27
|
+
return r"\{[A-Za-z_][A-Za-z0-9_]*\}"
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_value(cls, value: object) -> OMEROAddressTemplateField | None:
|
|
31
|
+
"""Parse a serialized template field at the filename boundary."""
|
|
32
|
+
|
|
33
|
+
if not isinstance(value, str) or re.fullmatch(cls.capture_pattern(), value) is None:
|
|
34
|
+
return None
|
|
35
|
+
return cls(value[1:-1])
|
|
36
|
+
|
|
37
|
+
def __post_init__(self) -> None:
|
|
38
|
+
if re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", self.name) is None:
|
|
39
|
+
raise ValueError(f"Invalid OMERO address template field: {self.name!r}")
|
|
40
|
+
|
|
41
|
+
def render(self) -> str:
|
|
42
|
+
"""Render the field at the filename-template boundary."""
|
|
43
|
+
|
|
44
|
+
return f"{{{self.name}}}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
OMEROAddressTemplateValue = OMEROAddressValue | OMEROAddressTemplateField
|
|
48
|
+
|
|
49
|
+
|
|
17
50
|
class OMEROAddressComponentBehaviorABC(ABC):
|
|
18
51
|
"""Leaf behavior owned by one OMERO address component declaration."""
|
|
19
52
|
|
|
@@ -34,6 +67,28 @@ class OMEROAddressComponentBehaviorABC(ABC):
|
|
|
34
67
|
def filename_fragment(self, component_name: str, value: object) -> str:
|
|
35
68
|
"""Render one normalized component value."""
|
|
36
69
|
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def filename_template_pattern(self, component_name: str) -> str:
|
|
72
|
+
"""Return this component's exact-or-symbolic template grammar."""
|
|
73
|
+
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def filename_template_fragment(self, component_name: str, value: object) -> str:
|
|
76
|
+
"""Render one exact value or nominal symbolic template field."""
|
|
77
|
+
|
|
78
|
+
def normalize_template(
|
|
79
|
+
self,
|
|
80
|
+
component_name: str,
|
|
81
|
+
value: object,
|
|
82
|
+
) -> OMEROAddressTemplateValue:
|
|
83
|
+
"""Normalize one exact value or serialized template field."""
|
|
84
|
+
|
|
85
|
+
if isinstance(value, OMEROAddressTemplateField):
|
|
86
|
+
return value
|
|
87
|
+
template_field = OMEROAddressTemplateField.from_value(value)
|
|
88
|
+
if template_field is not None:
|
|
89
|
+
return template_field
|
|
90
|
+
return self.normalize(component_name, value)
|
|
91
|
+
|
|
37
92
|
def zero_based(self, component_name: str, value: object) -> int:
|
|
38
93
|
"""Project a plane coordinate onto a zero-based OMERO array index."""
|
|
39
94
|
|
|
@@ -60,6 +115,18 @@ class OMEROWellComponentBehavior(OMEROAddressComponentBehaviorABC):
|
|
|
60
115
|
def filename_fragment(self, component_name: str, value: object) -> str:
|
|
61
116
|
return self.normalize(component_name, value)
|
|
62
117
|
|
|
118
|
+
def filename_template_pattern(self, component_name: str) -> str:
|
|
119
|
+
return (
|
|
120
|
+
f"(?P<{component_name}>(?:[A-Za-z]+\\d+|"
|
|
121
|
+
f"{OMEROAddressTemplateField.capture_pattern()}))"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def filename_template_fragment(self, component_name: str, value: object) -> str:
|
|
125
|
+
normalized = self.normalize_template(component_name, value)
|
|
126
|
+
if isinstance(normalized, OMEROAddressTemplateField):
|
|
127
|
+
return normalized.render()
|
|
128
|
+
return cast(str, normalized)
|
|
129
|
+
|
|
63
130
|
|
|
64
131
|
@dataclass(frozen=True, slots=True)
|
|
65
132
|
class OMEROPlaneCoordinateBehavior(OMEROAddressComponentBehaviorABC):
|
|
@@ -92,6 +159,18 @@ class OMEROPlaneCoordinateBehavior(OMEROAddressComponentBehaviorABC):
|
|
|
92
159
|
normalized = self.normalize(component_name, value)
|
|
93
160
|
return f"_{self.prefix}{normalized:0{self.padding}d}"
|
|
94
161
|
|
|
162
|
+
def filename_template_pattern(self, component_name: str) -> str:
|
|
163
|
+
return (
|
|
164
|
+
f"_{self.prefix}(?P<{component_name}>(?:\\d+|"
|
|
165
|
+
f"{OMEROAddressTemplateField.capture_pattern()}))"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def filename_template_fragment(self, component_name: str, value: object) -> str:
|
|
169
|
+
normalized = self.normalize_template(component_name, value)
|
|
170
|
+
if isinstance(normalized, OMEROAddressTemplateField):
|
|
171
|
+
return f"_{self.prefix}{normalized.render()}"
|
|
172
|
+
return f"_{self.prefix}{normalized:0{self.padding}d}"
|
|
173
|
+
|
|
95
174
|
def zero_based(self, component_name: str, value: object) -> int:
|
|
96
175
|
return self.normalize(component_name, value) - 1
|
|
97
176
|
|
|
@@ -122,6 +201,15 @@ class OMEROAddressComponent(Enum):
|
|
|
122
201
|
def filename_fragment(self, value: object) -> str:
|
|
123
202
|
return self.value.filename_fragment(self.component_name, value)
|
|
124
203
|
|
|
204
|
+
def normalize_template(self, value: object) -> OMEROAddressTemplateValue:
|
|
205
|
+
return self.value.normalize_template(self.component_name, value)
|
|
206
|
+
|
|
207
|
+
def filename_template_pattern(self) -> str:
|
|
208
|
+
return self.value.filename_template_pattern(self.component_name)
|
|
209
|
+
|
|
210
|
+
def filename_template_fragment(self, value: object) -> str:
|
|
211
|
+
return self.value.filename_template_fragment(self.component_name, value)
|
|
212
|
+
|
|
125
213
|
def captured_value(self, match: re.Match[str]) -> OMEROAddressValue:
|
|
126
214
|
return self.normalize(match.group(self.component_name))
|
|
127
215
|
|
|
@@ -229,6 +317,41 @@ class OMEROWellAddress:
|
|
|
229
317
|
return f"{self.row_label}{self.column_index + 1:02d}"
|
|
230
318
|
|
|
231
319
|
|
|
320
|
+
def _require_complete_component_values(
|
|
321
|
+
component_values: Iterable[tuple[OMEROAddressComponent, object]],
|
|
322
|
+
) -> dict[OMEROAddressComponent, object]:
|
|
323
|
+
"""Validate and index one complete OMERO component projection."""
|
|
324
|
+
|
|
325
|
+
supplied_values = tuple(component_values)
|
|
326
|
+
if any(not isinstance(component, OMEROAddressComponent) for component, _ in supplied_values):
|
|
327
|
+
raise TypeError("OMERO addresses require OMEROAddressComponent members")
|
|
328
|
+
values_by_component = dict(supplied_values)
|
|
329
|
+
if len(values_by_component) != len(supplied_values):
|
|
330
|
+
raise ValueError("Each OMERO address component may be bound only once")
|
|
331
|
+
missing = tuple(
|
|
332
|
+
component for component in OMEROAddressComponent if component not in values_by_component
|
|
333
|
+
)
|
|
334
|
+
if missing:
|
|
335
|
+
raise ValueError(
|
|
336
|
+
"OMERO plane address must bind every declared component: "
|
|
337
|
+
+ ", ".join(f"missing {component.component_name}" for component in missing)
|
|
338
|
+
)
|
|
339
|
+
return values_by_component
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _normalize_extension(extension: str) -> str:
|
|
343
|
+
"""Normalize one OMERO plane filename extension."""
|
|
344
|
+
|
|
345
|
+
normalized_extension = str(extension).strip()
|
|
346
|
+
if not normalized_extension or any(
|
|
347
|
+
separator in normalized_extension for separator in ("/", "\\")
|
|
348
|
+
):
|
|
349
|
+
raise ValueError(f"Invalid OMERO plane extension: {extension!r}")
|
|
350
|
+
if not normalized_extension.startswith("."):
|
|
351
|
+
normalized_extension = f".{normalized_extension}"
|
|
352
|
+
return normalized_extension
|
|
353
|
+
|
|
354
|
+
|
|
232
355
|
@dataclass(frozen=True, slots=True, init=False)
|
|
233
356
|
class OMEROPlaneAddress:
|
|
234
357
|
"""Canonical virtual filename identity for one OMERO image plane."""
|
|
@@ -242,36 +365,13 @@ class OMEROPlaneAddress:
|
|
|
242
365
|
*,
|
|
243
366
|
extension: str = ".tif",
|
|
244
367
|
) -> None:
|
|
245
|
-
|
|
246
|
-
if any(
|
|
247
|
-
not isinstance(component, OMEROAddressComponent) for component, _ in supplied_values
|
|
248
|
-
):
|
|
249
|
-
raise TypeError("OMERO addresses require OMEROAddressComponent members")
|
|
250
|
-
values_by_component = dict(supplied_values)
|
|
251
|
-
if len(values_by_component) != len(supplied_values):
|
|
252
|
-
raise ValueError("Each OMERO address component may be bound only once")
|
|
253
|
-
missing = tuple(
|
|
254
|
-
component for component in OMEROAddressComponent if component not in values_by_component
|
|
255
|
-
)
|
|
256
|
-
if missing:
|
|
257
|
-
raise ValueError(
|
|
258
|
-
"OMERO plane address must bind every declared component: "
|
|
259
|
-
+ ", ".join(f"missing {component.component_name}" for component in missing)
|
|
260
|
-
)
|
|
261
|
-
|
|
368
|
+
values_by_component = _require_complete_component_values(component_values)
|
|
262
369
|
normalized_values = tuple(
|
|
263
370
|
(component, component.normalize(values_by_component[component]))
|
|
264
371
|
for component in OMEROAddressComponent
|
|
265
372
|
)
|
|
266
|
-
normalized_extension = str(extension).strip()
|
|
267
|
-
if not normalized_extension or any(
|
|
268
|
-
separator in normalized_extension for separator in ("/", "\\")
|
|
269
|
-
):
|
|
270
|
-
raise ValueError(f"Invalid OMERO plane extension: {extension!r}")
|
|
271
|
-
if not normalized_extension.startswith("."):
|
|
272
|
-
normalized_extension = f".{normalized_extension}"
|
|
273
373
|
object.__setattr__(self, "_component_values", normalized_values)
|
|
274
|
-
object.__setattr__(self, "extension",
|
|
374
|
+
object.__setattr__(self, "extension", _normalize_extension(extension))
|
|
275
375
|
|
|
276
376
|
@classmethod
|
|
277
377
|
def from_component_values(
|
|
@@ -460,3 +560,99 @@ class OMEROPlaneAddress:
|
|
|
460
560
|
)
|
|
461
561
|
+ self.extension
|
|
462
562
|
)
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
@dataclass(frozen=True, slots=True, init=False)
|
|
566
|
+
class OMEROPlaneFilenameTemplate:
|
|
567
|
+
"""Exact or symbolic OMERO plane identity for filename pattern systems."""
|
|
568
|
+
|
|
569
|
+
_component_values: tuple[tuple[OMEROAddressComponent, OMEROAddressTemplateValue], ...]
|
|
570
|
+
extension: str
|
|
571
|
+
|
|
572
|
+
def __init__(
|
|
573
|
+
self,
|
|
574
|
+
component_values: Iterable[tuple[OMEROAddressComponent, object]],
|
|
575
|
+
*,
|
|
576
|
+
extension: str = ".tif",
|
|
577
|
+
) -> None:
|
|
578
|
+
values_by_component = _require_complete_component_values(component_values)
|
|
579
|
+
object.__setattr__(
|
|
580
|
+
self,
|
|
581
|
+
"_component_values",
|
|
582
|
+
tuple(
|
|
583
|
+
(
|
|
584
|
+
component,
|
|
585
|
+
component.normalize_template(values_by_component[component]),
|
|
586
|
+
)
|
|
587
|
+
for component in OMEROAddressComponent
|
|
588
|
+
),
|
|
589
|
+
)
|
|
590
|
+
object.__setattr__(self, "extension", _normalize_extension(extension))
|
|
591
|
+
|
|
592
|
+
@classmethod
|
|
593
|
+
def from_member_projection(
|
|
594
|
+
cls,
|
|
595
|
+
source_values: Iterable[tuple[Enum, object]],
|
|
596
|
+
*,
|
|
597
|
+
extension: str = ".tif",
|
|
598
|
+
) -> OMEROPlaneFilenameTemplate:
|
|
599
|
+
"""Construct a template from another nominal component family."""
|
|
600
|
+
|
|
601
|
+
return cls(
|
|
602
|
+
OMEROAddressComponent.project_values(source_values),
|
|
603
|
+
extension=extension,
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
@classmethod
|
|
607
|
+
@cache
|
|
608
|
+
def filename_pattern(cls) -> re.Pattern[str]:
|
|
609
|
+
"""Derive exact-or-symbolic grammar from the component owners."""
|
|
610
|
+
|
|
611
|
+
return re.compile(
|
|
612
|
+
"^"
|
|
613
|
+
+ "".join(component.filename_template_pattern() for component in OMEROAddressComponent)
|
|
614
|
+
+ r"(?:_[^.]*)?(?P<extension>(?:\.[A-Za-z0-9]+)+)$"
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
@classmethod
|
|
618
|
+
def from_filename(cls, filename: str) -> OMEROPlaneFilenameTemplate | None:
|
|
619
|
+
"""Parse one exact or symbolic OMERO plane filename template."""
|
|
620
|
+
|
|
621
|
+
basename = PurePosixPath(str(filename).replace("\\", "/")).name
|
|
622
|
+
match = cls.filename_pattern().fullmatch(basename)
|
|
623
|
+
if match is None:
|
|
624
|
+
return None
|
|
625
|
+
return cls(
|
|
626
|
+
(
|
|
627
|
+
(
|
|
628
|
+
component,
|
|
629
|
+
component.normalize_template(match.group(component.component_name)),
|
|
630
|
+
)
|
|
631
|
+
for component in OMEROAddressComponent
|
|
632
|
+
),
|
|
633
|
+
extension=match.group("extension"),
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
def projected_values(
|
|
637
|
+
self,
|
|
638
|
+
) -> tuple[tuple[OMEROAddressComponent, OMEROAddressValue], ...]:
|
|
639
|
+
"""Project nominal template fields onto serialized boundary values."""
|
|
640
|
+
|
|
641
|
+
return tuple(
|
|
642
|
+
(
|
|
643
|
+
component,
|
|
644
|
+
value.render() if isinstance(value, OMEROAddressTemplateField) else value,
|
|
645
|
+
)
|
|
646
|
+
for component, value in self._component_values
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
def filename(self) -> str:
|
|
650
|
+
"""Render through component-owned exact-or-symbolic fragments."""
|
|
651
|
+
|
|
652
|
+
return (
|
|
653
|
+
"".join(
|
|
654
|
+
component.filename_template_fragment(value)
|
|
655
|
+
for component, value in self._component_values
|
|
656
|
+
)
|
|
657
|
+
+ self.extension
|
|
658
|
+
)
|
|
@@ -5,6 +5,7 @@ import pytest
|
|
|
5
5
|
from polystore import (
|
|
6
6
|
OMEROAddressComponent,
|
|
7
7
|
OMEROPlaneAddress,
|
|
8
|
+
OMEROPlaneFilenameTemplate,
|
|
8
9
|
OMEROWellAddress,
|
|
9
10
|
)
|
|
10
11
|
|
|
@@ -92,6 +93,42 @@ def test_omero_plane_address_binds_one_based_site_and_zero_based_plane_indices()
|
|
|
92
93
|
assert address.zero_based(OMEROAddressComponent.TIMEPOINT) == 2
|
|
93
94
|
|
|
94
95
|
|
|
96
|
+
def test_omero_plane_filename_template_owns_symbolic_component_round_trip() -> None:
|
|
97
|
+
template = OMEROPlaneFilenameTemplate(
|
|
98
|
+
(
|
|
99
|
+
(OMEROAddressComponent.WELL, "A01"),
|
|
100
|
+
(OMEROAddressComponent.SITE, "{iii}"),
|
|
101
|
+
(OMEROAddressComponent.CHANNEL, 2),
|
|
102
|
+
(OMEROAddressComponent.Z_INDEX, 3),
|
|
103
|
+
(OMEROAddressComponent.TIMEPOINT, 1),
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
assert template.filename() == "A01_s{iii}_w2_z003_t001.tif"
|
|
108
|
+
assert OMEROPlaneFilenameTemplate.from_filename(template.filename()) == template
|
|
109
|
+
assert OMEROPlaneFilenameTemplate.from_filename(
|
|
110
|
+
"A01_s001_w2_z003_t001_result_suffix.tif"
|
|
111
|
+
) == OMEROPlaneFilenameTemplate(
|
|
112
|
+
(
|
|
113
|
+
(OMEROAddressComponent.WELL, "A01"),
|
|
114
|
+
(OMEROAddressComponent.SITE, 1),
|
|
115
|
+
(OMEROAddressComponent.CHANNEL, 2),
|
|
116
|
+
(OMEROAddressComponent.Z_INDEX, 3),
|
|
117
|
+
(OMEROAddressComponent.TIMEPOINT, 1),
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
assert dict(template.projected_values()) == {
|
|
121
|
+
OMEROAddressComponent.WELL: "A01",
|
|
122
|
+
OMEROAddressComponent.SITE: "{iii}",
|
|
123
|
+
OMEROAddressComponent.CHANNEL: 2,
|
|
124
|
+
OMEROAddressComponent.Z_INDEX: 3,
|
|
125
|
+
OMEROAddressComponent.TIMEPOINT: 1,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
with pytest.raises(ValueError, match="site must be a positive integer"):
|
|
129
|
+
OMEROPlaneAddress(template.projected_values())
|
|
130
|
+
|
|
131
|
+
|
|
95
132
|
def test_omero_wire_mapping_requires_complete_declared_components() -> None:
|
|
96
133
|
with pytest.raises(ValueError, match="lacks declared fields: timepoint"):
|
|
97
134
|
OMEROPlaneAddress.from_wire_mapping(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/receivers/core/window_projection.py
RENAMED
|
File without changes
|
|
File without changes
|
{polystore-0.2.7 → polystore-0.2.8}/src/polystore/streaming/receivers/fiji/fiji_batch_processor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|