ngio 0.4.0a4__py3-none-any.whl → 0.4.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ngio/common/_dimensions.py +209 -189
- ngio/common/_roi.py +2 -2
- ngio/experimental/iterators/__init__.py +0 -2
- ngio/experimental/iterators/_abstract_iterator.py +246 -26
- ngio/experimental/iterators/_feature.py +84 -41
- ngio/experimental/iterators/_image_processing.py +7 -36
- ngio/experimental/iterators/_mappers.py +48 -0
- ngio/experimental/iterators/_segmentation.py +7 -38
- ngio/images/_abstract_image.py +60 -5
- ngio/images/_image.py +2 -0
- ngio/images/_label.py +2 -0
- ngio/images/_masked_image.py +22 -17
- ngio/io_pipes/__init__.py +29 -3
- ngio/io_pipes/_io_pipes.py +93 -18
- ngio/io_pipes/_io_pipes_masked.py +17 -10
- ngio/io_pipes/_io_pipes_roi.py +10 -1
- ngio/io_pipes/_io_pipes_types.py +56 -0
- ngio/io_pipes/_ops_axes.py +199 -1
- ngio/io_pipes/_ops_slices.py +255 -27
- ngio/io_pipes/_ops_slices_utils.py +196 -0
- ngio/io_pipes/_ops_transforms.py +1 -1
- ngio/io_pipes/_zoom_transform.py +5 -5
- ngio/ome_zarr_meta/__init__.py +0 -2
- ngio/ome_zarr_meta/ngio_specs/__init__.py +0 -2
- ngio/ome_zarr_meta/ngio_specs/_axes.py +7 -131
- ngio/utils/_datasets.py +5 -0
- {ngio-0.4.0a4.dist-info → ngio-0.4.0b1.dist-info}/METADATA +1 -1
- {ngio-0.4.0a4.dist-info → ngio-0.4.0b1.dist-info}/RECORD +30 -28
- ngio/io_pipes/_io_pipes_utils.py +0 -299
- {ngio-0.4.0a4.dist-info → ngio-0.4.0b1.dist-info}/WHEEL +0 -0
- {ngio-0.4.0a4.dist-info → ngio-0.4.0b1.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,7 +4,6 @@ from collections.abc import Sequence
|
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from typing import Literal, TypeAlias, TypeVar
|
|
6
6
|
|
|
7
|
-
import numpy as np
|
|
8
7
|
from pydantic import BaseModel, ConfigDict, Field
|
|
9
8
|
|
|
10
9
|
from ngio.utils import NgioValidationError, NgioValueError
|
|
@@ -263,63 +262,6 @@ def validate_axes(
|
|
|
263
262
|
)
|
|
264
263
|
|
|
265
264
|
|
|
266
|
-
class AxesOps(BaseModel):
|
|
267
|
-
"""Model to represent axes operations.
|
|
268
|
-
|
|
269
|
-
This model will be used to transform objects from on disk axes to in memory axes.
|
|
270
|
-
"""
|
|
271
|
-
|
|
272
|
-
on_disk_axes: tuple[str, ...]
|
|
273
|
-
in_memory_axes: tuple[str, ...]
|
|
274
|
-
transpose_op: tuple[int, ...] | None = None
|
|
275
|
-
expand_op: tuple[int, ...] | None = None
|
|
276
|
-
squeeze_op: tuple[int, ...] | None = None
|
|
277
|
-
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
|
|
278
|
-
|
|
279
|
-
@property
|
|
280
|
-
def is_no_op(self) -> bool:
|
|
281
|
-
"""Check if all operations are no ops."""
|
|
282
|
-
if (
|
|
283
|
-
self.transpose_op is None
|
|
284
|
-
and self.expand_op is None
|
|
285
|
-
and self.squeeze_op is None
|
|
286
|
-
):
|
|
287
|
-
return True
|
|
288
|
-
return False
|
|
289
|
-
|
|
290
|
-
@property
|
|
291
|
-
def get_transpose_op(self) -> tuple[int, ...] | None:
|
|
292
|
-
"""Get the transpose axes."""
|
|
293
|
-
return self.transpose_op
|
|
294
|
-
|
|
295
|
-
@property
|
|
296
|
-
def get_expand_op(self) -> tuple[int, ...] | None:
|
|
297
|
-
"""Get the expand axes."""
|
|
298
|
-
return self.expand_op
|
|
299
|
-
|
|
300
|
-
@property
|
|
301
|
-
def get_squeeze_op(self) -> tuple[int, ...] | None:
|
|
302
|
-
"""Get the squeeze axes."""
|
|
303
|
-
return self.squeeze_op
|
|
304
|
-
|
|
305
|
-
@property
|
|
306
|
-
def set_transpose_op(self) -> tuple[int, ...] | None:
|
|
307
|
-
"""Set the transpose axes."""
|
|
308
|
-
if self.transpose_op is None:
|
|
309
|
-
return None
|
|
310
|
-
return tuple(np.argsort(self.transpose_op))
|
|
311
|
-
|
|
312
|
-
@property
|
|
313
|
-
def set_expand_op(self) -> tuple[int, ...] | None:
|
|
314
|
-
"""Set the expand axes."""
|
|
315
|
-
return self.squeeze_op
|
|
316
|
-
|
|
317
|
-
@property
|
|
318
|
-
def set_squeeze_op(self) -> tuple[int, ...] | None:
|
|
319
|
-
"""Set the squeeze axes."""
|
|
320
|
-
return self.expand_op
|
|
321
|
-
|
|
322
|
-
|
|
323
265
|
class AxesHandler:
|
|
324
266
|
"""This class is used to handle and operate on OME-Zarr axes.
|
|
325
267
|
|
|
@@ -487,6 +429,13 @@ class AxesHandler:
|
|
|
487
429
|
"""Get the index of the axis by name."""
|
|
488
430
|
return self._index_mapping.get(name, None)
|
|
489
431
|
|
|
432
|
+
def has_axis(self, axis_name: str) -> bool:
|
|
433
|
+
"""Return whether the axis exists."""
|
|
434
|
+
index = self.get_index(axis_name)
|
|
435
|
+
if index is None:
|
|
436
|
+
return False
|
|
437
|
+
return True
|
|
438
|
+
|
|
490
439
|
def get_canonical_name(self, name: str) -> str | None:
|
|
491
440
|
"""Get the canonical name of the axis by name."""
|
|
492
441
|
return self._axes_setup.get_canonical_name(name)
|
|
@@ -514,79 +463,6 @@ class AxesHandler:
|
|
|
514
463
|
new_axes.append(axes)
|
|
515
464
|
self._axes = new_axes
|
|
516
465
|
|
|
517
|
-
def _reorder_axes(
|
|
518
|
-
self, names: Sequence[str]
|
|
519
|
-
) -> tuple[tuple[int, ...] | None, tuple[int, ...] | None, tuple[int, ...] | None]:
|
|
520
|
-
"""Change the order of the axes."""
|
|
521
|
-
# Validate the names
|
|
522
|
-
unique_names = set(names)
|
|
523
|
-
if len(unique_names) != len(names):
|
|
524
|
-
raise NgioValueError(
|
|
525
|
-
"Duplicate axis names found. Please provide unique names for each axis."
|
|
526
|
-
)
|
|
527
|
-
for name in names:
|
|
528
|
-
if not isinstance(name, str):
|
|
529
|
-
raise NgioValueError(
|
|
530
|
-
f"Invalid axis name '{name}'. Axis names must be strings."
|
|
531
|
-
)
|
|
532
|
-
inv_canonical_map = self.axes_setup.inverse_canonical_map()
|
|
533
|
-
|
|
534
|
-
# Step 1: Check find squeeze axes
|
|
535
|
-
_axes_to_squeeze: list[int] = []
|
|
536
|
-
axes_names_after_squeeze = []
|
|
537
|
-
for i, ax in enumerate(self.axes_names):
|
|
538
|
-
# If the axis is not in the names, it means we need to squeeze it
|
|
539
|
-
ax_canonical = inv_canonical_map.get(ax, None)
|
|
540
|
-
if ax not in names and ax_canonical not in names:
|
|
541
|
-
_axes_to_squeeze.append(i)
|
|
542
|
-
elif ax in names:
|
|
543
|
-
axes_names_after_squeeze.append(ax)
|
|
544
|
-
elif ax_canonical in names:
|
|
545
|
-
# If the axis is in the canonical map, we add it to the names
|
|
546
|
-
axes_names_after_squeeze.append(ax_canonical)
|
|
547
|
-
|
|
548
|
-
axes_to_squeeze = tuple(_axes_to_squeeze) if len(_axes_to_squeeze) > 0 else None
|
|
549
|
-
|
|
550
|
-
# Step 2: Find the transposition order
|
|
551
|
-
_transposition_order: list[int] = []
|
|
552
|
-
axes_names_after_transpose = []
|
|
553
|
-
for ax in names:
|
|
554
|
-
if ax in axes_names_after_squeeze:
|
|
555
|
-
_transposition_order.append(axes_names_after_squeeze.index(ax))
|
|
556
|
-
axes_names_after_transpose.append(ax)
|
|
557
|
-
|
|
558
|
-
if np.allclose(_transposition_order, range(len(_transposition_order))):
|
|
559
|
-
# If the transposition order is the identity, we don't need to transpose
|
|
560
|
-
transposition_order = None
|
|
561
|
-
else:
|
|
562
|
-
transposition_order = tuple(_transposition_order)
|
|
563
|
-
|
|
564
|
-
# Step 3: Find axes to expand
|
|
565
|
-
_axes_to_expand: list[int] = []
|
|
566
|
-
for i, name in enumerate(names):
|
|
567
|
-
if name not in axes_names_after_transpose:
|
|
568
|
-
# If the axis is not in the mapping, it means we need to expand it
|
|
569
|
-
_axes_to_expand.append(i)
|
|
570
|
-
|
|
571
|
-
axes_to_expand = tuple(_axes_to_expand) if len(_axes_to_expand) > 0 else None
|
|
572
|
-
return axes_to_squeeze, transposition_order, axes_to_expand
|
|
573
|
-
|
|
574
|
-
def get_axes_ops(self, names: Sequence[str]) -> AxesOps:
|
|
575
|
-
"""Get the axes operations to go from on-disk to in-memory axes."""
|
|
576
|
-
axes_to_squeeze, transposition_order, axes_to_expand = self._reorder_axes(names)
|
|
577
|
-
return AxesOps(
|
|
578
|
-
on_disk_axes=self.axes_names,
|
|
579
|
-
in_memory_axes=tuple(names),
|
|
580
|
-
transpose_op=transposition_order,
|
|
581
|
-
expand_op=axes_to_expand,
|
|
582
|
-
squeeze_op=axes_to_squeeze,
|
|
583
|
-
)
|
|
584
|
-
|
|
585
|
-
def get_canonical_axes_ops(self) -> AxesOps:
|
|
586
|
-
"""Get the axes operations to go from on-disk to canonical in-memory axes."""
|
|
587
|
-
other = self._axes_setup.others
|
|
588
|
-
return self.get_axes_ops(list(self._canonical_order) + other)
|
|
589
|
-
|
|
590
466
|
|
|
591
467
|
def build_canonical_axes_handler(
|
|
592
468
|
axes_names: Sequence[str],
|
ngio/utils/_datasets.py
CHANGED
|
@@ -38,6 +38,11 @@ class UnzipAndRename(pooch.Unzip):
|
|
|
38
38
|
return None
|
|
39
39
|
|
|
40
40
|
tmp_dir = Path(extract_dir) / "tmp"
|
|
41
|
+
# If tmp_dir exists, remove it
|
|
42
|
+
if tmp_dir.exists():
|
|
43
|
+
shutil.rmtree(tmp_dir, ignore_errors=True)
|
|
44
|
+
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
|
|
41
46
|
super()._extract_file(fname, tmp_dir)
|
|
42
47
|
|
|
43
48
|
list_extracted_dirs = tmp_dir.iterdir()
|
|
@@ -1,43 +1,45 @@
|
|
|
1
1
|
ngio/__init__.py,sha256=rEgnXuU6TCejUUGsxt4eKmjMhxjYh0fYBxWF4o5YjbE,1435
|
|
2
2
|
ngio/common/__init__.py,sha256=aPSuUbdGryrxbnlWrsVNe3LZoBAWC4GijR1BNH1UwuU,612
|
|
3
|
-
ngio/common/_dimensions.py,sha256=
|
|
3
|
+
ngio/common/_dimensions.py,sha256=w8PYgyWxA8hgJETjFbw5CXf7WrasCL5FbzgfL1in86M,11361
|
|
4
4
|
ngio/common/_masking_roi.py,sha256=ZZTXordEZoq_ADk0OzADvq-5dPOwUBSuNobzFR8fpTw,5697
|
|
5
5
|
ngio/common/_pyramid.py,sha256=F5OI_mULxzMTpMeihC4Y22cjuB5GKN2jRdpwfMXJHiE,8018
|
|
6
|
-
ngio/common/_roi.py,sha256=
|
|
6
|
+
ngio/common/_roi.py,sha256=DFI2BJdJ-JNC5uZARxlvL25ZYHb9HJhCP-sOID_Db20,10196
|
|
7
7
|
ngio/common/_synt_images_utils.py,sha256=B6uYOW1NyrM06YMR-csca3_YnAAkPRTbvnbLdy9tk9E,3188
|
|
8
8
|
ngio/common/_zoom.py,sha256=U01c-vqXjzZkrpd9Yvs24frVfTls_xPJeeaFCGmUwYI,6727
|
|
9
9
|
ngio/experimental/__init__.py,sha256=3pmBtHi-i8bKjTsvrOJM56ZyRX3Pv_dceCdt88-8COQ,147
|
|
10
|
-
ngio/experimental/iterators/__init__.py,sha256=
|
|
11
|
-
ngio/experimental/iterators/_abstract_iterator.py,sha256=
|
|
12
|
-
ngio/experimental/iterators/_feature.py,sha256=
|
|
13
|
-
ngio/experimental/iterators/_image_processing.py,sha256=
|
|
10
|
+
ngio/experimental/iterators/__init__.py,sha256=TECOMGb5PEEZ0yXxt8pqIGvLKG41g_L1fTJU-zGPeV8,488
|
|
11
|
+
ngio/experimental/iterators/_abstract_iterator.py,sha256=7aAoMI-6vYGCMKxO3M10WpuBMTdqX4zr0K-TutAOp88,13195
|
|
12
|
+
ngio/experimental/iterators/_feature.py,sha256=g03yHIzYpTVeD3KUq2a4TwX29Dt48XaaGdZNSykYy7M,6749
|
|
13
|
+
ngio/experimental/iterators/_image_processing.py,sha256=cM7sL7xgdcjSOKAu-6367Aov89o6wgiJ_wqCGkU2Bsw,5091
|
|
14
|
+
ngio/experimental/iterators/_mappers.py,sha256=VVVsjems57wJUnWeufUFcgqa23k7VPeFL4Nc04HVw4o,1399
|
|
14
15
|
ngio/experimental/iterators/_rois_utils.py,sha256=Q-8lQ26neYn63h_RvfypYqvrq2UUN2O3xqVe57k_ufU,4363
|
|
15
|
-
ngio/experimental/iterators/_segmentation.py,sha256=
|
|
16
|
+
ngio/experimental/iterators/_segmentation.py,sha256=xzotGvTn04HPeMeXZ_URnQqWco6d2lH6Ng6vkCUh9NM,9153
|
|
16
17
|
ngio/hcs/__init__.py,sha256=G8j9vD-liLeB_UeGtKYIgshWvJnUA6ks9GwjvWBLdHs,357
|
|
17
18
|
ngio/hcs/_plate.py,sha256=qfRwbCKaoz_AWTi8RDFFwOxy5geSknfJrPcqFVno9zI,44288
|
|
18
19
|
ngio/images/__init__.py,sha256=9Whvt7GTiCgT_vXaEEqGnDaY1-UsRk3dhLTv091F_g4,1211
|
|
19
|
-
ngio/images/_abstract_image.py,sha256=
|
|
20
|
+
ngio/images/_abstract_image.py,sha256=hrB9xn4MFRxnxE1d7HKnM8SXVPUGhMD9u32yBHTsFiU,18517
|
|
20
21
|
ngio/images/_create.py,sha256=61cuco2jUK25WzOY-Sel9s931FtGPL2ut25L9W10bJ4,10171
|
|
21
22
|
ngio/images/_create_synt_container.py,sha256=il_rr5_2KIQ5Xsskj2rb2fEm100ZErZq89aW06kn_7k,5444
|
|
22
|
-
ngio/images/_image.py,sha256=
|
|
23
|
-
ngio/images/_label.py,sha256=
|
|
24
|
-
ngio/images/_masked_image.py,sha256=
|
|
23
|
+
ngio/images/_image.py,sha256=1WQOI26ITn1R2raR9JWVEhp6nKBE3ntXXAQr7EMTOEo,33054
|
|
24
|
+
ngio/images/_label.py,sha256=RIfXrLSKMQ-j8q-aaSiWeIwFEKdxAkQT1HbJNq4PJv0,11800
|
|
25
|
+
ngio/images/_masked_image.py,sha256=YhbBzgPZMav6rX0WYue1BaxAzEIsfaQrxUIOK6ZWZcw,18848
|
|
25
26
|
ngio/images/_ome_zarr_container.py,sha256=UJERXEgBkwclpLaHzWqUIQc6P-TG4zYuKuxPukJGa4Y,38433
|
|
26
27
|
ngio/images/_table_ops.py,sha256=jFv_AMqoB4JBpoWsMtZppZVW7dAOC_u-JpfNm8b33kY,15292
|
|
27
|
-
ngio/io_pipes/__init__.py,sha256=
|
|
28
|
-
ngio/io_pipes/_io_pipes.py,sha256=
|
|
29
|
-
ngio/io_pipes/_io_pipes_masked.py,sha256=
|
|
30
|
-
ngio/io_pipes/_io_pipes_roi.py,sha256=
|
|
31
|
-
ngio/io_pipes/
|
|
28
|
+
ngio/io_pipes/__init__.py,sha256=arW_7GWzZs82kPNKdm_6B1sIDFV0lWwp-ZaORr9Q1FQ,2412
|
|
29
|
+
ngio/io_pipes/_io_pipes.py,sha256=KuwMYofE11EKx0iplWXBQZ-eE1A9YpGDprWh98cUlAI,10710
|
|
30
|
+
ngio/io_pipes/_io_pipes_masked.py,sha256=077j1XNgyCNOHlOsbjPOWcLtb2ccFvbLp-cPzWL1j3c,16999
|
|
31
|
+
ngio/io_pipes/_io_pipes_roi.py,sha256=E_t3Lb5JD6zgsIok2w98xKks_6vhLIfaKDJIf2J95sA,4809
|
|
32
|
+
ngio/io_pipes/_io_pipes_types.py,sha256=xCeUd9rZe2wsgoZR1pUq6L4LSeawoPsb-9pmz5C6ztA,1393
|
|
32
33
|
ngio/io_pipes/_match_shape.py,sha256=eDy_Eqzld08m9zDuIjAvJnIhWh_HVjQS-5Pw9BMjdbw,13200
|
|
33
|
-
ngio/io_pipes/_ops_axes.py,sha256=
|
|
34
|
-
ngio/io_pipes/_ops_slices.py,sha256=
|
|
35
|
-
ngio/io_pipes/
|
|
36
|
-
ngio/io_pipes/
|
|
37
|
-
ngio/
|
|
34
|
+
ngio/io_pipes/_ops_axes.py,sha256=Geg4ZXxB0njWWopX9YeiwRJJ9Ef2GKfG0NIUafOmi2c,10043
|
|
35
|
+
ngio/io_pipes/_ops_slices.py,sha256=7wBXucmIOW0a1r9MzEVevPOEefyhS27-yhaF8aeyD6Y,15804
|
|
36
|
+
ngio/io_pipes/_ops_slices_utils.py,sha256=mKbAEccwsQjg6XyyJ5Ph5Xr9z6pUUCO3tZ4Yal-wYMs,6590
|
|
37
|
+
ngio/io_pipes/_ops_transforms.py,sha256=uITs6v6sZ7DQ_Hpw3JdX8MuPOzir-bihvGzY84Qn4wY,2934
|
|
38
|
+
ngio/io_pipes/_zoom_transform.py,sha256=bUeFTeGUbz3Io4uArqyHXkvLZmuKsOri1lvMhDXWchA,6716
|
|
39
|
+
ngio/ome_zarr_meta/__init__.py,sha256=tzxW2oVhfeMBVuv3XkcbOLzMnbDvUnie-AVsvSxRkdE,1265
|
|
38
40
|
ngio/ome_zarr_meta/_meta_handlers.py,sha256=ctknNDT8jxwyvxQf9on5gW31H1tRRsnneO38GT2UXoE,25880
|
|
39
|
-
ngio/ome_zarr_meta/ngio_specs/__init__.py,sha256=
|
|
40
|
-
ngio/ome_zarr_meta/ngio_specs/_axes.py,sha256=
|
|
41
|
+
ngio/ome_zarr_meta/ngio_specs/__init__.py,sha256=U2FqZR91Ob2N6CqKdyw-_Ll-wMgmGuPZGVRCr6wuRFY,1698
|
|
42
|
+
ngio/ome_zarr_meta/ngio_specs/_axes.py,sha256=gNzxCddn53m9dlNJlV5CL-aQS-nGJrbCDGfAp5L97LY,16412
|
|
41
43
|
ngio/ome_zarr_meta/ngio_specs/_channels.py,sha256=CVsbG52U31TaMdTj8XqvClUdBya2Ar3qBjDo_xhP-NM,16967
|
|
42
44
|
ngio/ome_zarr_meta/ngio_specs/_dataset.py,sha256=CrHnjVWBGYPqErKkHR-E2DKrE3DmGznXMkd3Y9Z4uYo,3434
|
|
43
45
|
ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py,sha256=N1CGPOubwf0pvm8tiTnh-C1cOu9lToyDe3WagnEnPN4,17207
|
|
@@ -72,12 +74,12 @@ ngio/tables/v1/_roi_table.py,sha256=g7UpMmpf3uAfaG59WYRnimUBgiB_T1qUJRwMZpMt9cI,
|
|
|
72
74
|
ngio/transforms/__init__.py,sha256=JA0-Ui7skbXkm9ofN-AEhU1FTLutkMkwTdVD-310frQ,113
|
|
73
75
|
ngio/transforms/_zoom.py,sha256=otyE-vxFnywUJ8U4mHjat-bNG_7_jv62ckTpqDMxyVQ,550
|
|
74
76
|
ngio/utils/__init__.py,sha256=XPYh8ehC7uXNU2cFFXZAw-S3DpWpX1Yq2xGkffZv5vI,1142
|
|
75
|
-
ngio/utils/_datasets.py,sha256=
|
|
77
|
+
ngio/utils/_datasets.py,sha256=6GtxfPkjutNaeg5BHuJDBP0GudvQXHLU6mmHp_o0bGA,5650
|
|
76
78
|
ngio/utils/_errors.py,sha256=pKQ12LUjQLYE1nUawemA5h7HsgznjaSvV1n2PQU33N0,759
|
|
77
79
|
ngio/utils/_fractal_fsspec_store.py,sha256=RdcCFOgHexRKX9zZvJV5RI-5OPc7VOPS6q_IeRxm24I,1548
|
|
78
80
|
ngio/utils/_logger.py,sha256=N5W0a_xwze4blS1MolidBkTMbjTbg8GPguJZNun3mAE,1392
|
|
79
81
|
ngio/utils/_zarr_utils.py,sha256=GUOcAx02IcfrJ5tIdKu8ChtRUUaBbkkddW5jaCCYnS8,13797
|
|
80
|
-
ngio-0.4.
|
|
81
|
-
ngio-0.4.
|
|
82
|
-
ngio-0.4.
|
|
83
|
-
ngio-0.4.
|
|
82
|
+
ngio-0.4.0b1.dist-info/METADATA,sha256=cL8YlW3QecX9Stge_sYlDGi5WAIKIMjoDCvICoqmDFw,5868
|
|
83
|
+
ngio-0.4.0b1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
84
|
+
ngio-0.4.0b1.dist-info/licenses/LICENSE,sha256=UgN_a1QCeNh9rZWfz-wORQFxE3elQzLWPQaoK6N6fxQ,1502
|
|
85
|
+
ngio-0.4.0b1.dist-info/RECORD,,
|
ngio/io_pipes/_io_pipes_utils.py
DELETED
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
from collections.abc import Mapping, Sequence
|
|
2
|
-
from typing import TypeAlias
|
|
3
|
-
|
|
4
|
-
from ngio.common._dimensions import Dimensions
|
|
5
|
-
from ngio.io_pipes._ops_slices import SlicingOps, SlicingType
|
|
6
|
-
from ngio.ome_zarr_meta.ngio_specs import Axis
|
|
7
|
-
from ngio.ome_zarr_meta.ngio_specs._axes import AxesOps
|
|
8
|
-
from ngio.utils import NgioValueError
|
|
9
|
-
|
|
10
|
-
SlicingInputType: TypeAlias = slice | Sequence[int] | int | None
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def _try_to_slice(value: Sequence[int]) -> slice | tuple[int, ...]:
|
|
14
|
-
"""Try to convert a list of integers into a slice if they are contiguous.
|
|
15
|
-
|
|
16
|
-
- If the input is empty, return an empty tuple.
|
|
17
|
-
- If the input is sorted, and contains contiguous integers,
|
|
18
|
-
return a slice from the minimum to the maximum integer.
|
|
19
|
-
- Otherwise, return the input as a tuple.
|
|
20
|
-
|
|
21
|
-
This is useful for optimizing array slicing operations
|
|
22
|
-
by allowing the use of slices when possible, which can be more efficient.
|
|
23
|
-
"""
|
|
24
|
-
if not value:
|
|
25
|
-
raise NgioValueError("Ngio does not support empty sequences as slice input.")
|
|
26
|
-
|
|
27
|
-
if not all(isinstance(i, int) for i in value):
|
|
28
|
-
_value = []
|
|
29
|
-
for i in value:
|
|
30
|
-
try:
|
|
31
|
-
_value.append(int(i))
|
|
32
|
-
except Exception as e:
|
|
33
|
-
raise NgioValueError(
|
|
34
|
-
f"Invalid value {i} of type {type(i)} in sequence {value}"
|
|
35
|
-
) from e
|
|
36
|
-
value = _value
|
|
37
|
-
# If the input is not sorted, return it as a tuple
|
|
38
|
-
max_input = max(value)
|
|
39
|
-
min_input = min(value)
|
|
40
|
-
assert min_input >= 0, "Input must contain non-negative integers"
|
|
41
|
-
|
|
42
|
-
if sorted(value) == list(range(min_input, max_input + 1)):
|
|
43
|
-
return slice(min_input, max_input + 1)
|
|
44
|
-
|
|
45
|
-
return tuple(value)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _remove_channel_slicing(
|
|
49
|
-
slicing_dict: dict[str, SlicingInputType],
|
|
50
|
-
dimensions: Dimensions,
|
|
51
|
-
) -> dict[str, SlicingInputType]:
|
|
52
|
-
"""This utility function removes the channel selection from the slice kwargs.
|
|
53
|
-
|
|
54
|
-
if ignore_channel_selection is True, it will remove the channel selection
|
|
55
|
-
regardless of the dimensions. If the ignore_channel_selection is False
|
|
56
|
-
it will fail.
|
|
57
|
-
"""
|
|
58
|
-
if dimensions.is_multi_channels:
|
|
59
|
-
return slicing_dict
|
|
60
|
-
|
|
61
|
-
if "c" in slicing_dict:
|
|
62
|
-
slicing_dict.pop("c", None)
|
|
63
|
-
return slicing_dict
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _check_slicing_virtual_axes(slice_: SlicingInputType) -> bool:
|
|
67
|
-
"""Check if the slice_ is compatible with virtual axes.
|
|
68
|
-
|
|
69
|
-
Virtual axes are axes that are not present in the actual data,
|
|
70
|
-
such as time or channel axes in some datasets.
|
|
71
|
-
So the only valid slices for virtual axes are:
|
|
72
|
-
- None: means all data along the axis
|
|
73
|
-
- 0: means the first element along the axis
|
|
74
|
-
- slice([0, None], [1, None])
|
|
75
|
-
"""
|
|
76
|
-
if slice_ is None or slice_ == 0:
|
|
77
|
-
return True
|
|
78
|
-
if isinstance(slice_, slice):
|
|
79
|
-
if slice_.start is None and slice_.stop is None:
|
|
80
|
-
return True
|
|
81
|
-
if slice_.start == 0 and slice_.stop is None:
|
|
82
|
-
return True
|
|
83
|
-
if slice_.start is None and slice_.stop == 0:
|
|
84
|
-
return True
|
|
85
|
-
if slice_.start == 0 and slice_.stop == 1:
|
|
86
|
-
return True
|
|
87
|
-
if isinstance(slice_, Sequence):
|
|
88
|
-
if len(slice_) == 1 and slice_[0] == 0:
|
|
89
|
-
return True
|
|
90
|
-
return False
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def _clean_slicing_dict(
|
|
94
|
-
dimensions: Dimensions,
|
|
95
|
-
slicing_dict: Mapping[str, SlicingInputType],
|
|
96
|
-
remove_channel_selection: bool = False,
|
|
97
|
-
) -> dict[str, SlicingInputType]:
|
|
98
|
-
"""Clean the slicing dict.
|
|
99
|
-
|
|
100
|
-
This function will:
|
|
101
|
-
- Validate that the axes in the slicing_dict are present in the dimensions.
|
|
102
|
-
- Make sure that the slicing_dict uses the on-disk axis names.
|
|
103
|
-
- Check for duplicate axis names in the slicing_dict.
|
|
104
|
-
- Clean up channel selection if the dimensions
|
|
105
|
-
"""
|
|
106
|
-
clean_slicing_dict: dict[str, SlicingInputType] = {}
|
|
107
|
-
for axis_name, slice_ in slicing_dict.items():
|
|
108
|
-
axis = dimensions.axes_handler.get_axis(axis_name)
|
|
109
|
-
if axis is None:
|
|
110
|
-
# Virtual axes should be allowed to be selected
|
|
111
|
-
# Common use case is still allowing channel_selection
|
|
112
|
-
# When the zarr has not channel axis.
|
|
113
|
-
if not _check_slicing_virtual_axes(slice_):
|
|
114
|
-
raise NgioValueError(
|
|
115
|
-
f"Invalid axis selection:{axis_name}={slice_}. "
|
|
116
|
-
f"Not found on the on-disk axes {dimensions.axes}."
|
|
117
|
-
)
|
|
118
|
-
# Virtual axes can be safely ignored
|
|
119
|
-
continue
|
|
120
|
-
if axis.name in clean_slicing_dict:
|
|
121
|
-
raise NgioValueError(
|
|
122
|
-
f"Duplicate axis {axis.name} in slice kwargs. "
|
|
123
|
-
"Please provide unique axis names."
|
|
124
|
-
)
|
|
125
|
-
clean_slicing_dict[axis.name] = slice_
|
|
126
|
-
|
|
127
|
-
if remove_channel_selection:
|
|
128
|
-
clean_slicing_dict = _remove_channel_slicing(
|
|
129
|
-
slicing_dict=clean_slicing_dict, dimensions=dimensions
|
|
130
|
-
)
|
|
131
|
-
return clean_slicing_dict
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def _normalize_axes_order(
|
|
135
|
-
dimensions: Dimensions,
|
|
136
|
-
axes_order: Sequence[str],
|
|
137
|
-
) -> list[str]:
|
|
138
|
-
"""Convert axes order to the on-disk axes names.
|
|
139
|
-
|
|
140
|
-
In this way there is not unambiguity in the axes order.
|
|
141
|
-
"""
|
|
142
|
-
new_axes_order = []
|
|
143
|
-
for axis_name in axes_order:
|
|
144
|
-
axis = dimensions.axes_handler.get_axis(axis_name)
|
|
145
|
-
if axis is None:
|
|
146
|
-
new_axes_order.append(axis_name)
|
|
147
|
-
else:
|
|
148
|
-
new_axes_order.append(axis.name)
|
|
149
|
-
return new_axes_order
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def _normalize_slicing_tuple(
|
|
153
|
-
axis: Axis,
|
|
154
|
-
slicing_dict: dict[str, SlicingInputType],
|
|
155
|
-
no_axes_ops: bool,
|
|
156
|
-
axes_order: list[str],
|
|
157
|
-
) -> tuple[SlicingType, str | None]:
|
|
158
|
-
"""Normalize the slicing dict to tuple.
|
|
159
|
-
|
|
160
|
-
Since the slicing dict can contain different types of values
|
|
161
|
-
We need to normalize them to more predictable types.
|
|
162
|
-
The output types are:
|
|
163
|
-
- slice
|
|
164
|
-
- int
|
|
165
|
-
- tuple of int (for non-contiguous selection)
|
|
166
|
-
"""
|
|
167
|
-
axis_name = axis.name
|
|
168
|
-
if axis_name not in slicing_dict:
|
|
169
|
-
# If no slice is provided for the axis, use a full slice
|
|
170
|
-
return slice(None), None
|
|
171
|
-
|
|
172
|
-
value = slicing_dict[axis_name]
|
|
173
|
-
if value is None:
|
|
174
|
-
return slice(None), None
|
|
175
|
-
|
|
176
|
-
if isinstance(value, slice):
|
|
177
|
-
return value, None
|
|
178
|
-
elif isinstance(value, int):
|
|
179
|
-
# If axes ops are requested, we need to preserve the dimension
|
|
180
|
-
# When we slice because the axes ops will be applied later
|
|
181
|
-
# If no axes ops are requested, we can safely keep the integer
|
|
182
|
-
# which will remove the dimension
|
|
183
|
-
if (not no_axes_ops) or (axis_name in axes_order):
|
|
184
|
-
# Axes ops require all dimensions to be preserved
|
|
185
|
-
value = slice(value, value + 1)
|
|
186
|
-
return value, None
|
|
187
|
-
return value, axis_name
|
|
188
|
-
elif isinstance(value, Sequence):
|
|
189
|
-
# If a contiguous sequence of integers is provided,
|
|
190
|
-
# convert it to a slice for efficiency
|
|
191
|
-
# Alternatively, it will be converted to a tuple of ints
|
|
192
|
-
return _try_to_slice(value), None
|
|
193
|
-
|
|
194
|
-
raise NgioValueError(
|
|
195
|
-
f"Invalid slice definition {value} of type {type(value)}. "
|
|
196
|
-
"Allowed types are: int, slice, sequence of int or None."
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
def _build_slicing_tuple(
|
|
201
|
-
*,
|
|
202
|
-
dimensions: Dimensions,
|
|
203
|
-
slicing_dict: dict[str, SlicingInputType],
|
|
204
|
-
axes_order: list[str] | None = None,
|
|
205
|
-
no_axes_ops: bool = False,
|
|
206
|
-
remove_channel_selection: bool = False,
|
|
207
|
-
) -> tuple[tuple[SlicingType, ...] | None, list[str]]:
|
|
208
|
-
"""Assemble slices to be used to query the array."""
|
|
209
|
-
if len(slicing_dict) == 0:
|
|
210
|
-
# Skip unnecessary computation if no slicing is requested
|
|
211
|
-
return None, []
|
|
212
|
-
_axes_order = (
|
|
213
|
-
_normalize_axes_order(dimensions=dimensions, axes_order=axes_order)
|
|
214
|
-
if axes_order is not None
|
|
215
|
-
else []
|
|
216
|
-
)
|
|
217
|
-
_slicing_dict = _clean_slicing_dict(
|
|
218
|
-
dimensions=dimensions,
|
|
219
|
-
slicing_dict=slicing_dict,
|
|
220
|
-
remove_channel_selection=remove_channel_selection,
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
slicing_tuple = []
|
|
224
|
-
axes_to_remove = []
|
|
225
|
-
for axis in dimensions.axes_handler.axes:
|
|
226
|
-
sl, ax_to_remove = _normalize_slicing_tuple(
|
|
227
|
-
axis=axis,
|
|
228
|
-
slicing_dict=_slicing_dict,
|
|
229
|
-
no_axes_ops=no_axes_ops,
|
|
230
|
-
axes_order=_axes_order,
|
|
231
|
-
)
|
|
232
|
-
slicing_tuple.append(sl)
|
|
233
|
-
if ax_to_remove is not None:
|
|
234
|
-
axes_to_remove.append(ax_to_remove)
|
|
235
|
-
slicing_tuple = tuple(slicing_tuple)
|
|
236
|
-
# Slicing tuple can have only one element of type tuple
|
|
237
|
-
# If multiple tuple are present it will lead to errors
|
|
238
|
-
# when querying the array
|
|
239
|
-
if sum(isinstance(s, tuple) for s in slicing_tuple) > 1:
|
|
240
|
-
raise NgioValueError(
|
|
241
|
-
f"Invalid slicing tuple {slicing_tuple}. Ngio does not support "
|
|
242
|
-
"multiple non-contiguous selections (tuples) in the slicing tuple. "
|
|
243
|
-
"Please use slices or single integer selections instead."
|
|
244
|
-
)
|
|
245
|
-
return slicing_tuple, axes_to_remove
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def _build_axes_ops(
|
|
249
|
-
*,
|
|
250
|
-
axes_order: Sequence[str] | None,
|
|
251
|
-
dimensions: Dimensions,
|
|
252
|
-
) -> tuple[list[str] | None, AxesOps]:
|
|
253
|
-
if axes_order is None:
|
|
254
|
-
return None, AxesOps(
|
|
255
|
-
on_disk_axes=dimensions.axes_handler.axes_names,
|
|
256
|
-
in_memory_axes=dimensions.axes_handler.axes_names,
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
axes_order = _normalize_axes_order(dimensions=dimensions, axes_order=axes_order)
|
|
260
|
-
axes_ops = dimensions.axes_handler.get_axes_ops(axes_order)
|
|
261
|
-
return axes_order, axes_ops
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
def setup_io_pipe(
|
|
265
|
-
*,
|
|
266
|
-
dimensions: Dimensions,
|
|
267
|
-
slicing_dict: dict[str, SlicingInputType] | None = None,
|
|
268
|
-
axes_order: Sequence[str] | None = None,
|
|
269
|
-
remove_channel_selection: bool = False,
|
|
270
|
-
) -> tuple[SlicingOps, AxesOps]:
|
|
271
|
-
"""Setup the slicing tuple and axes ops for an IO pipe."""
|
|
272
|
-
slicing_dict = slicing_dict or {}
|
|
273
|
-
axes_order, axes_ops = _build_axes_ops(
|
|
274
|
-
axes_order=axes_order,
|
|
275
|
-
dimensions=dimensions,
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
slicing_tuple, axes_to_remove = _build_slicing_tuple(
|
|
279
|
-
dimensions=dimensions,
|
|
280
|
-
slicing_dict=slicing_dict,
|
|
281
|
-
axes_order=axes_order,
|
|
282
|
-
no_axes_ops=axes_ops.is_no_op,
|
|
283
|
-
remove_channel_selection=remove_channel_selection,
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
if axes_to_remove:
|
|
287
|
-
in_memory_axes = tuple(
|
|
288
|
-
ax for ax in axes_ops.in_memory_axes if ax not in axes_to_remove
|
|
289
|
-
)
|
|
290
|
-
axes_ops = AxesOps(
|
|
291
|
-
on_disk_axes=axes_ops.on_disk_axes,
|
|
292
|
-
in_memory_axes=in_memory_axes,
|
|
293
|
-
)
|
|
294
|
-
slicing_ops = SlicingOps(
|
|
295
|
-
on_disk_axes=dimensions.axes_handler.axes_names,
|
|
296
|
-
slicing_tuple=slicing_tuple,
|
|
297
|
-
on_disk_shape=dimensions.shape,
|
|
298
|
-
)
|
|
299
|
-
return slicing_ops, axes_ops
|
|
File without changes
|
|
File without changes
|