mlarray 0.0.47__tar.gz → 0.0.49__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.
- {mlarray-0.0.47 → mlarray-0.0.49}/PKG-INFO +1 -1
- mlarray-0.0.49/examples/example_compressed_vs_uncompressed.py +42 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/mlarray/meta.py +66 -1
- {mlarray-0.0.47 → mlarray-0.0.49}/mlarray/mlarray.py +303 -73
- {mlarray-0.0.47 → mlarray-0.0.49}/mlarray.egg-info/PKG-INFO +1 -1
- {mlarray-0.0.47 → mlarray-0.0.49}/mlarray.egg-info/SOURCES.txt +1 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/tests/test_asarray.py +16 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/tests/test_usage.py +48 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/.github/workflows/workflow.yml +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/.gitignore +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/LICENSE +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/MANIFEST.in +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/README.md +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/assets/banner.png +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/assets/banner.png~ +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/docs/api.md +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/docs/cli.md +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/docs/index.md +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/docs/optimization.md +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/docs/schema.md +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/docs/usage.md +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/docs/why.md +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/examples/example_asarray.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/examples/example_bboxes_only.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/examples/example_channel.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/examples/example_in_memory_constructors.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/examples/example_metadata_only.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/examples/example_non_spatial.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/examples/example_open.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/examples/example_save_load.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/mkdocs.yml +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/mlarray/__init__.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/mlarray/cli.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/mlarray/utils.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/mlarray.egg-info/dependency_links.txt +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/mlarray.egg-info/entry_points.txt +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/mlarray.egg-info/requires.txt +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/mlarray.egg-info/top_level.txt +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/pyproject.toml +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/setup.cfg +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/tests/test_bboxes.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/tests/test_constructors.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/tests/test_create.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/tests/test_metadata.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/tests/test_open.py +0 -0
- {mlarray-0.0.47 → mlarray-0.0.49}/tests/test_optimization.py +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from mlarray import MLArray
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
array = np.arange(2 * 4 * 4, dtype=np.float32).reshape(2, 4, 4)
|
|
10
|
+
|
|
11
|
+
compressed_img = MLArray.asarray(
|
|
12
|
+
array,
|
|
13
|
+
compressed=True,
|
|
14
|
+
patch_size=None,
|
|
15
|
+
chunk_size=(1, 4, 4),
|
|
16
|
+
block_size=(1, 2, 2),
|
|
17
|
+
)
|
|
18
|
+
uncompressed_img = MLArray.asarray(array, compressed=False)
|
|
19
|
+
|
|
20
|
+
compressed_path = Path("example_compressed_output.mla")
|
|
21
|
+
uncompressed_path = Path("example_uncompressed_output.mla")
|
|
22
|
+
|
|
23
|
+
print("compressed in-memory backend (before save):", type(compressed_img._store))
|
|
24
|
+
print(
|
|
25
|
+
"uncompressed in-memory backend (before save):",
|
|
26
|
+
type(uncompressed_img._store),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
compressed_img.save(compressed_path)
|
|
30
|
+
uncompressed_img.save(uncompressed_path)
|
|
31
|
+
|
|
32
|
+
print("compressed in-memory backend (after save):", type(compressed_img._store))
|
|
33
|
+
print(
|
|
34
|
+
"uncompressed in-memory backend (after save):",
|
|
35
|
+
type(uncompressed_img._store),
|
|
36
|
+
)
|
|
37
|
+
print("saved compressed file:", compressed_path)
|
|
38
|
+
print("saved uncompressed->compressed file:", uncompressed_path)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
main()
|
|
@@ -393,6 +393,44 @@ def _cast_to_list(value: Any, label: str):
|
|
|
393
393
|
return out
|
|
394
394
|
|
|
395
395
|
|
|
396
|
+
def _to_jsonable(value: Any) -> Any:
|
|
397
|
+
"""Recursively convert values to JSON-serializable plain Python objects."""
|
|
398
|
+
if isinstance(value, Enum):
|
|
399
|
+
return value.value
|
|
400
|
+
|
|
401
|
+
if isinstance(value, Mapping):
|
|
402
|
+
return {str(k): _to_jsonable(v) for k, v in value.items()}
|
|
403
|
+
|
|
404
|
+
if isinstance(value, (list, tuple)):
|
|
405
|
+
return [_to_jsonable(v) for v in value]
|
|
406
|
+
|
|
407
|
+
if isinstance(value, np.generic):
|
|
408
|
+
return value.item()
|
|
409
|
+
|
|
410
|
+
return value
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _cast_to_jsonable_mapping(value: Any, label: str) -> dict[str, Any]:
|
|
414
|
+
"""Cast a value to a JSON-serializable mapping.
|
|
415
|
+
|
|
416
|
+
Accepts mappings directly or objects exposing ``__dict__`` (for example
|
|
417
|
+
Blosc2 ``CParams`` / ``DParams`` objects).
|
|
418
|
+
"""
|
|
419
|
+
if isinstance(value, Mapping):
|
|
420
|
+
out = dict(value)
|
|
421
|
+
elif hasattr(value, "__dict__"):
|
|
422
|
+
out = dict(vars(value))
|
|
423
|
+
else:
|
|
424
|
+
raise TypeError(f"{label} must be a mapping or object with __dict__")
|
|
425
|
+
|
|
426
|
+
out = _to_jsonable(out)
|
|
427
|
+
if not isinstance(out, dict):
|
|
428
|
+
raise TypeError(f"{label} could not be converted to a mapping")
|
|
429
|
+
if not is_serializable(out):
|
|
430
|
+
raise TypeError(f"{label} is not JSON-serializable")
|
|
431
|
+
return out
|
|
432
|
+
|
|
433
|
+
|
|
396
434
|
def _validate_int(value: Any, label: str) -> None:
|
|
397
435
|
"""Validate that value is an int.
|
|
398
436
|
|
|
@@ -565,10 +603,14 @@ class MetaBlosc2(BaseMeta):
|
|
|
565
603
|
chunk_size: List of per-dimension chunk sizes. Length must match ndims.
|
|
566
604
|
block_size: List of per-dimension block sizes. Length must match ndims.
|
|
567
605
|
patch_size: List of per-dimension patch sizes. Length must match spatial ndims.
|
|
606
|
+
cparams: Blosc2 compression parameters as a JSON-serializable dict.
|
|
607
|
+
dparams: Blosc2 decompression parameters as a JSON-serializable dict.
|
|
568
608
|
"""
|
|
569
609
|
chunk_size: Optional[list] = None
|
|
570
610
|
block_size: Optional[list] = None
|
|
571
611
|
patch_size: Optional[list] = None
|
|
612
|
+
cparams: Optional[dict[str, Any]] = None
|
|
613
|
+
dparams: Optional[dict[str, Any]] = None
|
|
572
614
|
|
|
573
615
|
def _validate_and_cast(self, *, ndims: Optional[int] = None, spatial_ndims: Optional[int] = None, **_: Any) -> None:
|
|
574
616
|
"""Validate and normalize tiling sizes.
|
|
@@ -591,6 +633,12 @@ class MetaBlosc2(BaseMeta):
|
|
|
591
633
|
self.patch_size = _cast_to_list(self.patch_size, "meta.blosc2.patch_size")
|
|
592
634
|
_validate_float_int_list(self.patch_size, "meta.blosc2.patch_size", spatial_ndims)
|
|
593
635
|
|
|
636
|
+
if self.cparams is not None:
|
|
637
|
+
self.cparams = _cast_to_jsonable_mapping(self.cparams, "meta.blosc2.cparams")
|
|
638
|
+
|
|
639
|
+
if self.dparams is not None:
|
|
640
|
+
self.dparams = _cast_to_jsonable_mapping(self.dparams, "meta.blosc2.dparams")
|
|
641
|
+
|
|
594
642
|
|
|
595
643
|
class AxisLabelEnum(str, Enum):
|
|
596
644
|
"""Axis label/role identifiers used for spatial metadata.
|
|
@@ -628,6 +676,7 @@ class MetaSpatial(BaseMeta):
|
|
|
628
676
|
spacing: Per-dimension spacing values. Length must match ndims.
|
|
629
677
|
origin: Per-dimension origin values. Length must match ndims.
|
|
630
678
|
direction: Direction cosine matrix of shape [ndims, ndims].
|
|
679
|
+
affine: Homogeneous affine matrix of shape [ndims + 1, ndims + 1].
|
|
631
680
|
shape: Array shape. Length must match (spatial + non-spatial) ndims.
|
|
632
681
|
axis_labels: Per-axis labels or roles. Length must match ndims.
|
|
633
682
|
axis_units: Per-axis units. Length must match ndims.
|
|
@@ -638,6 +687,7 @@ class MetaSpatial(BaseMeta):
|
|
|
638
687
|
spacing: Optional[list[Union[int,float]]] = None
|
|
639
688
|
origin: Optional[list[Union[int,float]]] = None
|
|
640
689
|
direction: Optional[list[list[Union[int,float]]]] = None
|
|
690
|
+
affine: Optional[list[list[Union[int,float]]]] = None
|
|
641
691
|
shape: Optional[list[int]] = None
|
|
642
692
|
axis_labels: Optional[list[Union[str,AxisLabel]]] = None
|
|
643
693
|
axis_units: Optional[list[str]] = None
|
|
@@ -668,6 +718,21 @@ class MetaSpatial(BaseMeta):
|
|
|
668
718
|
self.direction = _cast_to_list(self.direction, "meta.spatial.direction")
|
|
669
719
|
_validate_float_int_matrix(self.direction, "meta.spatial.direction", spatial_ndims)
|
|
670
720
|
|
|
721
|
+
if self.affine is not None:
|
|
722
|
+
self.affine = _cast_to_list(self.affine, "meta.spatial.affine")
|
|
723
|
+
if spatial_ndims is not None:
|
|
724
|
+
_validate_float_int_matrix(
|
|
725
|
+
self.affine,
|
|
726
|
+
"meta.spatial.affine",
|
|
727
|
+
spatial_ndims + 1,
|
|
728
|
+
)
|
|
729
|
+
else:
|
|
730
|
+
_validate_float_int_matrix(self.affine, "meta.spatial.affine")
|
|
731
|
+
n_rows = len(self.affine)
|
|
732
|
+
for row in self.affine:
|
|
733
|
+
if len(row) != n_rows:
|
|
734
|
+
raise ValueError("meta.spatial.affine must be a square matrix")
|
|
735
|
+
|
|
671
736
|
if self.shape is not None:
|
|
672
737
|
self.shape = _cast_to_list(self.shape, "meta.spatial.shape")
|
|
673
738
|
_validate_float_int_list(self.shape, "meta.spatial.shape", ndims)
|
|
@@ -867,7 +932,7 @@ class Meta(BaseMeta):
|
|
|
867
932
|
Attributes:
|
|
868
933
|
source: Source metadata from the original image source (JSON-serializable dict).
|
|
869
934
|
extra: Additional metadata (JSON-serializable dict).
|
|
870
|
-
spatial: Spatial metadata (spacing, origin, direction, shape).
|
|
935
|
+
spatial: Spatial metadata (spacing, origin, direction, affine, shape).
|
|
871
936
|
stats: Summary statistics.
|
|
872
937
|
bbox: Bounding boxes.
|
|
873
938
|
is_seg: Segmentation flag.
|
|
@@ -2,11 +2,13 @@ from copy import deepcopy
|
|
|
2
2
|
import numpy as np
|
|
3
3
|
import blosc2
|
|
4
4
|
import math
|
|
5
|
-
from typing import Dict, Optional, Union, List, Tuple
|
|
5
|
+
from typing import Any, Dict, Optional, Union, List, Tuple
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
import os
|
|
8
8
|
from mlarray.meta import Meta, MetaBlosc2, AxisLabel, _spatial_axis_mask
|
|
9
9
|
from mlarray.utils import is_serializable
|
|
10
|
+
import pickle
|
|
11
|
+
import gzip
|
|
10
12
|
|
|
11
13
|
MLARRAY_SUFFIX = "mla"
|
|
12
14
|
MLARRAY_VERSION = "v0"
|
|
@@ -20,6 +22,7 @@ class MLArray:
|
|
|
20
22
|
spacing: Optional[Union[List, Tuple, np.ndarray]] = None,
|
|
21
23
|
origin: Optional[Union[List, Tuple, np.ndarray]] = None,
|
|
22
24
|
direction: Optional[Union[List, Tuple, np.ndarray]] = None,
|
|
25
|
+
affine: Optional[Union[List, Tuple, np.ndarray]] = None,
|
|
23
26
|
meta: Optional[Union[Dict, Meta]] = None,
|
|
24
27
|
axis_labels: Optional[List[Union[str, AxisLabel]]] = None,
|
|
25
28
|
copy: Optional['MLArray'] = None,
|
|
@@ -28,6 +31,7 @@ class MLArray:
|
|
|
28
31
|
block_size: Optional[Union[int, List, Tuple]] = None,
|
|
29
32
|
cparams: Optional[Union[Dict, blosc2.CParams]] = None,
|
|
30
33
|
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
34
|
+
compressed: bool = True,
|
|
31
35
|
) -> None:
|
|
32
36
|
"""Initializes a MLArray instance.
|
|
33
37
|
|
|
@@ -48,6 +52,9 @@ class MLArray:
|
|
|
48
52
|
direction (Optional[Union[List, Tuple, np.ndarray]]): Direction
|
|
49
53
|
cosine matrix. Provide a 2D list/tuple/ndarray with shape
|
|
50
54
|
(ndims, ndims) for spatial dimensions.
|
|
55
|
+
affine (Optional[Union[List, Tuple, np.ndarray]]): Homogeneous
|
|
56
|
+
affine matrix. Provide a 2D list/tuple/ndarray with shape
|
|
57
|
+
(spatial_ndims + 1, spatial_ndims + 1).
|
|
51
58
|
meta (Optional[Dict | Meta]): Free-form metadata dictionary or Meta
|
|
52
59
|
instance. Must be JSON-serializable when saving.
|
|
53
60
|
If meta is passed as a Dict, it is internally converted into a
|
|
@@ -75,10 +82,12 @@ class MLArray:
|
|
|
75
82
|
self.mmap_mode = None
|
|
76
83
|
self.meta = None
|
|
77
84
|
self._store = None
|
|
85
|
+
self._backend = None
|
|
78
86
|
if isinstance(array, (str, Path)) and (
|
|
79
87
|
spacing is not None
|
|
80
88
|
or origin is not None
|
|
81
89
|
or direction is not None
|
|
90
|
+
or affine is not None
|
|
82
91
|
or meta is not None
|
|
83
92
|
or axis_labels is not None
|
|
84
93
|
or copy is not None
|
|
@@ -89,14 +98,23 @@ class MLArray:
|
|
|
89
98
|
or dparams is not None
|
|
90
99
|
):
|
|
91
100
|
raise RuntimeError(
|
|
92
|
-
"Spacing, origin, direction, meta, axis_labels, copy, patch_size, "
|
|
101
|
+
"Spacing, origin, direction, affine, meta, axis_labels, copy, patch_size, "
|
|
93
102
|
"chunk_size, block_size, cparams or dparams cannot be set when "
|
|
94
103
|
"array is a filepath."
|
|
95
104
|
)
|
|
96
105
|
if isinstance(array, (str, Path)):
|
|
97
|
-
self._load(array)
|
|
106
|
+
self._load(array, compressed=compressed)
|
|
98
107
|
else:
|
|
99
|
-
self._validate_and_add_meta(
|
|
108
|
+
self._validate_and_add_meta(
|
|
109
|
+
meta,
|
|
110
|
+
spacing=spacing,
|
|
111
|
+
origin=origin,
|
|
112
|
+
direction=direction,
|
|
113
|
+
affine=affine,
|
|
114
|
+
axis_labels=axis_labels,
|
|
115
|
+
has_array=False,
|
|
116
|
+
validate=False,
|
|
117
|
+
)
|
|
100
118
|
if array is not None:
|
|
101
119
|
self._asarray(
|
|
102
120
|
array,
|
|
@@ -106,10 +124,16 @@ class MLArray:
|
|
|
106
124
|
block_size=block_size,
|
|
107
125
|
cparams=cparams,
|
|
108
126
|
dparams=dparams,
|
|
127
|
+
compressed=compressed,
|
|
109
128
|
)
|
|
110
129
|
has_array = True
|
|
111
130
|
else:
|
|
112
|
-
|
|
131
|
+
if compressed:
|
|
132
|
+
self._store = blosc2.empty((0,))
|
|
133
|
+
self._backend = "blosc2"
|
|
134
|
+
else:
|
|
135
|
+
self._store = np.empty((0,))
|
|
136
|
+
self._backend = "numpy"
|
|
113
137
|
has_array = False
|
|
114
138
|
if copy is not None:
|
|
115
139
|
self.meta.copy_from(copy.meta)
|
|
@@ -224,6 +248,7 @@ class MLArray:
|
|
|
224
248
|
cls,
|
|
225
249
|
filepath: Union[str, Path],
|
|
226
250
|
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
251
|
+
compressed: bool = True,
|
|
227
252
|
):
|
|
228
253
|
"""Loads a MLArray file as a whole. Does not use memory-mapping. Both MLArray ('.mla') and Blosc2 ('.b2nd') files are supported.
|
|
229
254
|
|
|
@@ -243,7 +268,7 @@ class MLArray:
|
|
|
243
268
|
RuntimeError: If the file extension is not ".b2nd" or ".mla".
|
|
244
269
|
"""
|
|
245
270
|
class_instance = cls()
|
|
246
|
-
class_instance._load(filepath, dparams)
|
|
271
|
+
class_instance._load(filepath, dparams, compressed=compressed)
|
|
247
272
|
return class_instance
|
|
248
273
|
|
|
249
274
|
@classmethod
|
|
@@ -257,6 +282,7 @@ class MLArray:
|
|
|
257
282
|
block_size: Optional[Union[int, List, Tuple]] = None,
|
|
258
283
|
cparams: Optional[Union[Dict, blosc2.CParams]] = None,
|
|
259
284
|
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
285
|
+
compressed: bool = True,
|
|
260
286
|
):
|
|
261
287
|
"""Create an in-memory MLArray with uninitialized values.
|
|
262
288
|
|
|
@@ -284,6 +310,7 @@ class MLArray:
|
|
|
284
310
|
"""
|
|
285
311
|
class_instance = cls()
|
|
286
312
|
class_instance._construct_in_memory(
|
|
313
|
+
constructor="empty",
|
|
287
314
|
shape=shape,
|
|
288
315
|
dtype=dtype,
|
|
289
316
|
meta=meta,
|
|
@@ -292,7 +319,7 @@ class MLArray:
|
|
|
292
319
|
block_size=block_size,
|
|
293
320
|
cparams=cparams,
|
|
294
321
|
dparams=dparams,
|
|
295
|
-
|
|
322
|
+
compressed=compressed,
|
|
296
323
|
)
|
|
297
324
|
return class_instance
|
|
298
325
|
|
|
@@ -307,6 +334,7 @@ class MLArray:
|
|
|
307
334
|
block_size: Optional[Union[int, List, Tuple]] = None,
|
|
308
335
|
cparams: Optional[Union[Dict, blosc2.CParams]] = None,
|
|
309
336
|
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
337
|
+
compressed: bool = True,
|
|
310
338
|
):
|
|
311
339
|
"""Create an in-memory MLArray filled with zeros.
|
|
312
340
|
|
|
@@ -332,6 +360,7 @@ class MLArray:
|
|
|
332
360
|
"""
|
|
333
361
|
class_instance = cls()
|
|
334
362
|
class_instance._construct_in_memory(
|
|
363
|
+
constructor="zeros",
|
|
335
364
|
shape=shape,
|
|
336
365
|
dtype=dtype,
|
|
337
366
|
meta=meta,
|
|
@@ -340,7 +369,7 @@ class MLArray:
|
|
|
340
369
|
block_size=block_size,
|
|
341
370
|
cparams=cparams,
|
|
342
371
|
dparams=dparams,
|
|
343
|
-
|
|
372
|
+
compressed=compressed,
|
|
344
373
|
)
|
|
345
374
|
return class_instance
|
|
346
375
|
|
|
@@ -355,6 +384,7 @@ class MLArray:
|
|
|
355
384
|
block_size: Optional[Union[int, List, Tuple]] = None,
|
|
356
385
|
cparams: Optional[Union[Dict, blosc2.CParams]] = None,
|
|
357
386
|
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
387
|
+
compressed: bool = True,
|
|
358
388
|
):
|
|
359
389
|
"""Create an in-memory MLArray filled with ones.
|
|
360
390
|
|
|
@@ -382,6 +412,7 @@ class MLArray:
|
|
|
382
412
|
dtype = blosc2.DEFAULT_FLOAT if dtype is None else dtype
|
|
383
413
|
class_instance = cls()
|
|
384
414
|
class_instance._construct_in_memory(
|
|
415
|
+
constructor="ones",
|
|
385
416
|
shape=shape,
|
|
386
417
|
dtype=dtype,
|
|
387
418
|
meta=meta,
|
|
@@ -390,7 +421,7 @@ class MLArray:
|
|
|
390
421
|
block_size=block_size,
|
|
391
422
|
cparams=cparams,
|
|
392
423
|
dparams=dparams,
|
|
393
|
-
|
|
424
|
+
compressed=compressed,
|
|
394
425
|
)
|
|
395
426
|
return class_instance
|
|
396
427
|
|
|
@@ -406,6 +437,7 @@ class MLArray:
|
|
|
406
437
|
block_size: Optional[Union[int, List, Tuple]] = None,
|
|
407
438
|
cparams: Optional[Union[Dict, blosc2.CParams]] = None,
|
|
408
439
|
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
440
|
+
compressed: bool = True,
|
|
409
441
|
):
|
|
410
442
|
"""Create an in-memory MLArray filled with ``fill_value``.
|
|
411
443
|
|
|
@@ -439,6 +471,7 @@ class MLArray:
|
|
|
439
471
|
dtype = np.dtype(type(fill_value))
|
|
440
472
|
class_instance = cls()
|
|
441
473
|
class_instance._construct_in_memory(
|
|
474
|
+
constructor="full",
|
|
442
475
|
shape=shape,
|
|
443
476
|
dtype=dtype,
|
|
444
477
|
meta=meta,
|
|
@@ -447,7 +480,8 @@ class MLArray:
|
|
|
447
480
|
block_size=block_size,
|
|
448
481
|
cparams=cparams,
|
|
449
482
|
dparams=dparams,
|
|
450
|
-
|
|
483
|
+
compressed=compressed,
|
|
484
|
+
constructor_kwargs={"fill_value": fill_value},
|
|
451
485
|
)
|
|
452
486
|
return class_instance
|
|
453
487
|
|
|
@@ -466,6 +500,7 @@ class MLArray:
|
|
|
466
500
|
block_size: Optional[Union[int, List, Tuple]] = None,
|
|
467
501
|
cparams: Optional[Union[Dict, blosc2.CParams]] = None,
|
|
468
502
|
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
503
|
+
compressed: bool = True,
|
|
469
504
|
):
|
|
470
505
|
"""Create an in-memory MLArray with evenly spaced values.
|
|
471
506
|
|
|
@@ -525,6 +560,7 @@ class MLArray:
|
|
|
525
560
|
|
|
526
561
|
class_instance = cls()
|
|
527
562
|
class_instance._construct_in_memory(
|
|
563
|
+
constructor="arange",
|
|
528
564
|
shape=shape,
|
|
529
565
|
dtype=dtype,
|
|
530
566
|
meta=meta,
|
|
@@ -533,13 +569,13 @@ class MLArray:
|
|
|
533
569
|
block_size=block_size,
|
|
534
570
|
cparams=cparams,
|
|
535
571
|
dparams=dparams,
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
572
|
+
compressed=compressed,
|
|
573
|
+
constructor_kwargs={
|
|
574
|
+
"start": start,
|
|
575
|
+
"stop": stop,
|
|
576
|
+
"step": step,
|
|
577
|
+
"c_order": c_order,
|
|
578
|
+
},
|
|
543
579
|
)
|
|
544
580
|
return class_instance
|
|
545
581
|
|
|
@@ -559,6 +595,7 @@ class MLArray:
|
|
|
559
595
|
block_size: Optional[Union[int, List, Tuple]] = None,
|
|
560
596
|
cparams: Optional[Union[Dict, blosc2.CParams]] = None,
|
|
561
597
|
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
598
|
+
compressed: bool = True,
|
|
562
599
|
):
|
|
563
600
|
"""Create an in-memory MLArray with evenly spaced samples.
|
|
564
601
|
|
|
@@ -617,6 +654,7 @@ class MLArray:
|
|
|
617
654
|
|
|
618
655
|
class_instance = cls()
|
|
619
656
|
class_instance._construct_in_memory(
|
|
657
|
+
constructor="linspace",
|
|
620
658
|
shape=shape,
|
|
621
659
|
dtype=dtype,
|
|
622
660
|
meta=meta,
|
|
@@ -625,14 +663,14 @@ class MLArray:
|
|
|
625
663
|
block_size=block_size,
|
|
626
664
|
cparams=cparams,
|
|
627
665
|
dparams=dparams,
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
666
|
+
compressed=compressed,
|
|
667
|
+
constructor_kwargs={
|
|
668
|
+
"start": start,
|
|
669
|
+
"stop": stop,
|
|
670
|
+
"num": num,
|
|
671
|
+
"endpoint": endpoint,
|
|
672
|
+
"c_order": c_order,
|
|
673
|
+
},
|
|
636
674
|
)
|
|
637
675
|
return class_instance
|
|
638
676
|
|
|
@@ -645,7 +683,8 @@ class MLArray:
|
|
|
645
683
|
chunk_size: Optional[Union[int, List, Tuple]]= None,
|
|
646
684
|
block_size: Optional[Union[int, List, Tuple]] = None,
|
|
647
685
|
cparams: Optional[Union[Dict, blosc2.CParams]] = None,
|
|
648
|
-
dparams: Optional[Union[Dict, blosc2.DParams]] = None
|
|
686
|
+
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
687
|
+
compressed: bool = True,
|
|
649
688
|
):
|
|
650
689
|
"""Convert a NumPy array into an in-memory Blosc2-backed MLArray.
|
|
651
690
|
|
|
@@ -688,7 +727,16 @@ class MLArray:
|
|
|
688
727
|
implemented for the provided dimensionality.
|
|
689
728
|
"""
|
|
690
729
|
class_instance = cls()
|
|
691
|
-
class_instance._asarray(
|
|
730
|
+
class_instance._asarray(
|
|
731
|
+
array,
|
|
732
|
+
meta,
|
|
733
|
+
patch_size,
|
|
734
|
+
chunk_size,
|
|
735
|
+
block_size,
|
|
736
|
+
cparams,
|
|
737
|
+
dparams,
|
|
738
|
+
compressed=compressed,
|
|
739
|
+
)
|
|
692
740
|
return class_instance
|
|
693
741
|
|
|
694
742
|
@classmethod
|
|
@@ -702,6 +750,7 @@ class MLArray:
|
|
|
702
750
|
block_size: Optional[Union[int, List, Tuple]] = None,
|
|
703
751
|
cparams: Optional[Union[Dict, blosc2.CParams]] = None,
|
|
704
752
|
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
753
|
+
compressed: bool = True,
|
|
705
754
|
):
|
|
706
755
|
"""Create an in-memory MLArray with the same shape as ``x``.
|
|
707
756
|
|
|
@@ -730,6 +779,7 @@ class MLArray:
|
|
|
730
779
|
class_instance = cls()
|
|
731
780
|
shape, dtype, meta = class_instance._resolve_like_input(x, dtype, meta)
|
|
732
781
|
class_instance._construct_in_memory(
|
|
782
|
+
constructor="empty",
|
|
733
783
|
shape=shape,
|
|
734
784
|
dtype=dtype,
|
|
735
785
|
meta=meta,
|
|
@@ -738,7 +788,7 @@ class MLArray:
|
|
|
738
788
|
block_size=block_size,
|
|
739
789
|
cparams=cparams,
|
|
740
790
|
dparams=dparams,
|
|
741
|
-
|
|
791
|
+
compressed=compressed,
|
|
742
792
|
)
|
|
743
793
|
return class_instance
|
|
744
794
|
|
|
@@ -753,6 +803,7 @@ class MLArray:
|
|
|
753
803
|
block_size: Optional[Union[int, List, Tuple]] = None,
|
|
754
804
|
cparams: Optional[Union[Dict, blosc2.CParams]] = None,
|
|
755
805
|
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
806
|
+
compressed: bool = True,
|
|
756
807
|
):
|
|
757
808
|
"""Create an in-memory MLArray of zeros with the same shape as ``x``.
|
|
758
809
|
|
|
@@ -781,6 +832,7 @@ class MLArray:
|
|
|
781
832
|
class_instance = cls()
|
|
782
833
|
shape, dtype, meta = class_instance._resolve_like_input(x, dtype, meta)
|
|
783
834
|
class_instance._construct_in_memory(
|
|
835
|
+
constructor="zeros",
|
|
784
836
|
shape=shape,
|
|
785
837
|
dtype=dtype,
|
|
786
838
|
meta=meta,
|
|
@@ -789,7 +841,7 @@ class MLArray:
|
|
|
789
841
|
block_size=block_size,
|
|
790
842
|
cparams=cparams,
|
|
791
843
|
dparams=dparams,
|
|
792
|
-
|
|
844
|
+
compressed=compressed,
|
|
793
845
|
)
|
|
794
846
|
return class_instance
|
|
795
847
|
|
|
@@ -804,6 +856,7 @@ class MLArray:
|
|
|
804
856
|
block_size: Optional[Union[int, List, Tuple]] = None,
|
|
805
857
|
cparams: Optional[Union[Dict, blosc2.CParams]] = None,
|
|
806
858
|
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
859
|
+
compressed: bool = True,
|
|
807
860
|
):
|
|
808
861
|
"""Create an in-memory MLArray of ones with the same shape as ``x``.
|
|
809
862
|
|
|
@@ -832,6 +885,7 @@ class MLArray:
|
|
|
832
885
|
class_instance = cls()
|
|
833
886
|
shape, dtype, meta = class_instance._resolve_like_input(x, dtype, meta)
|
|
834
887
|
class_instance._construct_in_memory(
|
|
888
|
+
constructor="ones",
|
|
835
889
|
shape=shape,
|
|
836
890
|
dtype=dtype,
|
|
837
891
|
meta=meta,
|
|
@@ -840,7 +894,7 @@ class MLArray:
|
|
|
840
894
|
block_size=block_size,
|
|
841
895
|
cparams=cparams,
|
|
842
896
|
dparams=dparams,
|
|
843
|
-
|
|
897
|
+
compressed=compressed,
|
|
844
898
|
)
|
|
845
899
|
return class_instance
|
|
846
900
|
|
|
@@ -856,6 +910,7 @@ class MLArray:
|
|
|
856
910
|
block_size: Optional[Union[int, List, Tuple]] = None,
|
|
857
911
|
cparams: Optional[Union[Dict, blosc2.CParams]] = None,
|
|
858
912
|
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
913
|
+
compressed: bool = True,
|
|
859
914
|
):
|
|
860
915
|
"""Create an in-memory MLArray filled with ``fill_value`` and shape of ``x``.
|
|
861
916
|
|
|
@@ -886,6 +941,7 @@ class MLArray:
|
|
|
886
941
|
class_instance = cls()
|
|
887
942
|
shape, dtype, meta = class_instance._resolve_like_input(x, dtype, meta)
|
|
888
943
|
class_instance._construct_in_memory(
|
|
944
|
+
constructor="full",
|
|
889
945
|
shape=shape,
|
|
890
946
|
dtype=dtype,
|
|
891
947
|
meta=meta,
|
|
@@ -894,7 +950,8 @@ class MLArray:
|
|
|
894
950
|
block_size=block_size,
|
|
895
951
|
cparams=cparams,
|
|
896
952
|
dparams=dparams,
|
|
897
|
-
|
|
953
|
+
compressed=compressed,
|
|
954
|
+
constructor_kwargs={"fill_value": fill_value},
|
|
898
955
|
)
|
|
899
956
|
return class_instance
|
|
900
957
|
|
|
@@ -923,7 +980,8 @@ class MLArray:
|
|
|
923
980
|
|
|
924
981
|
if Path(filepath).is_file():
|
|
925
982
|
os.remove(str(filepath))
|
|
926
|
-
|
|
983
|
+
|
|
984
|
+
self._ensure_blosc2_store()
|
|
927
985
|
self._write_metadata(force=True)
|
|
928
986
|
self._store.save(str(filepath))
|
|
929
987
|
self._update_blosc2_meta()
|
|
@@ -937,6 +995,7 @@ class MLArray:
|
|
|
937
995
|
"""
|
|
938
996
|
self._write_metadata()
|
|
939
997
|
self._store = None
|
|
998
|
+
self._backend = None
|
|
940
999
|
self.filepath = None
|
|
941
1000
|
self.support_metadata = None
|
|
942
1001
|
self.mode = None
|
|
@@ -1069,6 +1128,8 @@ class MLArray:
|
|
|
1069
1128
|
"""
|
|
1070
1129
|
if self._store is None or self.meta._has_array.has_array == False:
|
|
1071
1130
|
return None
|
|
1131
|
+
if self.meta.spatial.affine is not None:
|
|
1132
|
+
return self.meta.spatial.affine
|
|
1072
1133
|
spacing = np.array(self.spacing) if self.spacing is not None else np.ones(self.spatial_ndim)
|
|
1073
1134
|
origin = np.array(self.origin) if self.origin is not None else np.zeros(self.spatial_ndim)
|
|
1074
1135
|
direction = np.array(self.direction) if self.direction is not None else np.eye(self.spatial_ndim)
|
|
@@ -1360,6 +1421,7 @@ class MLArray:
|
|
|
1360
1421
|
dparams = MLArray._resolve_dparams(dparams)
|
|
1361
1422
|
|
|
1362
1423
|
self._store = blosc2.open(urlpath=str(filepath), dparams=dparams, mode=mode, mmap_mode=mmap_mode)
|
|
1424
|
+
self._backend = "blosc2"
|
|
1363
1425
|
self._read_meta()
|
|
1364
1426
|
self._update_blosc2_meta()
|
|
1365
1427
|
self.mode = mode
|
|
@@ -1439,15 +1501,13 @@ class MLArray:
|
|
|
1439
1501
|
|
|
1440
1502
|
self._validate_and_add_meta(meta, has_array=True)
|
|
1441
1503
|
spatial_axis_mask = [True] * len(shape) if self.meta.spatial.axis_labels is None else _spatial_axis_mask(self.meta.spatial.axis_labels)
|
|
1442
|
-
self.meta.blosc2 = self._comp_and_validate_blosc2_meta(self.meta.blosc2, patch_size, chunk_size, block_size, shape, np.dtype(dtype).itemsize, spatial_axis_mask)
|
|
1504
|
+
self.meta.blosc2 = self._comp_and_validate_blosc2_meta(self.meta.blosc2, patch_size, chunk_size, block_size, shape, np.dtype(dtype).itemsize, spatial_axis_mask, cparams, dparams)
|
|
1443
1505
|
self.meta._has_array.has_array = True
|
|
1444
1506
|
|
|
1445
1507
|
self.support_metadata = str(filepath).endswith(f".{MLARRAY_SUFFIX}")
|
|
1446
|
-
|
|
1447
|
-
cparams = MLArray._resolve_cparams(cparams)
|
|
1448
|
-
dparams = MLArray._resolve_dparams(dparams)
|
|
1449
1508
|
|
|
1450
|
-
self._store = blosc2.empty(shape=shape, dtype=np.dtype(dtype), urlpath=str(filepath), chunks=self.meta.blosc2.chunk_size, blocks=self.meta.blosc2.block_size, cparams=cparams, dparams=dparams, mmap_mode=mmap_mode)
|
|
1509
|
+
self._store = blosc2.empty(shape=shape, dtype=np.dtype(dtype), urlpath=str(filepath), chunks=self.meta.blosc2.chunk_size, blocks=self.meta.blosc2.block_size, cparams=MLArray._resolve_cparams(self.meta.blosc2.cparams), dparams=MLArray._resolve_dparams(self.meta.blosc2.dparams), mmap_mode=mmap_mode)
|
|
1510
|
+
self._backend = "blosc2"
|
|
1451
1511
|
self._update_blosc2_meta()
|
|
1452
1512
|
self.mode = mode
|
|
1453
1513
|
self.mmap_mode = mmap_mode
|
|
@@ -1458,6 +1518,7 @@ class MLArray:
|
|
|
1458
1518
|
self,
|
|
1459
1519
|
filepath: Union[str, Path],
|
|
1460
1520
|
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
1521
|
+
compressed: bool = True,
|
|
1461
1522
|
):
|
|
1462
1523
|
"""Internal MLArray load method. Loads a MLArray file. Both MLArray ('.mla') and Blosc2 ('.b2nd') files are supported.
|
|
1463
1524
|
|
|
@@ -1484,10 +1545,14 @@ class MLArray:
|
|
|
1484
1545
|
ondisk = blosc2.open(str(filepath), dparams=dparams, mode="r")
|
|
1485
1546
|
cframe = ondisk.to_cframe()
|
|
1486
1547
|
self._store = blosc2.ndarray_from_cframe(cframe, copy=True)
|
|
1548
|
+
self._backend = "blosc2"
|
|
1487
1549
|
self.mode = None
|
|
1488
1550
|
self.mmap_mode = None
|
|
1489
1551
|
self._read_meta()
|
|
1490
1552
|
self._update_blosc2_meta()
|
|
1553
|
+
if not compressed:
|
|
1554
|
+
self._store = np.asarray(self._store[...])
|
|
1555
|
+
self._backend = "numpy"
|
|
1491
1556
|
|
|
1492
1557
|
def _asarray(
|
|
1493
1558
|
self,
|
|
@@ -1497,7 +1562,8 @@ class MLArray:
|
|
|
1497
1562
|
chunk_size: Optional[Union[int, List, Tuple]]= None,
|
|
1498
1563
|
block_size: Optional[Union[int, List, Tuple]] = None,
|
|
1499
1564
|
cparams: Optional[Union[Dict, blosc2.CParams]] = None,
|
|
1500
|
-
dparams: Optional[Union[Dict, blosc2.DParams]] = None
|
|
1565
|
+
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
1566
|
+
compressed: bool = True,
|
|
1501
1567
|
):
|
|
1502
1568
|
"""Internal MLArray asarray method.
|
|
1503
1569
|
|
|
@@ -1539,25 +1605,20 @@ class MLArray:
|
|
|
1539
1605
|
if not isinstance(array, np.ndarray):
|
|
1540
1606
|
raise TypeError("array must be a numpy.ndarray")
|
|
1541
1607
|
self._construct_in_memory(
|
|
1542
|
-
|
|
1608
|
+
constructor="asarray",
|
|
1609
|
+
source_array=np.ascontiguousarray(array[...]),
|
|
1543
1610
|
meta=meta,
|
|
1544
1611
|
patch_size=patch_size,
|
|
1545
1612
|
chunk_size=chunk_size,
|
|
1546
1613
|
block_size=block_size,
|
|
1547
1614
|
cparams=cparams,
|
|
1548
1615
|
dparams=dparams,
|
|
1549
|
-
|
|
1550
|
-
kwargs["source_array"],
|
|
1551
|
-
chunks=kwargs["chunks"],
|
|
1552
|
-
blocks=kwargs["blocks"],
|
|
1553
|
-
cparams=kwargs["cparams"],
|
|
1554
|
-
dparams=kwargs["dparams"],
|
|
1555
|
-
),
|
|
1616
|
+
compressed=compressed,
|
|
1556
1617
|
)
|
|
1557
1618
|
|
|
1558
1619
|
def _construct_in_memory(
|
|
1559
1620
|
self,
|
|
1560
|
-
|
|
1621
|
+
constructor: str,
|
|
1561
1622
|
shape: Optional[Union[int, List, Tuple, np.ndarray]] = None,
|
|
1562
1623
|
dtype: Optional[np.dtype] = None,
|
|
1563
1624
|
source_array: Optional[np.ndarray] = None,
|
|
@@ -1567,6 +1628,8 @@ class MLArray:
|
|
|
1567
1628
|
block_size: Optional[Union[int, List, Tuple]] = None,
|
|
1568
1629
|
cparams: Optional[Union[Dict, blosc2.CParams]] = None,
|
|
1569
1630
|
dparams: Optional[Union[Dict, blosc2.DParams]] = None,
|
|
1631
|
+
compressed: bool = True,
|
|
1632
|
+
constructor_kwargs: Optional[dict[str, Any]] = None,
|
|
1570
1633
|
):
|
|
1571
1634
|
"""Internal generic constructor for in-memory Blosc2-backed MLArrays.
|
|
1572
1635
|
|
|
@@ -1575,8 +1638,7 @@ class MLArray:
|
|
|
1575
1638
|
array shape, required when ``source_array`` is None.
|
|
1576
1639
|
dtype (Optional[np.dtype]): Target dtype, required when
|
|
1577
1640
|
``source_array`` is None.
|
|
1578
|
-
|
|
1579
|
-
returning a Blosc2 NDArray.
|
|
1641
|
+
constructor (str): Constructor operation name.
|
|
1580
1642
|
source_array (Optional[np.ndarray]): Source array that should be
|
|
1581
1643
|
converted into an in-memory Blosc2-backed store.
|
|
1582
1644
|
meta (Optional[Union[Dict, Meta]]): Optional metadata attached to
|
|
@@ -1599,12 +1661,13 @@ class MLArray:
|
|
|
1599
1661
|
Raises:
|
|
1600
1662
|
ValueError: If constructor inputs are inconsistent.
|
|
1601
1663
|
"""
|
|
1664
|
+
constructor_kwargs = {} if constructor_kwargs is None else dict(constructor_kwargs)
|
|
1665
|
+
|
|
1602
1666
|
if source_array is not None:
|
|
1603
1667
|
if shape is not None or dtype is not None:
|
|
1604
1668
|
raise ValueError(
|
|
1605
1669
|
"shape/dtype must not be set when source_array is provided."
|
|
1606
1670
|
)
|
|
1607
|
-
source_array = np.ascontiguousarray(source_array[...])
|
|
1608
1671
|
shape = self._normalize_shape(source_array.shape)
|
|
1609
1672
|
dtype = np.dtype(source_array.dtype)
|
|
1610
1673
|
else:
|
|
@@ -1621,6 +1684,7 @@ class MLArray:
|
|
|
1621
1684
|
if self.meta.spatial.axis_labels is None
|
|
1622
1685
|
else _spatial_axis_mask(self.meta.spatial.axis_labels)
|
|
1623
1686
|
)
|
|
1687
|
+
|
|
1624
1688
|
self.meta.blosc2 = self._comp_and_validate_blosc2_meta(
|
|
1625
1689
|
self.meta.blosc2,
|
|
1626
1690
|
patch_size,
|
|
@@ -1629,29 +1693,101 @@ class MLArray:
|
|
|
1629
1693
|
shape,
|
|
1630
1694
|
dtype.itemsize,
|
|
1631
1695
|
spatial_axis_mask,
|
|
1696
|
+
cparams,
|
|
1697
|
+
dparams,
|
|
1632
1698
|
)
|
|
1633
1699
|
self.meta._has_array.has_array = True
|
|
1634
1700
|
|
|
1635
|
-
|
|
1636
|
-
|
|
1701
|
+
backend_lib = blosc2 if compressed else np
|
|
1702
|
+
blosc2_storage_kwargs = {}
|
|
1703
|
+
if compressed:
|
|
1704
|
+
blosc2_storage_kwargs = {
|
|
1705
|
+
"chunks": self.meta.blosc2.chunk_size,
|
|
1706
|
+
"blocks": self.meta.blosc2.block_size,
|
|
1707
|
+
"cparams": MLArray._resolve_cparams(self.meta.blosc2.cparams),
|
|
1708
|
+
"dparams": MLArray._resolve_dparams(self.meta.blosc2.dparams),
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
if constructor == "asarray":
|
|
1712
|
+
if compressed:
|
|
1713
|
+
self._store = blosc2.asarray(source_array, **blosc2_storage_kwargs)
|
|
1714
|
+
else:
|
|
1715
|
+
self._store = np.asarray(source_array, dtype=dtype)
|
|
1716
|
+
else:
|
|
1717
|
+
call_kwargs = self._build_constructor_call_kwargs(
|
|
1718
|
+
constructor=constructor,
|
|
1719
|
+
shape=shape,
|
|
1720
|
+
dtype=dtype,
|
|
1721
|
+
constructor_kwargs=constructor_kwargs,
|
|
1722
|
+
)
|
|
1637
1723
|
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1724
|
+
if not compressed and constructor in ("arange", "linspace"):
|
|
1725
|
+
self._store = self._construct_numpy_range(constructor, call_kwargs)
|
|
1726
|
+
else:
|
|
1727
|
+
func = getattr(backend_lib, constructor, None)
|
|
1728
|
+
if func is None:
|
|
1729
|
+
raise ValueError(f"Unknown constructor '{constructor}'.")
|
|
1730
|
+
if compressed:
|
|
1731
|
+
call_kwargs.update(blosc2_storage_kwargs)
|
|
1732
|
+
self._store = func(**call_kwargs)
|
|
1733
|
+
|
|
1734
|
+
self._backend = "blosc2" if compressed else "numpy"
|
|
1648
1735
|
|
|
1649
|
-
self.
|
|
1736
|
+
self.support_metadata = True
|
|
1650
1737
|
|
|
1651
1738
|
self._update_blosc2_meta()
|
|
1652
1739
|
self._validate_and_add_meta(self.meta)
|
|
1653
1740
|
|
|
1654
|
-
|
|
1741
|
+
@staticmethod
|
|
1742
|
+
def _build_constructor_call_kwargs(
|
|
1743
|
+
constructor: str,
|
|
1744
|
+
shape: Tuple[int, ...],
|
|
1745
|
+
dtype: np.dtype,
|
|
1746
|
+
constructor_kwargs: dict[str, Any],
|
|
1747
|
+
) -> dict[str, Any]:
|
|
1748
|
+
"""Build constructor kwargs shared by NumPy and Blosc2 backends."""
|
|
1749
|
+
if constructor in ("empty", "zeros", "ones"):
|
|
1750
|
+
return {"shape": shape, "dtype": dtype}
|
|
1751
|
+
if constructor == "full":
|
|
1752
|
+
return {
|
|
1753
|
+
"shape": shape,
|
|
1754
|
+
"fill_value": constructor_kwargs["fill_value"],
|
|
1755
|
+
"dtype": dtype,
|
|
1756
|
+
}
|
|
1757
|
+
if constructor == "arange":
|
|
1758
|
+
return {
|
|
1759
|
+
"start": constructor_kwargs["start"],
|
|
1760
|
+
"stop": constructor_kwargs["stop"],
|
|
1761
|
+
"step": constructor_kwargs["step"],
|
|
1762
|
+
"dtype": dtype,
|
|
1763
|
+
"shape": shape,
|
|
1764
|
+
"c_order": constructor_kwargs.get("c_order", True),
|
|
1765
|
+
}
|
|
1766
|
+
if constructor == "linspace":
|
|
1767
|
+
return {
|
|
1768
|
+
"start": constructor_kwargs["start"],
|
|
1769
|
+
"stop": constructor_kwargs["stop"],
|
|
1770
|
+
"num": constructor_kwargs["num"],
|
|
1771
|
+
"dtype": dtype,
|
|
1772
|
+
"shape": shape,
|
|
1773
|
+
"endpoint": constructor_kwargs.get("endpoint", True),
|
|
1774
|
+
"c_order": constructor_kwargs.get("c_order", True),
|
|
1775
|
+
}
|
|
1776
|
+
raise ValueError(f"Unknown constructor '{constructor}'.")
|
|
1777
|
+
|
|
1778
|
+
@staticmethod
|
|
1779
|
+
def _construct_numpy_range(
|
|
1780
|
+
constructor: str,
|
|
1781
|
+
call_kwargs: dict[str, Any],
|
|
1782
|
+
) -> np.ndarray:
|
|
1783
|
+
"""Construct NumPy arange/linspace arrays and apply shape/order reshaping."""
|
|
1784
|
+
shape = call_kwargs.pop("shape")
|
|
1785
|
+
c_order = call_kwargs.pop("c_order", True)
|
|
1786
|
+
func = getattr(np, constructor)
|
|
1787
|
+
arr = func(**call_kwargs)
|
|
1788
|
+
return np.reshape(arr, shape, order="C" if c_order else "F")
|
|
1789
|
+
|
|
1790
|
+
def _comp_and_validate_blosc2_meta(self, meta_blosc2, patch_size, chunk_size, block_size, shape, dtype_itemsize, spatial_axis_mask, cparams, dparams):
|
|
1655
1791
|
"""Compute and validate Blosc2 chunk/block metadata.
|
|
1656
1792
|
|
|
1657
1793
|
Args:
|
|
@@ -1693,11 +1829,30 @@ class MLArray:
|
|
|
1693
1829
|
if patch_size is not None:
|
|
1694
1830
|
chunk_size, block_size = MLArray.comp_blosc2_params(shape, patch_size, spatial_axis_mask, bytes_per_pixel=dtype_itemsize)
|
|
1695
1831
|
|
|
1696
|
-
|
|
1832
|
+
cparams = MLArray._resolve_cparams(cparams)
|
|
1833
|
+
dparams = MLArray._resolve_dparams(dparams)
|
|
1834
|
+
|
|
1835
|
+
meta_blosc2 = MetaBlosc2(
|
|
1836
|
+
chunk_size=chunk_size,
|
|
1837
|
+
block_size=block_size,
|
|
1838
|
+
patch_size=patch_size,
|
|
1839
|
+
cparams=cparams,
|
|
1840
|
+
dparams=dparams,
|
|
1841
|
+
)
|
|
1697
1842
|
meta_blosc2._validate_and_cast(ndims=len(shape), spatial_ndims=num_spatial_axes)
|
|
1698
1843
|
return meta_blosc2
|
|
1699
1844
|
|
|
1700
|
-
def _validate_and_add_meta(
|
|
1845
|
+
def _validate_and_add_meta(
|
|
1846
|
+
self,
|
|
1847
|
+
meta,
|
|
1848
|
+
spacing=None,
|
|
1849
|
+
origin=None,
|
|
1850
|
+
direction=None,
|
|
1851
|
+
affine=None,
|
|
1852
|
+
axis_labels=None,
|
|
1853
|
+
has_array=None,
|
|
1854
|
+
validate=True,
|
|
1855
|
+
):
|
|
1701
1856
|
"""Validate and attach metadata to the MLArray instance.
|
|
1702
1857
|
|
|
1703
1858
|
Args:
|
|
@@ -1709,6 +1864,8 @@ class MLArray:
|
|
|
1709
1864
|
spatial axis.
|
|
1710
1865
|
direction (Optional[Union[List, Tuple, np.ndarray]]): Direction
|
|
1711
1866
|
cosine matrix with shape (ndims, ndims).
|
|
1867
|
+
affine (Optional[Union[List, Tuple, np.ndarray]]): Homogeneous
|
|
1868
|
+
affine matrix with shape (spatial_ndims + 1, spatial_ndims + 1).
|
|
1712
1869
|
axis_labels (Optional[List[Union[str, AxisLabel]]]): Per-axis labels or roles. Length must match ndims.
|
|
1713
1870
|
has_array (Optional[bool]): Explicitly set whether array data is
|
|
1714
1871
|
present. When True, metadata is validated with array-dependent
|
|
@@ -1726,12 +1883,22 @@ class MLArray:
|
|
|
1726
1883
|
meta = Meta()
|
|
1727
1884
|
self.meta = meta
|
|
1728
1885
|
self.meta._mlarray_version = MLARRAY_VERSION
|
|
1886
|
+
|
|
1887
|
+
if affine is not None and (
|
|
1888
|
+
spacing is not None or origin is not None or direction is not None
|
|
1889
|
+
):
|
|
1890
|
+
raise ValueError(
|
|
1891
|
+
"affine cannot be provided together with spacing, origin, or direction."
|
|
1892
|
+
)
|
|
1893
|
+
|
|
1729
1894
|
if spacing is not None:
|
|
1730
1895
|
self.meta.spatial.spacing = spacing
|
|
1731
1896
|
if origin is not None:
|
|
1732
1897
|
self.meta.spatial.origin = origin
|
|
1733
1898
|
if direction is not None:
|
|
1734
1899
|
self.meta.spatial.direction = direction
|
|
1900
|
+
if affine is not None:
|
|
1901
|
+
self.meta.spatial.affine = affine
|
|
1735
1902
|
if axis_labels is not None:
|
|
1736
1903
|
self.meta.spatial.axis_labels = axis_labels
|
|
1737
1904
|
if has_array == True:
|
|
@@ -1747,15 +1914,18 @@ class MLArray:
|
|
|
1747
1914
|
Updates ``self.meta.blosc2`` from the underlying store when the array
|
|
1748
1915
|
is present.
|
|
1749
1916
|
"""
|
|
1750
|
-
if self.
|
|
1917
|
+
if self._backend != "blosc2":
|
|
1918
|
+
return
|
|
1919
|
+
if self.support_metadata and self.meta._has_array.has_array == True:
|
|
1751
1920
|
self.meta.blosc2.chunk_size = list(self._store.chunks)
|
|
1752
1921
|
self.meta.blosc2.block_size = list(self._store.blocks)
|
|
1753
1922
|
|
|
1754
1923
|
def _read_meta(self):
|
|
1755
1924
|
"""Read MLArray metadata from the underlying store, if available."""
|
|
1756
1925
|
meta = Meta()
|
|
1757
|
-
if self.support_metadata and
|
|
1926
|
+
if self.support_metadata and self._backend == "blosc2":
|
|
1758
1927
|
meta = self._store.vlmeta["mlarray"]
|
|
1928
|
+
meta = pickle.loads(gzip.decompress(meta))
|
|
1759
1929
|
meta = Meta.from_mapping(meta)
|
|
1760
1930
|
self._validate_and_add_meta(meta)
|
|
1761
1931
|
|
|
@@ -1766,7 +1936,7 @@ class MLArray:
|
|
|
1766
1936
|
force (bool): If True, write even when mmap_mode is read-only.
|
|
1767
1937
|
"""
|
|
1768
1938
|
is_writable = False
|
|
1769
|
-
if self.support_metadata
|
|
1939
|
+
if self.support_metadata:
|
|
1770
1940
|
if self.mode in ('a', 'w') and self.mmap_mode is None:
|
|
1771
1941
|
is_writable = True
|
|
1772
1942
|
elif self.mmap_mode in ('r+', 'w+'):
|
|
@@ -1776,11 +1946,58 @@ class MLArray:
|
|
|
1776
1946
|
|
|
1777
1947
|
if not is_writable:
|
|
1778
1948
|
return
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1949
|
+
|
|
1950
|
+
if self._backend != "blosc2":
|
|
1951
|
+
return
|
|
1952
|
+
|
|
1953
|
+
meta = self.meta.to_mapping()
|
|
1954
|
+
if not is_serializable(meta):
|
|
1782
1955
|
raise RuntimeError("Metadata is not serializable.")
|
|
1783
|
-
|
|
1956
|
+
|
|
1957
|
+
meta = gzip.compress(pickle.dumps(meta, protocol=pickle.HIGHEST_PROTOCOL))
|
|
1958
|
+
self._store.vlmeta["mlarray"] = meta
|
|
1959
|
+
|
|
1960
|
+
def _ensure_blosc2_store(self):
|
|
1961
|
+
"""Ensure underlying store is Blosc2, converting from NumPy when needed."""
|
|
1962
|
+
if self._store is None:
|
|
1963
|
+
self._store = blosc2.empty((0,))
|
|
1964
|
+
self._backend = "blosc2"
|
|
1965
|
+
return
|
|
1966
|
+
|
|
1967
|
+
if self._backend == "blosc2":
|
|
1968
|
+
return
|
|
1969
|
+
|
|
1970
|
+
array = np.asarray(self._store)
|
|
1971
|
+
if not self.meta._has_array.has_array:
|
|
1972
|
+
self._store = blosc2.empty((0,))
|
|
1973
|
+
self._backend = "blosc2"
|
|
1974
|
+
return
|
|
1975
|
+
|
|
1976
|
+
shape = self._normalize_shape(array.shape)
|
|
1977
|
+
spatial_axis_mask = (
|
|
1978
|
+
[True] * len(shape)
|
|
1979
|
+
if self.meta.spatial.axis_labels is None
|
|
1980
|
+
else _spatial_axis_mask(self.meta.spatial.axis_labels)
|
|
1981
|
+
)
|
|
1982
|
+
self.meta.blosc2 = self._comp_and_validate_blosc2_meta(
|
|
1983
|
+
self.meta.blosc2,
|
|
1984
|
+
patch_size="default",
|
|
1985
|
+
chunk_size=self.meta.blosc2.chunk_size,
|
|
1986
|
+
block_size=self.meta.blosc2.block_size,
|
|
1987
|
+
shape=shape,
|
|
1988
|
+
dtype_itemsize=np.dtype(array.dtype).itemsize,
|
|
1989
|
+
spatial_axis_mask=spatial_axis_mask,
|
|
1990
|
+
cparams=self.meta.blosc2.cparams,
|
|
1991
|
+
dparams=self.meta.blosc2.dparams,
|
|
1992
|
+
)
|
|
1993
|
+
self._store = blosc2.asarray(
|
|
1994
|
+
np.ascontiguousarray(array),
|
|
1995
|
+
chunks=self.meta.blosc2.chunk_size,
|
|
1996
|
+
blocks=self.meta.blosc2.block_size,
|
|
1997
|
+
cparams=MLArray._resolve_cparams(self.meta.blosc2.cparams),
|
|
1998
|
+
dparams=MLArray._resolve_dparams(self.meta.blosc2.dparams),
|
|
1999
|
+
)
|
|
2000
|
+
self._backend = "blosc2"
|
|
1784
2001
|
|
|
1785
2002
|
@staticmethod
|
|
1786
2003
|
def _normalize_shape(shape: Union[int, List, Tuple, np.ndarray]) -> Tuple[int, ...]:
|
|
@@ -1812,6 +2029,19 @@ class MLArray:
|
|
|
1812
2029
|
"""Resolve compression params with MLArray defaults."""
|
|
1813
2030
|
if cparams is None:
|
|
1814
2031
|
return {"codec": blosc2.Codec.LZ4HC, "clevel": 8}
|
|
2032
|
+
if isinstance(cparams, dict):
|
|
2033
|
+
cparams = dict(cparams)
|
|
2034
|
+
if "codec" in cparams and not isinstance(cparams["codec"], blosc2.Codec):
|
|
2035
|
+
cparams["codec"] = blosc2.Codec(cparams["codec"])
|
|
2036
|
+
if "splitmode" in cparams and not isinstance(cparams["splitmode"], blosc2.SplitMode):
|
|
2037
|
+
cparams["splitmode"] = blosc2.SplitMode(cparams["splitmode"])
|
|
2038
|
+
if "tuner" in cparams and not isinstance(cparams["tuner"], blosc2.Tuner):
|
|
2039
|
+
cparams["tuner"] = blosc2.Tuner(cparams["tuner"])
|
|
2040
|
+
if "filters" in cparams and isinstance(cparams["filters"], (list, tuple)):
|
|
2041
|
+
cparams["filters"] = [
|
|
2042
|
+
f if isinstance(f, blosc2.Filter) else blosc2.Filter(f)
|
|
2043
|
+
for f in cparams["filters"]
|
|
2044
|
+
]
|
|
1815
2045
|
return cparams
|
|
1816
2046
|
|
|
1817
2047
|
@staticmethod
|
|
@@ -22,6 +22,7 @@ docs/why.md
|
|
|
22
22
|
examples/example_asarray.py
|
|
23
23
|
examples/example_bboxes_only.py
|
|
24
24
|
examples/example_channel.py
|
|
25
|
+
examples/example_compressed_vs_uncompressed.py
|
|
25
26
|
examples/example_in_memory_constructors.py
|
|
26
27
|
examples/example_metadata_only.py
|
|
27
28
|
examples/example_non_spatial.py
|
|
@@ -64,6 +64,22 @@ class TestAsArray(unittest.TestCase):
|
|
|
64
64
|
self.assertEqual(image.meta.source.to_plain(), {"patient_id": "p-001"})
|
|
65
65
|
self.assertTrue(image.meta.is_seg)
|
|
66
66
|
|
|
67
|
+
def test_asarray_uncompressed_numpy_store_and_save(self):
|
|
68
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
69
|
+
array = _make_array(seed=3)
|
|
70
|
+
image = MLArray.asarray(array, meta={"case_id": "np"}, compressed=False)
|
|
71
|
+
|
|
72
|
+
self.assertTrue(isinstance(image._store, np.ndarray))
|
|
73
|
+
self.assertTrue(np.allclose(image.to_numpy(), array))
|
|
74
|
+
self.assertEqual(image.meta.source.to_plain(), {"case_id": "np"})
|
|
75
|
+
|
|
76
|
+
path = Path(tmpdir) / "asarray-numpy-save.mla"
|
|
77
|
+
image.save(path)
|
|
78
|
+
loaded = MLArray(path)
|
|
79
|
+
self.assertFalse(isinstance(loaded._store, np.ndarray))
|
|
80
|
+
self.assertTrue(np.allclose(loaded.to_numpy(), array))
|
|
81
|
+
self.assertEqual(loaded.meta.source.to_plain(), {"case_id": "np"})
|
|
82
|
+
|
|
67
83
|
|
|
68
84
|
if __name__ == "__main__":
|
|
69
85
|
unittest.main()
|
|
@@ -25,6 +25,18 @@ class TestUsage(unittest.TestCase):
|
|
|
25
25
|
loaded = MLArray(path)
|
|
26
26
|
self.assertEqual(loaded.shape, array.shape)
|
|
27
27
|
|
|
28
|
+
def test_default_usage_uncompressed_backend(self):
|
|
29
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
30
|
+
array = _make_array(seed=11)
|
|
31
|
+
image = MLArray(array, compressed=False)
|
|
32
|
+
self.assertTrue(isinstance(image._store, np.ndarray))
|
|
33
|
+
|
|
34
|
+
path = Path(tmpdir) / "sample-uncompressed.mla"
|
|
35
|
+
image.save(path)
|
|
36
|
+
loaded = MLArray(path)
|
|
37
|
+
self.assertEqual(loaded.shape, array.shape)
|
|
38
|
+
self.assertTrue(np.allclose(loaded.to_numpy(), array))
|
|
39
|
+
|
|
28
40
|
def test_mmap_loading(self):
|
|
29
41
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
30
42
|
array = _make_array()
|
|
@@ -34,6 +46,16 @@ class TestUsage(unittest.TestCase):
|
|
|
34
46
|
loaded = MLArray.open(path, mmap_mode="r")
|
|
35
47
|
self.assertFalse(isinstance(loaded._store, np.ndarray))
|
|
36
48
|
|
|
49
|
+
def test_load_uncompressed_store(self):
|
|
50
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
51
|
+
array = _make_array()
|
|
52
|
+
path = Path(tmpdir) / "sample-load-uncompressed.mla"
|
|
53
|
+
MLArray(array).save(path)
|
|
54
|
+
|
|
55
|
+
loaded = MLArray.load(path, compressed=False)
|
|
56
|
+
self.assertTrue(isinstance(loaded._store, np.ndarray))
|
|
57
|
+
self.assertTrue(np.allclose(loaded.to_numpy(), array))
|
|
58
|
+
|
|
37
59
|
def test_loading_and_saving(self):
|
|
38
60
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
39
61
|
array = _make_array()
|
|
@@ -66,6 +88,32 @@ class TestUsage(unittest.TestCase):
|
|
|
66
88
|
self.assertEqual(loaded.origin, [10.0, 10.0, 30.0])
|
|
67
89
|
self.assertEqual(loaded.meta.source["study_id"], "study-001")
|
|
68
90
|
|
|
91
|
+
def test_affine_stored_in_meta_spatial(self):
|
|
92
|
+
array = _make_array(shape=(8, 8, 8))
|
|
93
|
+
affine = [
|
|
94
|
+
[1.0, 0.0, 0.0, 10.0],
|
|
95
|
+
[0.0, 2.0, 0.0, 20.0],
|
|
96
|
+
[0.0, 0.0, 3.0, 30.0],
|
|
97
|
+
[0.0, 0.0, 0.0, 1.0],
|
|
98
|
+
]
|
|
99
|
+
image = MLArray(array, affine=affine)
|
|
100
|
+
|
|
101
|
+
self.assertEqual(image.meta.spatial.affine, affine)
|
|
102
|
+
self.assertIsNone(image.meta.spatial.spacing)
|
|
103
|
+
self.assertIsNone(image.meta.spatial.origin)
|
|
104
|
+
self.assertIsNone(image.meta.spatial.direction)
|
|
105
|
+
|
|
106
|
+
def test_affine_and_spacing_origin_direction_mix_raises(self):
|
|
107
|
+
array = _make_array(shape=(8, 8, 8))
|
|
108
|
+
affine = [
|
|
109
|
+
[1.0, 0.0, 0.0, 10.0],
|
|
110
|
+
[0.0, 1.0, 0.0, 20.0],
|
|
111
|
+
[0.0, 0.0, 1.0, 30.0],
|
|
112
|
+
[0.0, 0.0, 0.0, 1.0],
|
|
113
|
+
]
|
|
114
|
+
with self.assertRaises(ValueError):
|
|
115
|
+
MLArray(array, spacing=(1.0, 1.0, 1.0), affine=affine)
|
|
116
|
+
|
|
69
117
|
def test_copy_metadata_with_override(self):
|
|
70
118
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
71
119
|
array = _make_array()
|
|
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
|