ngio 0.4.0a4__py3-none-any.whl → 0.4.1__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.
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngio
3
- Version: 0.4.0a4
3
+ Version: 0.4.1
4
4
  Summary: Next Generation file format IO
5
5
  Project-URL: homepage, https://github.com/BioVisionCenter/ngio
6
6
  Project-URL: repository, https://github.com/BioVisionCenter/ngio
@@ -28,7 +28,6 @@ Requires-Dist: pooch
28
28
  Requires-Dist: pyarrow
29
29
  Requires-Dist: pydantic
30
30
  Requires-Dist: requests
31
- Requires-Dist: xarray
32
31
  Requires-Dist: zarr<3
33
32
  Provides-Extra: dev
34
33
  Requires-Dist: devtools; extra == 'dev'
@@ -83,20 +82,22 @@ Ngio's mission is to streamline working with OME-Zarr files by providing a simpl
83
82
 
84
83
  ## Key Features
85
84
 
86
- ### 📊 Simple Object-Based API
85
+ ### 🔍 Simple Object-Based API
87
86
 
88
87
  - Easily open, explore, and manipulate OME-Zarr images and HCS plates
89
88
  - Create and derive new images and labels with minimal boilerplate code
90
89
 
91
- ### 🔍 Rich Tables and Regions of Interest (ROI) Support
90
+ ### 📊 Rich Tables and Regions of Interest (ROI) Support
92
91
 
