polystore 0.2.0__tar.gz → 0.2.2__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.0/src/polystore.egg-info → polystore-0.2.2}/PKG-INFO +2 -1
- {polystore-0.2.0 → polystore-0.2.2}/pyproject.toml +2 -1
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/__init__.py +2 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/disk.py +11 -8
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/imagej_runtime.py +18 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/omero_local.py +4 -26
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/omero_tables.py +96 -0
- {polystore-0.2.0 → polystore-0.2.2/src/polystore.egg-info}/PKG-INFO +2 -1
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore.egg-info/requires.txt +1 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_disk_backend.py +18 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_imagej_runtime.py +9 -1
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_omero_tables.py +62 -1
- {polystore-0.2.0 → polystore-0.2.2}/LICENSE +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/README.md +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/setup.cfg +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/array_payload.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/async_init.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/atomic.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/backend_registry.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/base.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/bioformats_java.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/bioformats_storage.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/config.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/constants.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/exceptions.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/fiji_stream.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/filemanager.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/formats.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/lazy_imports.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/memory.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/metadata_migration.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/metadata_writer.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/napari_stream.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/ome_zarr_storage.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/omero_text.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/roi.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/roi_converters.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/__init__.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/_streaming_backend.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/base.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/handlers/__init__.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/handlers/fiji_images.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/handlers/fiji_rois.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/identity.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/receivers/__init__.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/receivers/batching/__init__.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/receivers/core/__init__.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/receivers/core/batch_engine.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/receivers/core/contracts.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/receivers/core/window_projection.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/receivers/fiji/__init__.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/receivers/fiji/fiji_batch_processor.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/receivers/napari/__init__.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/receivers/napari/layer_key.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/receivers/napari/napari_batch_processor.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming/viewer_transport.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/streaming_constants.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/utils.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/virtual_workspace.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/zarr.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/zarr_batch.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore/zmq_config.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore.egg-info/SOURCES.txt +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore.egg-info/dependency_links.txt +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/src/polystore.egg-info/top_level.txt +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_array_payload.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_backend_registry.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_basic.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_bioformats_java.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_bioformats_storage.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_disk_coverage.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_disk_more.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_filemanager_backends.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_filemanager_extended.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_lazy_package_exports.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_memory_backend.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_memory_coverage.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_memory_type_authority.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_napari_batch_processor.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_ome_zarr_storage.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_omero_image_plane_batch.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_omero_text.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_png_format.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_roi.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_streaming_identity.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_streaming_metadata.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_streaming_receiver_core.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_streaming_shared_memory.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_viewer_transport.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_zarr_backend.py +0 -0
- {polystore-0.2.0 → polystore-0.2.2}/tests/test_zarr_batch.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polystore
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Framework-agnostic multi-backend storage abstraction for ML and scientific computing
|
|
5
5
|
Author-email: Tristan Simas <tristan.simas@mail.mcgill.ca>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -58,6 +58,7 @@ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
|
58
58
|
Requires-Dist: coverage>=7.3.2; extra == "dev"
|
|
59
59
|
Requires-Dist: genbadge[coverage]; extra == "dev"
|
|
60
60
|
Requires-Dist: pandas>=2.3.1; extra == "dev"
|
|
61
|
+
Requires-Dist: jpype1>=1.4.0; extra == "dev"
|
|
61
62
|
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
62
63
|
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
63
64
|
Provides-Extra: docs
|
|
@@ -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.2"
|
|
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"
|
|
@@ -94,6 +94,7 @@ dev = [
|
|
|
94
94
|
"coverage>=7.3.2",
|
|
95
95
|
"genbadge[coverage]",
|
|
96
96
|
"pandas>=2.3.1",
|
|
97
|
+
"jpype1>=1.4.0",
|
|
97
98
|
"black>=23.0.0",
|
|
98
99
|
"ruff>=0.1.0",
|
|
99
100
|
]
|
|
@@ -40,6 +40,7 @@ from .filemanager import FileManager
|
|
|
40
40
|
from .formats import DEFAULT_IMAGE_EXTENSIONS, FileFormat
|
|
41
41
|
from .imagej_runtime import (
|
|
42
42
|
FIJI_IMAGEJ_RUNTIME,
|
|
43
|
+
ImageJJvmTeardown,
|
|
43
44
|
ImageJRuntimePolicy,
|
|
44
45
|
ImageJRuntimeUnavailableError,
|
|
45
46
|
)
|
|
@@ -96,6 +97,7 @@ __all__ = [
|
|
|
96
97
|
"Backend",
|
|
97
98
|
"MemoryType",
|
|
98
99
|
"FileFormat",
|
|
100
|
+
"ImageJJvmTeardown",
|
|
99
101
|
"ImageJRuntimePolicy",
|
|
100
102
|
"ImageJRuntimeUnavailableError",
|
|
101
103
|
"FIJI_IMAGEJ_RUNTIME",
|
|
@@ -24,6 +24,7 @@ from .exceptions import StorageResolutionError
|
|
|
24
24
|
from .formats import FileFormat
|
|
25
25
|
|
|
26
26
|
logger = logging.getLogger(__name__)
|
|
27
|
+
DISK_TEXT_ENCODING = "utf-8"
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def optional_import(module_name):
|
|
@@ -293,34 +294,36 @@ class DiskStorageBackend(StorageBackend):
|
|
|
293
294
|
return numeric_arrays[0]
|
|
294
295
|
|
|
295
296
|
def _text_writer(self, path, data, **kwargs):
|
|
296
|
-
"""Write text
|
|
297
|
-
|
|
297
|
+
"""Write UTF-8 text bytes without platform newline translation."""
|
|
298
|
+
|
|
299
|
+
del kwargs
|
|
300
|
+
path.write_bytes(str(data).encode(DISK_TEXT_ENCODING))
|
|
298
301
|
|
|
299
302
|
def _text_reader(self, path):
|
|
300
|
-
return path.read_text()
|
|
303
|
+
return path.read_text(encoding=DISK_TEXT_ENCODING)
|
|
301
304
|
|
|
302
305
|
def _json_writer(self, path, data, **kwargs):
|
|
303
306
|
import json
|
|
304
307
|
|
|
305
308
|
# Ensure parent directory exists
|
|
306
309
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
307
|
-
|
|
310
|
+
self._text_writer(path, json.dumps(data, indent=2))
|
|
308
311
|
|
|
309
312
|
def _json_reader(self, path):
|
|
310
313
|
import json
|
|
311
314
|
|
|
312
|
-
return json.loads(
|
|
315
|
+
return json.loads(self._text_reader(path))
|
|
313
316
|
|
|
314
317
|
def _csv_writer(self, path, data, **kwargs):
|
|
315
318
|
import csv
|
|
316
319
|
|
|
317
320
|
# Handle pre-formatted CSV strings (from pandas to_csv)
|
|
318
321
|
if isinstance(data, str):
|
|
319
|
-
|
|
322
|
+
self._text_writer(path, data)
|
|
320
323
|
return
|
|
321
324
|
|
|
322
325
|
# Assume data is a list of rows or a dict
|
|
323
|
-
with path.open("w", newline="") as f:
|
|
326
|
+
with path.open("w", newline="", encoding=DISK_TEXT_ENCODING) as f:
|
|
324
327
|
if isinstance(data, dict):
|
|
325
328
|
# Write dict as CSV with headers
|
|
326
329
|
writer = csv.DictWriter(f, fieldnames=data.keys())
|
|
@@ -355,7 +358,7 @@ class DiskStorageBackend(StorageBackend):
|
|
|
355
358
|
def _csv_reader(self, path):
|
|
356
359
|
import csv
|
|
357
360
|
|
|
358
|
-
with path.open("r", newline="") as f:
|
|
361
|
+
with path.open("r", newline="", encoding=DISK_TEXT_ENCODING) as f:
|
|
359
362
|
reader = csv.DictReader(f)
|
|
360
363
|
return list(reader)
|
|
361
364
|
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import logging
|
|
6
6
|
import time
|
|
7
7
|
from dataclasses import dataclass
|
|
8
|
+
from enum import Enum
|
|
8
9
|
from typing import Any
|
|
9
10
|
|
|
10
11
|
logger = logging.getLogger(__name__)
|
|
@@ -14,6 +15,20 @@ class ImageJRuntimeUnavailableError(RuntimeError):
|
|
|
14
15
|
"""Raised when an ImageJ distribution cannot use its declared Java runtime."""
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
class ImageJJvmTeardown(Enum):
|
|
19
|
+
"""JPype teardown behavior owned by an ImageJ process runtime."""
|
|
20
|
+
|
|
21
|
+
PROCESS_EXIT = False
|
|
22
|
+
DESTROY_JVM = True
|
|
23
|
+
|
|
24
|
+
def configure(self) -> None:
|
|
25
|
+
"""Apply this teardown behavior before the process starts its JVM."""
|
|
26
|
+
|
|
27
|
+
import jpype.config
|
|
28
|
+
|
|
29
|
+
jpype.config.destroy_jvm = self.value
|
|
30
|
+
|
|
31
|
+
|
|
17
32
|
@dataclass(frozen=True, slots=True)
|
|
18
33
|
class ImageJRuntimePolicy:
|
|
19
34
|
"""Own the endpoint and managed Java requirement for an ImageJ runtime."""
|
|
@@ -22,6 +37,7 @@ class ImageJRuntimePolicy:
|
|
|
22
37
|
java_fetch: str
|
|
23
38
|
java_vendor: str
|
|
24
39
|
java_version: str
|
|
40
|
+
jvm_teardown: ImageJJvmTeardown
|
|
25
41
|
initialization_retry_delays_seconds: tuple[float, ...] = ()
|
|
26
42
|
|
|
27
43
|
@property
|
|
@@ -66,6 +82,7 @@ class ImageJRuntimePolicy:
|
|
|
66
82
|
def configure_java(self, scyjava_module: Any) -> None:
|
|
67
83
|
"""Select managed Java before startup or validate the active JVM."""
|
|
68
84
|
|
|
85
|
+
self.jvm_teardown.configure()
|
|
69
86
|
if scyjava_module.jvm_started():
|
|
70
87
|
self.require_compatible_active_java(scyjava_module)
|
|
71
88
|
return
|
|
@@ -105,5 +122,6 @@ FIJI_IMAGEJ_RUNTIME = ImageJRuntimePolicy(
|
|
|
105
122
|
java_fetch="always",
|
|
106
123
|
java_vendor="zulu-jre",
|
|
107
124
|
java_version="21",
|
|
125
|
+
jvm_teardown=ImageJJvmTeardown.PROCESS_EXIT,
|
|
108
126
|
initialization_retry_delays_seconds=(1.0, 2.0),
|
|
109
127
|
)
|
|
@@ -28,7 +28,7 @@ import numpy as np
|
|
|
28
28
|
|
|
29
29
|
from .array_payload import storage_numpy_array
|
|
30
30
|
from .base import PicklableBackend, VirtualBackend
|
|
31
|
-
from .omero_tables import OMERO_TABLE_SERVICE
|
|
31
|
+
from .omero_tables import OMERO_TABLE_SERVICE, OMEROTableColumnType
|
|
32
32
|
from .omero_text import OMEROTextFormat
|
|
33
33
|
|
|
34
34
|
logger = logging.getLogger(__name__)
|
|
@@ -616,7 +616,6 @@ class OMEROLocalBackend(VirtualBackend, PicklableBackend):
|
|
|
616
616
|
from io import StringIO
|
|
617
617
|
|
|
618
618
|
import pandas as pd
|
|
619
|
-
from omero.grid import BoolColumn, DoubleColumn, LongColumn, StringColumn
|
|
620
619
|
from omero.model import FileAnnotationI
|
|
621
620
|
from omero.rtypes import rstring
|
|
622
621
|
|
|
@@ -650,30 +649,9 @@ class OMEROLocalBackend(VirtualBackend, PicklableBackend):
|
|
|
650
649
|
# OMERO table names cannot contain dots except for the .h5 extension
|
|
651
650
|
table_name = output_path.name.split(".")[0]
|
|
652
651
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
col_data = df[col_name]
|
|
657
|
-
|
|
658
|
-
# Determine column type from pandas dtype
|
|
659
|
-
if pd.api.types.is_integer_dtype(col_data):
|
|
660
|
-
col = LongColumn(col_name, "", [])
|
|
661
|
-
col.values = col_data.astype(int).tolist()
|
|
662
|
-
elif pd.api.types.is_float_dtype(col_data):
|
|
663
|
-
col = DoubleColumn(col_name, "", [])
|
|
664
|
-
col.values = col_data.astype(float).tolist()
|
|
665
|
-
elif pd.api.types.is_bool_dtype(col_data):
|
|
666
|
-
col = BoolColumn(col_name, "", [])
|
|
667
|
-
col.values = col_data.astype(bool).tolist()
|
|
668
|
-
else:
|
|
669
|
-
# Default to string column
|
|
670
|
-
# Calculate max string length (OMERO requires size > 0)
|
|
671
|
-
str_values = col_data.astype(str).tolist()
|
|
672
|
-
max_len = max(len(s) for s in str_values) if str_values else 1
|
|
673
|
-
col = StringColumn(col_name, "", max_len, [])
|
|
674
|
-
col.values = str_values
|
|
675
|
-
|
|
676
|
-
columns.append(col)
|
|
652
|
+
columns = [
|
|
653
|
+
OMEROTableColumnType.column_for(col_name, df[col_name]) for col_name in df.columns
|
|
654
|
+
]
|
|
677
655
|
|
|
678
656
|
table = OMERO_TABLE_SERVICE.create_table(conn, f"{table_name}.h5")
|
|
679
657
|
|
|
@@ -4,11 +4,107 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
import time
|
|
7
|
+
from collections.abc import Callable
|
|
7
8
|
from dataclasses import dataclass
|
|
9
|
+
from enum import Enum
|
|
8
10
|
from typing import Any
|
|
9
11
|
|
|
10
12
|
logger = logging.getLogger(__name__)
|
|
11
13
|
|
|
14
|
+
OMEROColumnMatcher = Callable[[Any], bool]
|
|
15
|
+
OMEROColumnBuilder = Callable[[str, Any], Any]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _integer_column_matches(series: Any) -> bool:
|
|
19
|
+
import pandas as pd
|
|
20
|
+
|
|
21
|
+
return bool(pd.api.types.is_integer_dtype(series))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _float_column_matches(series: Any) -> bool:
|
|
25
|
+
import pandas as pd
|
|
26
|
+
|
|
27
|
+
return bool(pd.api.types.is_float_dtype(series))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _bool_column_matches(series: Any) -> bool:
|
|
31
|
+
import pandas as pd
|
|
32
|
+
|
|
33
|
+
return bool(pd.api.types.is_bool_dtype(series))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _fallback_column_matches(series: Any) -> bool:
|
|
37
|
+
del series
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _integer_column(name: str, series: Any) -> Any:
|
|
42
|
+
from omero.grid import LongColumn
|
|
43
|
+
|
|
44
|
+
column = LongColumn(name, "", [])
|
|
45
|
+
column.values = series.astype(int).tolist()
|
|
46
|
+
return column
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _float_column(name: str, series: Any) -> Any:
|
|
50
|
+
from omero.grid import DoubleColumn
|
|
51
|
+
|
|
52
|
+
column = DoubleColumn(name, "", [])
|
|
53
|
+
column.values = series.astype(float).tolist()
|
|
54
|
+
return column
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _bool_column(name: str, series: Any) -> Any:
|
|
58
|
+
from omero.grid import BoolColumn
|
|
59
|
+
|
|
60
|
+
column = BoolColumn(name, "", [])
|
|
61
|
+
column.values = series.astype(bool).tolist()
|
|
62
|
+
return column
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _string_column(name: str, series: Any) -> Any:
|
|
66
|
+
from omero.grid import StringColumn
|
|
67
|
+
|
|
68
|
+
values = [str(value) for value in series.tolist()]
|
|
69
|
+
column = StringColumn(
|
|
70
|
+
name,
|
|
71
|
+
"",
|
|
72
|
+
max((len(value) for value in values), default=1),
|
|
73
|
+
[],
|
|
74
|
+
)
|
|
75
|
+
column.values = values
|
|
76
|
+
return column
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class OMEROTableColumnType(Enum):
|
|
80
|
+
"""One pandas dtype family's complete OMERO column projection."""
|
|
81
|
+
|
|
82
|
+
def __new__(
|
|
83
|
+
cls,
|
|
84
|
+
identity: str,
|
|
85
|
+
matches: OMEROColumnMatcher,
|
|
86
|
+
build: OMEROColumnBuilder,
|
|
87
|
+
):
|
|
88
|
+
member = object.__new__(cls)
|
|
89
|
+
member._value_ = identity
|
|
90
|
+
member._matches = matches
|
|
91
|
+
member._build = build
|
|
92
|
+
return member
|
|
93
|
+
|
|
94
|
+
INTEGER = ("integer", _integer_column_matches, _integer_column)
|
|
95
|
+
FLOAT = ("float", _float_column_matches, _float_column)
|
|
96
|
+
BOOLEAN = ("boolean", _bool_column_matches, _bool_column)
|
|
97
|
+
STRING = ("string", _fallback_column_matches, _string_column)
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def column_for(cls, name: str, series: Any) -> Any:
|
|
101
|
+
"""Build the first declared OMERO column matching one pandas series."""
|
|
102
|
+
|
|
103
|
+
return next(member for member in cls if member._matches(series))._build(
|
|
104
|
+
name,
|
|
105
|
+
series,
|
|
106
|
+
)
|
|
107
|
+
|
|
12
108
|
|
|
13
109
|
class OMEROTableServiceUnavailableError(RuntimeError):
|
|
14
110
|
"""Raised when OMERO.tables cannot satisfy a table operation."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polystore
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Framework-agnostic multi-backend storage abstraction for ML and scientific computing
|
|
5
5
|
Author-email: Tristan Simas <tristan.simas@mail.mcgill.ca>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -58,6 +58,7 @@ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
|
58
58
|
Requires-Dist: coverage>=7.3.2; extra == "dev"
|
|
59
59
|
Requires-Dist: genbadge[coverage]; extra == "dev"
|
|
60
60
|
Requires-Dist: pandas>=2.3.1; extra == "dev"
|
|
61
|
+
Requires-Dist: jpype1>=1.4.0; extra == "dev"
|
|
61
62
|
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
62
63
|
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
63
64
|
Provides-Extra: docs
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
Keep tests small and focused — these hit CSV/JSON/TEXT handlers, listing,
|
|
4
4
|
ensure_directory idempotence, and symlink creation.
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
8
9
|
import numpy as np
|
|
@@ -38,6 +39,23 @@ def test_text_json_csv_save_load(tmp_path: Path):
|
|
|
38
39
|
assert loaded[0]["a"] == "1"
|
|
39
40
|
|
|
40
41
|
|
|
42
|
+
def test_preformatted_text_is_written_as_exact_utf8_bytes(
|
|
43
|
+
tmp_path: Path,
|
|
44
|
+
monkeypatch,
|
|
45
|
+
) -> None:
|
|
46
|
+
content = "well,count\r\nA01,2\r\n"
|
|
47
|
+
output_path = tmp_path / "summary.csv"
|
|
48
|
+
|
|
49
|
+
def reject_platform_text_translation(*args, **kwargs):
|
|
50
|
+
del args, kwargs
|
|
51
|
+
raise AssertionError("text persistence must bypass platform newline translation")
|
|
52
|
+
|
|
53
|
+
monkeypatch.setattr(Path, "write_text", reject_platform_text_translation)
|
|
54
|
+
DiskBackend().save(content, output_path)
|
|
55
|
+
|
|
56
|
+
assert output_path.read_bytes() == content.encode(disk_module.DISK_TEXT_ENCODING)
|
|
57
|
+
|
|
58
|
+
|
|
41
59
|
def test_tiff_save_normalizes_external_array_payload(tmp_path, monkeypatch) -> None:
|
|
42
60
|
class ExternalArray:
|
|
43
61
|
pass
|
|
@@ -6,6 +6,7 @@ import pytest
|
|
|
6
6
|
|
|
7
7
|
from polystore.imagej_runtime import (
|
|
8
8
|
FIJI_IMAGEJ_RUNTIME,
|
|
9
|
+
ImageJJvmTeardown,
|
|
9
10
|
ImageJRuntimePolicy,
|
|
10
11
|
ImageJRuntimeUnavailableError,
|
|
11
12
|
)
|
|
@@ -49,7 +50,10 @@ class _ImageJ:
|
|
|
49
50
|
return self.gateway
|
|
50
51
|
|
|
51
52
|
|
|
52
|
-
def test_fiji_runtime_selects_managed_java_before_initialization() -> None:
|
|
53
|
+
def test_fiji_runtime_selects_managed_java_before_initialization(monkeypatch) -> None:
|
|
54
|
+
import jpype.config
|
|
55
|
+
|
|
56
|
+
monkeypatch.setattr(jpype.config, "destroy_jvm", True)
|
|
53
57
|
scyjava = _ScyJava(started=False, java_version="21.0.8")
|
|
54
58
|
imagej = _ImageJ(scyjava)
|
|
55
59
|
|
|
@@ -58,6 +62,7 @@ def test_fiji_runtime_selects_managed_java_before_initialization() -> None:
|
|
|
58
62
|
assert gateway is imagej.gateway
|
|
59
63
|
assert scyjava.constraints == [{"fetch": "always", "vendor": "zulu-jre", "version": "21"}]
|
|
60
64
|
assert imagej.calls == [("sc.fiji:fiji", "headless")]
|
|
65
|
+
assert jpype.config.destroy_jvm is False
|
|
61
66
|
|
|
62
67
|
|
|
63
68
|
def test_runtime_accepts_compatible_active_java_without_reconfiguration() -> None:
|
|
@@ -90,6 +95,7 @@ def test_runtime_reports_an_unparseable_active_java_version() -> None:
|
|
|
90
95
|
java_fetch="always",
|
|
91
96
|
java_vendor="example-jre",
|
|
92
97
|
java_version="21",
|
|
98
|
+
jvm_teardown=ImageJJvmTeardown.PROCESS_EXIT,
|
|
93
99
|
)
|
|
94
100
|
scyjava = _ScyJava(started=True, java_version="unknown")
|
|
95
101
|
|
|
@@ -132,6 +138,7 @@ def test_runtime_retries_initialization_only_before_jvm_start(monkeypatch) -> No
|
|
|
132
138
|
java_fetch="always",
|
|
133
139
|
java_vendor="example-jre",
|
|
134
140
|
java_version="21",
|
|
141
|
+
jvm_teardown=ImageJJvmTeardown.PROCESS_EXIT,
|
|
135
142
|
initialization_retry_delays_seconds=(0.1, 0.2),
|
|
136
143
|
)
|
|
137
144
|
scyjava = _ScyJava(started=False, java_version="21")
|
|
@@ -162,6 +169,7 @@ def test_runtime_preserves_final_failure_after_retry_schedule() -> None:
|
|
|
162
169
|
java_fetch="always",
|
|
163
170
|
java_vendor="example-jre",
|
|
164
171
|
java_version="21",
|
|
172
|
+
jvm_teardown=ImageJJvmTeardown.PROCESS_EXIT,
|
|
165
173
|
initialization_retry_delays_seconds=(0.0, 0.0),
|
|
166
174
|
)
|
|
167
175
|
scyjava = _ScyJava(started=False, java_version="21")
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"""Tests for the declaration-owned OMERO.tables capability boundary."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import sys
|
|
4
|
+
from types import ModuleType, SimpleNamespace
|
|
4
5
|
|
|
6
|
+
import pandas as pd
|
|
5
7
|
import pytest
|
|
6
8
|
|
|
7
9
|
from polystore.omero_tables import (
|
|
10
|
+
OMEROTableColumnType,
|
|
8
11
|
OMEROTableService,
|
|
9
12
|
OMEROTableServiceUnavailableError,
|
|
10
13
|
)
|
|
@@ -12,6 +15,64 @@ from polystore.omero_tables import (
|
|
|
12
15
|
_DEFAULT_TABLE = object()
|
|
13
16
|
|
|
14
17
|
|
|
18
|
+
class _Column:
|
|
19
|
+
def __init__(self, *declaration) -> None:
|
|
20
|
+
self.declaration = declaration
|
|
21
|
+
self.values = []
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _LongColumn(_Column):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _DoubleColumn(_Column):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _BoolColumn(_Column):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class _StringColumn(_Column):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.fixture
|
|
41
|
+
def omero_grid_columns(monkeypatch):
|
|
42
|
+
grid = ModuleType("omero.grid")
|
|
43
|
+
grid.LongColumn = _LongColumn
|
|
44
|
+
grid.DoubleColumn = _DoubleColumn
|
|
45
|
+
grid.BoolColumn = _BoolColumn
|
|
46
|
+
grid.StringColumn = _StringColumn
|
|
47
|
+
omero = ModuleType("omero")
|
|
48
|
+
omero.__path__ = []
|
|
49
|
+
monkeypatch.setitem(sys.modules, "omero", omero)
|
|
50
|
+
monkeypatch.setitem(sys.modules, "omero.grid", grid)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.parametrize(
|
|
54
|
+
("series", "column_type", "values"),
|
|
55
|
+
(
|
|
56
|
+
(pd.Series([1, 2]), _LongColumn, [1, 2]),
|
|
57
|
+
(pd.Series([1.5, 2.5]), _DoubleColumn, [1.5, 2.5]),
|
|
58
|
+
(pd.Series([True, False]), _BoolColumn, [True, False]),
|
|
59
|
+
(pd.Series(["A01", float("nan")], dtype=object), _StringColumn, ["A01", "nan"]),
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
def test_table_column_type_owns_dtype_projection(
|
|
63
|
+
omero_grid_columns,
|
|
64
|
+
series,
|
|
65
|
+
column_type,
|
|
66
|
+
values,
|
|
67
|
+
) -> None:
|
|
68
|
+
column = OMEROTableColumnType.column_for("value", series)
|
|
69
|
+
|
|
70
|
+
assert isinstance(column, column_type)
|
|
71
|
+
assert column.values == values
|
|
72
|
+
if isinstance(column, _StringColumn):
|
|
73
|
+
assert column.declaration == ("value", "", 3, [])
|
|
74
|
+
|
|
75
|
+
|
|
15
76
|
class _Resources:
|
|
16
77
|
def __init__(
|
|
17
78
|
self,
|
|
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.0 → polystore-0.2.2}/src/polystore/streaming/receivers/core/window_projection.py
RENAMED
|
File without changes
|
|
File without changes
|
{polystore-0.2.0 → polystore-0.2.2}/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
|