92
+ - Tight integration with [tabular data](https://biovisioncenter.github.io/ngio/stable/table_specs/overview/)
93
93
  - Extract and analyze specific regions of interest
94
- - Tight integration with [Tabular Data](https://BioVisionCenter.github.io/ngio/stable/table_specs/overview/)
94
+ - Store measurements and other metadata in the OME-Zarr container
95
+ - Extensible & modular allowing users to define custom table schemas and on disk serialization
95
96
 
96
- ### 🔄 Scalable Data Processing (Coming Soon)
97
+ ### 🔄 Scalable Data Processing
97
98
 
98
- - Powerful iterators for processing data at scale
99
- - Efficient memory management for large datasets
99
+ - Powerful iterators for building scalable and generalizable image processing pipelines
100
+ - Extensible mapping mechanism for custom parallelization strategies
100
101
 
101
102
  ## Installation
102
103
 
@@ -114,23 +115,22 @@ Currently, ngio only supports OME-Zarr v0.4. Support for version 0.5 and higher
114
115
 
115
116
  ## Development Status
116
117
 
117
- !!! warning
118
- Ngio is under active development and is not yet stable. The API is subject to change, and bugs and breaking changes are expected.
119
- We follow [Semantic Versioning](https://semver.org/). Which means for 0.x releases potentially breaking changes can be introduced in minor releases.
118
+ Ngio is under active development and is not yet stable. The API is subject to change, and bugs and breaking changes are expected.
119
+ We follow [Semantic Versioning](https://semver.org/). Which means for 0.x releases potentially breaking changes can be introduced in minor releases.
120
120
 
121
121
  ### Available Features
122
122
 
123
123
  - ✅ OME-Zarr metadata handling and validation
124
124
  - ✅ Image and label access across pyramid levels
125
125
  - ✅ ROI and table support
126
+ - ✅ Image processing iterators
126
127
  - ✅ Streaming from remote sources
127
128
  - ✅ Documentation and examples
128
129
 
129
130
  ### Upcoming Features
130
131
 
131
- - Advanced image processing iterators
132
- - Parallel processing capabilities
133
- - Support for OME-Zarr v0.5 and Zarr v3
132
+ - Support for OME-Zarr v0.5 and Zarr v3 (via `zarr-python` v3)
133
+ - Enhanced performance optimizations (parallel iterators, optimized io strategies)
134
134
 
135
135
  ## Contributors
136
136
 
@@ -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=zl9mPiYYYxr_s1MT0jQiUhpeG5qAc0l3u-IHgvcJ1XY,10877
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=gW-_ouxoHrcG5ox6Kl8NOyMhu8XJFKm7xu8w03O6xds,10185
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=on_sUvuRhHBb7-r5u3Ojvu6K9FGjUOrWGUkLQ4aRzbs,556
11
- ngio/experimental/iterators/_abstract_iterator.py,sha256=4YNnL8OZ01KEG0Ga6UuETmXy4TV8oyhYC0ZlB_WLiBA,5537
12
- ngio/experimental/iterators/_feature.py,sha256=5opX2o9iJazOOro0hEAF0oF7gJtFzmprRd2vusP3lcI,5874
13
- ngio/experimental/iterators/_image_processing.py,sha256=Dt8mgrlrehwIaHpxNh9e7GSPRme7vWwStdsF9oSwR7E,6082
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=M0SVSyBBJDc_pYzMOJJiUmeULk9QXoKe4Lg1EwMz4H8,10276
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=0o1wiNnzHXcSJHAJPd7cMdYatS_B8wT2bcWMzfyFd6A,16841
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=TB-xCGeYRxPj8pzREjj0tkGJz6EqjiUoDLw148ocWi4,32966
23
- ngio/images/_label.py,sha256=ZQYtsECM04oPrXoiGAWvfIRz7CtsCTPd5ktUMCi-5nA,11712
24
- ngio/images/_masked_image.py,sha256=0RFW5YudH8cfjLMa263PenBmok2FhYm-eNtgoQkz2Es,18731
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=HkqzC1gwGLFzIcHMkgEQ8-rh7w6LDEfvEwkwli-0IHE,1179
28
- ngio/io_pipes/_io_pipes.py,sha256=loYbsFv0dbpngkd1HYsJ5s9HDcP9lb-QJQBwymSJyg8,8568
29
- ngio/io_pipes/_io_pipes_masked.py,sha256=jRDS7m4b6Hl7AdxRvg9Dz3nOIl9nypoktj43FHEuBUI,16943
30
- ngio/io_pipes/_io_pipes_roi.py,sha256=Rgvqy9C-fYEEmg2yoDROE3xiRGhjo5lH5_B8gXvt2dg,4678
31
- ngio/io_pipes/_io_pipes_utils.py,sha256=r_c1N48L01McTLPRgezW9KGFes3JHQYYkpzOXnQElQ0,10505
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=BHOSFBGTKF32f4kqj8uu_NpiyLpR0zzBy9HN01OPGSs,4014
34
- ngio/io_pipes/_ops_slices.py,sha256=9o2HqsiEfeBVcTcTpqmLEKrV-UOkILHdvii-nTXP_0w,7887
35
- ngio/io_pipes/_ops_transforms.py,sha256=HsxLLGjQ8kx9HpkikYHhXC4s4DD1T56GJ4StjO7FCSk,2946
36
- ngio/io_pipes/_zoom_transform.py,sha256=Au2nqNfODQWEpvc12OmYVmP-Uso7fEc-14hTK_GsGi4,6723
37
- ngio/ome_zarr_meta/__init__.py,sha256=A5pyhdyLpwRyXXB81tMHFu36rq9-QzAzG0htRqBMpTo,1293
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=WBY1tO6_Qhf8FaDujfTdipuuqFf7PSi204wx5VKKs88,6884
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=PXTmXX9aeOlM9FLL-BXzW8v9ByezL_X7EDNx2zoBYm4,1726
40
- ngio/ome_zarr_meta/ngio_specs/_axes.py,sha256=R2HIL31FreeqawvXQbhRRu3lPxYPviFOSnCOses6Dhw,21134
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=2g-Neg78dNcqyDz39QQw-Ifp9GITHjVHisdqgvvDNDE,5475
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.0a4.dist-info/METADATA,sha256=Xg_Fe1eqlYK8VSvuZO2eRctss4pRDD1hjwgT4v_grHs,5868
81
- ngio-0.4.0a4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
82
- ngio-0.4.0a4.dist-info/licenses/LICENSE,sha256=UgN_a1QCeNh9rZWfz-wORQFxE3elQzLWPQaoK6N6fxQ,1502
83
- ngio-0.4.0a4.dist-info/RECORD,,
82
+ ngio-0.4.1.dist-info/METADATA,sha256=nx7nG1l_Q24dbiLVCerWU4FYdJr19UQfBW6QGR0Bm2M,6095
83
+ ngio-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
84
+ ngio-0.4.1.dist-info/licenses/LICENSE,sha256=UgN_a1QCeNh9rZWfz-wORQFxE3elQzLWPQaoK6N6fxQ,1502
85
+ ngio-0.4.1.dist-info/RECORD,,
@@ -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