ngio 0.4.8__py3-none-any.whl → 0.5.0__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/__init__.py +5 -2
- ngio/common/__init__.py +11 -6
- ngio/common/_masking_roi.py +34 -54
- ngio/common/_pyramid.py +322 -75
- ngio/common/_roi.py +258 -330
- ngio/experimental/iterators/_feature.py +3 -3
- ngio/experimental/iterators/_rois_utils.py +10 -11
- ngio/hcs/_plate.py +192 -136
- ngio/images/_abstract_image.py +539 -35
- ngio/images/_create_synt_container.py +45 -47
- ngio/images/_create_utils.py +406 -0
- ngio/images/_image.py +524 -248
- ngio/images/_label.py +257 -180
- ngio/images/_masked_image.py +2 -2
- ngio/images/_ome_zarr_container.py +658 -255
- ngio/io_pipes/_io_pipes.py +9 -9
- ngio/io_pipes/_io_pipes_masked.py +7 -7
- ngio/io_pipes/_io_pipes_roi.py +6 -6
- ngio/io_pipes/_io_pipes_types.py +3 -3
- ngio/io_pipes/_match_shape.py +6 -8
- ngio/io_pipes/_ops_slices_utils.py +8 -5
- ngio/ome_zarr_meta/__init__.py +29 -18
- ngio/ome_zarr_meta/_meta_handlers.py +402 -689
- ngio/ome_zarr_meta/ngio_specs/__init__.py +4 -0
- ngio/ome_zarr_meta/ngio_specs/_axes.py +152 -51
- ngio/ome_zarr_meta/ngio_specs/_dataset.py +13 -22
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +129 -91
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +69 -69
- ngio/ome_zarr_meta/v04/__init__.py +5 -1
- ngio/ome_zarr_meta/v04/{_v04_spec_utils.py → _v04_spec.py} +55 -86
- ngio/ome_zarr_meta/v05/__init__.py +27 -0
- ngio/ome_zarr_meta/v05/_custom_models.py +18 -0
- ngio/ome_zarr_meta/v05/_v05_spec.py +495 -0
- ngio/resources/__init__.py +1 -1
- ngio/resources/resource_model.py +1 -1
- ngio/tables/_tables_container.py +82 -24
- ngio/tables/backends/_abstract_backend.py +7 -0
- ngio/tables/backends/_anndata.py +60 -7
- ngio/tables/backends/_anndata_utils.py +2 -4
- ngio/tables/backends/_csv.py +3 -19
- ngio/tables/backends/_json.py +10 -13
- ngio/tables/backends/_parquet.py +3 -31
- ngio/tables/backends/_py_arrow_backends.py +222 -0
- ngio/tables/backends/_utils.py +1 -1
- ngio/tables/v1/_roi_table.py +41 -24
- ngio/utils/__init__.py +8 -12
- ngio/utils/_cache.py +48 -0
- ngio/utils/_zarr_utils.py +354 -236
- {ngio-0.4.8.dist-info → ngio-0.5.0.dist-info}/METADATA +12 -5
- ngio-0.5.0.dist-info/RECORD +88 -0
- ngio/images/_create.py +0 -276
- ngio/tables/backends/_non_zarr_backends.py +0 -196
- ngio/utils/_logger.py +0 -50
- ngio-0.4.8.dist-info/RECORD +0 -85
- {ngio-0.4.8.dist-info → ngio-0.5.0.dist-info}/WHEEL +0 -0
- {ngio-0.4.8.dist-info → ngio-0.5.0.dist-info}/licenses/LICENSE +0 -0
ngio/images/_image.py
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
"""Generic class to handle Image-like data in a OME-NGFF file."""
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
from
|
|
3
|
+
import warnings
|
|
4
|
+
from collections.abc import Mapping, Sequence
|
|
5
|
+
from typing import Any, Literal
|
|
5
6
|
|
|
6
7
|
import dask.array as da
|
|
7
8
|
import numpy as np
|
|
8
9
|
from pydantic import BaseModel, model_validator
|
|
9
|
-
from zarr.
|
|
10
|
+
from zarr.core.array import CompressorLike
|
|
10
11
|
|
|
11
12
|
from ngio.common import (
|
|
12
13
|
Dimensions,
|
|
13
14
|
InterpolationOrder,
|
|
14
15
|
Roi,
|
|
15
|
-
RoiPixels,
|
|
16
16
|
)
|
|
17
|
-
from ngio.
|
|
18
|
-
from ngio.images.
|
|
17
|
+
from ngio.common._pyramid import ChunksLike, ShardsLike
|
|
18
|
+
from ngio.images._abstract_image import AbstractImage, abstract_derive
|
|
19
19
|
from ngio.io_pipes import (
|
|
20
20
|
SlicingInputType,
|
|
21
21
|
TransformProtocol,
|
|
@@ -24,19 +24,19 @@ from ngio.ome_zarr_meta import (
|
|
|
24
24
|
ImageMetaHandler,
|
|
25
25
|
NgioImageMeta,
|
|
26
26
|
PixelSize,
|
|
27
|
-
find_image_meta_handler,
|
|
28
27
|
)
|
|
29
28
|
from ngio.ome_zarr_meta.ngio_specs import (
|
|
30
29
|
Channel,
|
|
31
30
|
ChannelsMeta,
|
|
32
|
-
ChannelVisualisation,
|
|
33
31
|
DefaultSpaceUnit,
|
|
34
32
|
DefaultTimeUnit,
|
|
33
|
+
NgffVersions,
|
|
35
34
|
SpaceUnits,
|
|
36
35
|
TimeUnits,
|
|
37
36
|
)
|
|
37
|
+
from ngio.ome_zarr_meta.ngio_specs._axes import AxesSetup
|
|
38
38
|
from ngio.utils import (
|
|
39
|
-
|
|
39
|
+
NgioValueError,
|
|
40
40
|
StoreOrGroup,
|
|
41
41
|
ZarrGroupHandler,
|
|
42
42
|
)
|
|
@@ -47,7 +47,7 @@ class ChannelSelectionModel(BaseModel):
|
|
|
47
47
|
|
|
48
48
|
This model is used to select a channel by label, wavelength ID, or index.
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Properties:
|
|
51
51
|
identifier (str): Unique identifier for the channel.
|
|
52
52
|
This can be a channel label, wavelength ID, or index.
|
|
53
53
|
mode (Literal["label", "wavelength_id", "index"]): Specifies how to
|
|
@@ -88,7 +88,7 @@ def _check_channel_meta(meta: NgioImageMeta, dimension: Dimensions) -> ChannelsM
|
|
|
88
88
|
return ChannelsMeta.default_init(labels=c_dim)
|
|
89
89
|
|
|
90
90
|
if len(meta.channels_meta.channels) != c_dim:
|
|
91
|
-
raise
|
|
91
|
+
raise NgioValueError(
|
|
92
92
|
"The number of channels does not match the image. "
|
|
93
93
|
f"Expected {len(meta.channels_meta.channels)} channels, got {c_dim}."
|
|
94
94
|
)
|
|
@@ -96,7 +96,7 @@ def _check_channel_meta(meta: NgioImageMeta, dimension: Dimensions) -> ChannelsM
|
|
|
96
96
|
return meta.channels_meta
|
|
97
97
|
|
|
98
98
|
|
|
99
|
-
class Image(AbstractImage
|
|
99
|
+
class Image(AbstractImage):
|
|
100
100
|
"""A class to handle a single image (or level) in an OME-Zarr image.
|
|
101
101
|
|
|
102
102
|
This class is meant to be subclassed by specific image types.
|
|
@@ -106,7 +106,7 @@ class Image(AbstractImage[ImageMetaHandler]):
|
|
|
106
106
|
self,
|
|
107
107
|
group_handler: ZarrGroupHandler,
|
|
108
108
|
path: str,
|
|
109
|
-
meta_handler: ImageMetaHandler
|
|
109
|
+
meta_handler: ImageMetaHandler,
|
|
110
110
|
) -> None:
|
|
111
111
|
"""Initialize the Image at a single level.
|
|
112
112
|
|
|
@@ -116,16 +116,22 @@ class Image(AbstractImage[ImageMetaHandler]):
|
|
|
116
116
|
meta_handler: The image metadata handler.
|
|
117
117
|
|
|
118
118
|
"""
|
|
119
|
-
if meta_handler is None:
|
|
120
|
-
meta_handler = find_image_meta_handler(group_handler)
|
|
121
119
|
super().__init__(
|
|
122
120
|
group_handler=group_handler, path=path, meta_handler=meta_handler
|
|
123
121
|
)
|
|
124
122
|
|
|
123
|
+
@property
|
|
124
|
+
def meta_handler(self) -> ImageMetaHandler:
|
|
125
|
+
"""Return the metadata handler."""
|
|
126
|
+
assert isinstance(self._meta_handler, ImageMetaHandler)
|
|
127
|
+
return self._meta_handler
|
|
128
|
+
|
|
125
129
|
@property
|
|
126
130
|
def meta(self) -> NgioImageMeta:
|
|
127
131
|
"""Return the metadata."""
|
|
128
|
-
|
|
132
|
+
meta = self.meta_handler.get_meta()
|
|
133
|
+
assert isinstance(meta, NgioImageMeta)
|
|
134
|
+
return meta
|
|
129
135
|
|
|
130
136
|
@property
|
|
131
137
|
def channels_meta(self) -> ChannelsMeta:
|
|
@@ -185,7 +191,7 @@ class Image(AbstractImage[ImageMetaHandler]):
|
|
|
185
191
|
|
|
186
192
|
def get_roi_as_numpy(
|
|
187
193
|
self,
|
|
188
|
-
roi: Roi
|
|
194
|
+
roi: Roi,
|
|
189
195
|
channel_selection: ChannelSlicingInputType = None,
|
|
190
196
|
axes_order: Sequence[str] | None = None,
|
|
191
197
|
transforms: Sequence[TransformProtocol] | None = None,
|
|
@@ -239,7 +245,7 @@ class Image(AbstractImage[ImageMetaHandler]):
|
|
|
239
245
|
|
|
240
246
|
def get_roi_as_dask(
|
|
241
247
|
self,
|
|
242
|
-
roi: Roi
|
|
248
|
+
roi: Roi,
|
|
243
249
|
channel_selection: ChannelSlicingInputType = None,
|
|
244
250
|
axes_order: Sequence[str] | None = None,
|
|
245
251
|
transforms: Sequence[TransformProtocol] | None = None,
|
|
@@ -296,7 +302,7 @@ class Image(AbstractImage[ImageMetaHandler]):
|
|
|
296
302
|
|
|
297
303
|
def get_roi(
|
|
298
304
|
self,
|
|
299
|
-
roi: Roi
|
|
305
|
+
roi: Roi,
|
|
300
306
|
channel_selection: ChannelSlicingInputType = None,
|
|
301
307
|
axes_order: Sequence[str] | None = None,
|
|
302
308
|
transforms: Sequence[TransformProtocol] | None = None,
|
|
@@ -356,7 +362,7 @@ class Image(AbstractImage[ImageMetaHandler]):
|
|
|
356
362
|
|
|
357
363
|
def set_roi(
|
|
358
364
|
self,
|
|
359
|
-
roi: Roi
|
|
365
|
+
roi: Roi,
|
|
360
366
|
patch: np.ndarray | da.Array,
|
|
361
367
|
channel_selection: ChannelSlicingInputType = None,
|
|
362
368
|
axes_order: Sequence[str] | None = None,
|
|
@@ -394,67 +400,135 @@ class Image(AbstractImage[ImageMetaHandler]):
|
|
|
394
400
|
|
|
395
401
|
|
|
396
402
|
class ImagesContainer:
|
|
397
|
-
"""A class to handle the /
|
|
403
|
+
"""A class to handle the /images group in an OME-NGFF file."""
|
|
398
404
|
|
|
399
|
-
def __init__(
|
|
400
|
-
|
|
405
|
+
def __init__(
|
|
406
|
+
self,
|
|
407
|
+
group_handler: ZarrGroupHandler,
|
|
408
|
+
axes_setup: AxesSetup | None,
|
|
409
|
+
version: NgffVersions | None = None,
|
|
410
|
+
validate_paths: bool = True,
|
|
411
|
+
) -> None:
|
|
412
|
+
"""Initialize the ImagesContainer."""
|
|
401
413
|
self._group_handler = group_handler
|
|
402
|
-
self._meta_handler =
|
|
414
|
+
self._meta_handler = ImageMetaHandler(
|
|
415
|
+
group_handler=group_handler, axes_setup=axes_setup, version=version
|
|
416
|
+
)
|
|
417
|
+
if validate_paths:
|
|
418
|
+
for level_path in self._meta_handler.get_meta().paths:
|
|
419
|
+
self.get(path=level_path)
|
|
403
420
|
|
|
404
421
|
@property
|
|
405
422
|
def meta(self) -> NgioImageMeta:
|
|
406
423
|
"""Return the metadata."""
|
|
407
|
-
return self._meta_handler.
|
|
424
|
+
return self._meta_handler.get_meta()
|
|
425
|
+
|
|
426
|
+
@property
|
|
427
|
+
def channels_meta(self) -> ChannelsMeta:
|
|
428
|
+
"""Return the channels metadata."""
|
|
429
|
+
return self.get().channels_meta
|
|
430
|
+
|
|
431
|
+
@property
|
|
432
|
+
def axes_setup(self) -> AxesSetup:
|
|
433
|
+
"""Return the axes setup."""
|
|
434
|
+
return self.meta.axes_handler.axes_setup
|
|
435
|
+
|
|
436
|
+
@property
|
|
437
|
+
def level_paths(self) -> list[str]:
|
|
438
|
+
"""Return the paths of the levels in the image."""
|
|
439
|
+
return self.meta.paths
|
|
440
|
+
|
|
441
|
+
@property
|
|
442
|
+
def levels_paths(self) -> list[str]:
|
|
443
|
+
"""Deprecated: use 'level_paths' instead."""
|
|
444
|
+
warnings.warn(
|
|
445
|
+
"'levels_paths' is deprecated and will be removed in ngio=0.6. "
|
|
446
|
+
"Please use 'level_paths' instead.",
|
|
447
|
+
DeprecationWarning,
|
|
448
|
+
stacklevel=2,
|
|
449
|
+
)
|
|
450
|
+
return self.level_paths
|
|
408
451
|
|
|
409
452
|
@property
|
|
410
453
|
def levels(self) -> int:
|
|
411
454
|
"""Return the number of levels in the image."""
|
|
412
|
-
return self.
|
|
455
|
+
return self.meta.levels
|
|
413
456
|
|
|
414
457
|
@property
|
|
415
|
-
def
|
|
416
|
-
"""Return
|
|
417
|
-
return self.
|
|
458
|
+
def is_3d(self) -> bool:
|
|
459
|
+
"""Return True if the image is 3D."""
|
|
460
|
+
return self.get().is_3d
|
|
418
461
|
|
|
419
462
|
@property
|
|
420
|
-
def
|
|
421
|
-
"""Return the
|
|
422
|
-
|
|
423
|
-
|
|
463
|
+
def is_2d(self) -> bool:
|
|
464
|
+
"""Return True if the image is 2D."""
|
|
465
|
+
return self.get().is_2d
|
|
466
|
+
|
|
467
|
+
@property
|
|
468
|
+
def is_time_series(self) -> bool:
|
|
469
|
+
"""Return True if the image is a time series."""
|
|
470
|
+
return self.get().is_time_series
|
|
471
|
+
|
|
472
|
+
@property
|
|
473
|
+
def is_2d_time_series(self) -> bool:
|
|
474
|
+
"""Return True if the image is a 2D time series."""
|
|
475
|
+
return self.get().is_2d_time_series
|
|
476
|
+
|
|
477
|
+
@property
|
|
478
|
+
def is_3d_time_series(self) -> bool:
|
|
479
|
+
"""Return True if the image is a 3D time series."""
|
|
480
|
+
return self.get().is_3d_time_series
|
|
481
|
+
|
|
482
|
+
@property
|
|
483
|
+
def is_multi_channels(self) -> bool:
|
|
484
|
+
"""Return True if the image is multichannel."""
|
|
485
|
+
return self.get().is_multi_channels
|
|
486
|
+
|
|
487
|
+
@property
|
|
488
|
+
def space_unit(self) -> str | None:
|
|
489
|
+
"""Return the space unit of the image."""
|
|
490
|
+
return self.meta.space_unit
|
|
491
|
+
|
|
492
|
+
@property
|
|
493
|
+
def time_unit(self) -> str | None:
|
|
494
|
+
"""Return the time unit of the image."""
|
|
495
|
+
return self.meta.time_unit
|
|
424
496
|
|
|
425
497
|
@property
|
|
426
498
|
def channel_labels(self) -> list[str]:
|
|
427
499
|
"""Return the channels of the image."""
|
|
428
|
-
|
|
429
|
-
return image.channel_labels
|
|
500
|
+
return self.get().channel_labels
|
|
430
501
|
|
|
431
502
|
@property
|
|
432
503
|
def wavelength_ids(self) -> list[str | None]:
|
|
433
|
-
"""Return the wavelength of the image."""
|
|
434
|
-
|
|
435
|
-
|
|
504
|
+
"""Return the list of wavelength of the image."""
|
|
505
|
+
return self.get().wavelength_ids
|
|
506
|
+
|
|
507
|
+
@property
|
|
508
|
+
def num_channels(self) -> int:
|
|
509
|
+
"""Return the number of channels."""
|
|
510
|
+
return self.get().num_channels
|
|
436
511
|
|
|
437
512
|
def get_channel_idx(
|
|
438
513
|
self, channel_label: str | None = None, wavelength_id: str | None = None
|
|
439
514
|
) -> int:
|
|
440
|
-
"""Get the index of a channel by label or wavelength ID.
|
|
441
|
-
|
|
442
|
-
Args:
|
|
443
|
-
channel_label (str | None): The label of the channel.
|
|
444
|
-
If None a wavelength ID must be provided.
|
|
445
|
-
wavelength_id (str | None): The wavelength ID of the channel.
|
|
446
|
-
If None a channel label must be provided.
|
|
447
|
-
|
|
448
|
-
Returns:
|
|
449
|
-
int: The index of the channel.
|
|
450
|
-
|
|
451
|
-
"""
|
|
452
|
-
image = self.get()
|
|
453
|
-
return image.get_channel_idx(
|
|
515
|
+
"""Get the index of a channel by its label or wavelength ID."""
|
|
516
|
+
return self.channels_meta.get_channel_idx(
|
|
454
517
|
channel_label=channel_label, wavelength_id=wavelength_id
|
|
455
518
|
)
|
|
456
519
|
|
|
457
|
-
def
|
|
520
|
+
def _set_channel_meta(
|
|
521
|
+
self,
|
|
522
|
+
channels_meta: ChannelsMeta | None = None,
|
|
523
|
+
) -> None:
|
|
524
|
+
"""Set the channels metadata."""
|
|
525
|
+
if channels_meta is None:
|
|
526
|
+
channels_meta = ChannelsMeta.default_init(labels=self.num_channels)
|
|
527
|
+
meta = self.meta
|
|
528
|
+
meta.set_channels_meta(channels_meta)
|
|
529
|
+
self._meta_handler.update_meta(meta)
|
|
530
|
+
|
|
531
|
+
def _set_channel_meta_legacy(
|
|
458
532
|
self,
|
|
459
533
|
labels: Sequence[str | None] | int | None = None,
|
|
460
534
|
wavelength_id: Sequence[str | None] | None = None,
|
|
@@ -490,32 +564,22 @@ class ImagesContainer:
|
|
|
490
564
|
ref_image = self.get(path=low_res_dataset.path)
|
|
491
565
|
|
|
492
566
|
if start is not None and end is None:
|
|
493
|
-
raise
|
|
494
|
-
"If start is provided, end must be provided as well."
|
|
495
|
-
)
|
|
567
|
+
raise NgioValueError("If start is provided, end must be provided as well.")
|
|
496
568
|
if end is not None and start is None:
|
|
497
|
-
raise
|
|
498
|
-
"If end is provided, start must be provided as well."
|
|
499
|
-
)
|
|
569
|
+
raise NgioValueError("If end is provided, start must be provided as well.")
|
|
500
570
|
|
|
501
571
|
if start is not None and percentiles is not None:
|
|
502
|
-
raise
|
|
572
|
+
raise NgioValueError(
|
|
503
573
|
"If start and end are provided, percentiles must be None."
|
|
504
574
|
)
|
|
505
575
|
|
|
506
|
-
if percentiles is not None:
|
|
507
|
-
start, end = compute_image_percentile(
|
|
508
|
-
ref_image,
|
|
509
|
-
start_percentile=percentiles[0],
|
|
510
|
-
end_percentile=percentiles[1],
|
|
511
|
-
)
|
|
512
576
|
elif start is not None and end is not None:
|
|
513
577
|
if len(start) != len(end):
|
|
514
|
-
raise
|
|
578
|
+
raise NgioValueError(
|
|
515
579
|
"The start and end lists must have the same length."
|
|
516
580
|
)
|
|
517
581
|
if len(start) != self.num_channels:
|
|
518
|
-
raise
|
|
582
|
+
raise NgioValueError(
|
|
519
583
|
"The start and end lists must have the same length as "
|
|
520
584
|
"the number of channels."
|
|
521
585
|
)
|
|
@@ -539,44 +603,212 @@ class ImagesContainer:
|
|
|
539
603
|
data_type=ref_image.dtype,
|
|
540
604
|
**omero_kwargs,
|
|
541
605
|
)
|
|
606
|
+
self._set_channel_meta(channel_meta)
|
|
607
|
+
if percentiles is not None:
|
|
608
|
+
self.set_channel_windows_with_percentiles(percentiles=percentiles)
|
|
542
609
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
610
|
+
def set_channel_meta(
|
|
611
|
+
self,
|
|
612
|
+
channel_meta: ChannelsMeta | None = None,
|
|
613
|
+
labels: Sequence[str | None] | int | None = None,
|
|
614
|
+
wavelength_id: Sequence[str | None] | None = None,
|
|
615
|
+
start: Sequence[float | None] | None = None,
|
|
616
|
+
end: Sequence[float | None] | None = None,
|
|
617
|
+
percentiles: tuple[float, float] | None = None,
|
|
618
|
+
colors: Sequence[str | None] | None = None,
|
|
619
|
+
active: Sequence[bool | None] | None = None,
|
|
620
|
+
**omero_kwargs: dict,
|
|
621
|
+
) -> None:
|
|
622
|
+
"""Create a ChannelsMeta object with the default unit.
|
|
623
|
+
|
|
624
|
+
Args:
|
|
625
|
+
channel_meta (ChannelsMeta | None): The channels metadata to set.
|
|
626
|
+
If none, it will fall back to the deprecated parameters.
|
|
627
|
+
labels(Sequence[str | None] | int): Deprecated. The list of channels names
|
|
628
|
+
in the image. If an integer is provided, the channels will
|
|
629
|
+
be named "channel_i".
|
|
630
|
+
wavelength_id(Sequence[str | None]): Deprecated. The wavelength ID of the
|
|
631
|
+
channel. If None, the wavelength ID will be the same as
|
|
632
|
+
the channel name.
|
|
633
|
+
start(Sequence[float | None]): Deprecated. The start value for each channel.
|
|
634
|
+
If None, the start value will be computed from the image.
|
|
635
|
+
end(Sequence[float | None]): Deprecated. The end value for each channel.
|
|
636
|
+
If None, the end value will be computed from the image.
|
|
637
|
+
percentiles(tuple[float, float] | None): Deprecated. The start and end
|
|
638
|
+
percentiles for each channel. If None, the percentiles will
|
|
639
|
+
not be computed.
|
|
640
|
+
colors(Sequence[str | None]): Deprecated. The list of colors for the
|
|
641
|
+
channels. If None, the colors will be random.
|
|
642
|
+
active (Sequence[bool | None]): Deprecated. Whether the channel should
|
|
643
|
+
be shown by default.
|
|
644
|
+
omero_kwargs(dict): Deprecated. Extra fields to store in the omero
|
|
645
|
+
attributes.
|
|
646
|
+
"""
|
|
647
|
+
_is_legacy = any(
|
|
648
|
+
param is not None
|
|
649
|
+
for param in [
|
|
650
|
+
labels,
|
|
651
|
+
wavelength_id,
|
|
652
|
+
start,
|
|
653
|
+
end,
|
|
654
|
+
percentiles,
|
|
655
|
+
colors,
|
|
656
|
+
active,
|
|
657
|
+
]
|
|
658
|
+
)
|
|
659
|
+
if _is_legacy:
|
|
660
|
+
warnings.warn(
|
|
661
|
+
"The following parameters are deprecated and will be removed in "
|
|
662
|
+
"ngio=0.6: labels, wavelength_id, start, end, percentiles, "
|
|
663
|
+
"colors, active, omero_kwargs. Please use the "
|
|
664
|
+
"'channel_meta' parameter instead.",
|
|
665
|
+
DeprecationWarning,
|
|
666
|
+
stacklevel=2,
|
|
667
|
+
)
|
|
668
|
+
self._set_channel_meta_legacy(
|
|
669
|
+
labels=labels,
|
|
670
|
+
wavelength_id=wavelength_id,
|
|
671
|
+
start=start,
|
|
672
|
+
end=end,
|
|
673
|
+
percentiles=percentiles,
|
|
674
|
+
colors=colors,
|
|
675
|
+
active=active,
|
|
676
|
+
**omero_kwargs,
|
|
677
|
+
)
|
|
678
|
+
return None
|
|
679
|
+
self._set_channel_meta(channel_meta)
|
|
680
|
+
|
|
681
|
+
def set_channel_labels(
|
|
682
|
+
self,
|
|
683
|
+
labels: Sequence[str],
|
|
684
|
+
) -> None:
|
|
685
|
+
"""Update the labels of the channels.
|
|
686
|
+
|
|
687
|
+
Args:
|
|
688
|
+
labels (Sequence[str]): The new labels for the channels.
|
|
689
|
+
"""
|
|
690
|
+
channels_meta = self.channels_meta
|
|
691
|
+
if len(labels) != len(channels_meta.channels):
|
|
692
|
+
raise NgioValueError(
|
|
693
|
+
"The number of labels must match the number of channels."
|
|
694
|
+
)
|
|
695
|
+
new_channels = []
|
|
696
|
+
for label, ch in zip(labels, channels_meta.channels, strict=True):
|
|
697
|
+
channel = ch.model_copy(update={"label": label})
|
|
698
|
+
new_channels.append(channel)
|
|
699
|
+
new_meta = channels_meta.model_copy(update={"channels": new_channels})
|
|
700
|
+
self._set_channel_meta(new_meta)
|
|
701
|
+
|
|
702
|
+
def set_channel_colors(
|
|
703
|
+
self,
|
|
704
|
+
colors: Sequence[str],
|
|
705
|
+
) -> None:
|
|
706
|
+
"""Update the colors of the channels.
|
|
707
|
+
|
|
708
|
+
Args:
|
|
709
|
+
colors (Sequence[str]): The new colors for the channels.
|
|
710
|
+
"""
|
|
711
|
+
channel_meta = self.channels_meta
|
|
712
|
+
if len(colors) != len(channel_meta.channels):
|
|
713
|
+
raise NgioValueError(
|
|
714
|
+
"The number of colors must match the number of channels."
|
|
715
|
+
)
|
|
716
|
+
new_channels = []
|
|
717
|
+
for color, ch in zip(colors, channel_meta.channels, strict=True):
|
|
718
|
+
ch_visualisation = ch.channel_visualisation.model_copy(
|
|
719
|
+
update={"color": color}
|
|
720
|
+
)
|
|
721
|
+
channel = ch.model_copy(update={"channel_visualisation": ch_visualisation})
|
|
722
|
+
new_channels.append(channel)
|
|
723
|
+
new_meta = channel_meta.model_copy(update={"channels": new_channels})
|
|
724
|
+
self._set_channel_meta(new_meta)
|
|
546
725
|
|
|
547
726
|
def set_channel_percentiles(
|
|
548
727
|
self,
|
|
549
728
|
start_percentile: float = 0.1,
|
|
550
729
|
end_percentile: float = 99.9,
|
|
551
730
|
) -> None:
|
|
552
|
-
"""Update the
|
|
553
|
-
if self.meta._channels_meta is None:
|
|
554
|
-
raise NgioValidationError("The channels meta is not initialized.")
|
|
731
|
+
"""Deprecated: Update the channel windows using percentiles.
|
|
555
732
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
733
|
+
Args:
|
|
734
|
+
start_percentile (float): The start percentile.
|
|
735
|
+
end_percentile (float): The end percentile.
|
|
736
|
+
"""
|
|
737
|
+
warnings.warn(
|
|
738
|
+
"The 'set_channel_percentiles' method is deprecated and will be removed in "
|
|
739
|
+
"ngio=0.6. Please use 'set_channel_windows_with_percentiles' instead.",
|
|
740
|
+
DeprecationWarning,
|
|
741
|
+
stacklevel=2,
|
|
742
|
+
)
|
|
743
|
+
self.set_channel_windows_with_percentiles(
|
|
744
|
+
percentiles=(start_percentile, end_percentile)
|
|
560
745
|
)
|
|
561
746
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
747
|
+
def set_channel_windows(
|
|
748
|
+
self,
|
|
749
|
+
starts_ends: Sequence[tuple[float, float]],
|
|
750
|
+
min_max: Sequence[tuple[float, float]] | None = None,
|
|
751
|
+
) -> None:
|
|
752
|
+
"""Update the channel windows.
|
|
753
|
+
|
|
754
|
+
These values are used by viewers to set the display
|
|
755
|
+
range of each channel.
|
|
756
|
+
|
|
757
|
+
Args:
|
|
758
|
+
starts_ends (Sequence[tuple[float, float]]): The start and end values
|
|
759
|
+
for each channel.
|
|
760
|
+
min_max (Sequence[tuple[float, float]] | None): The min and max values
|
|
761
|
+
for each channel. If None, the min and max values will not be updated.
|
|
762
|
+
"""
|
|
763
|
+
current_channels = self.channels_meta.channels
|
|
764
|
+
if len(starts_ends) != len(current_channels):
|
|
765
|
+
raise NgioValueError(
|
|
766
|
+
"The number of start-end pairs must match the number of channels."
|
|
568
767
|
)
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
768
|
+
if min_max is not None and len(min_max) != len(current_channels):
|
|
769
|
+
raise NgioValueError(
|
|
770
|
+
"The number of min-max pairs must match the number of channels."
|
|
572
771
|
)
|
|
573
|
-
|
|
574
|
-
|
|
772
|
+
if min_max is None:
|
|
773
|
+
min_max_ = [None] * len(current_channels)
|
|
774
|
+
else:
|
|
775
|
+
min_max_ = list(min_max)
|
|
776
|
+
channels = []
|
|
777
|
+
for se, mm, ch in zip(
|
|
778
|
+
starts_ends, min_max_, self.channels_meta.channels, strict=True
|
|
779
|
+
):
|
|
780
|
+
updates = {"start": se[0], "end": se[1]}
|
|
781
|
+
if mm is not None:
|
|
782
|
+
updates.update({"min": mm[0], "max": mm[1]})
|
|
783
|
+
channel_visualisation = ch.channel_visualisation.model_copy(update=updates)
|
|
784
|
+
channel = ch.model_copy(
|
|
785
|
+
update={"channel_visualisation": channel_visualisation}
|
|
786
|
+
)
|
|
787
|
+
channels.append(channel)
|
|
575
788
|
new_meta = ChannelsMeta(channels=channels)
|
|
576
|
-
|
|
577
789
|
meta = self.meta
|
|
578
790
|
meta.set_channels_meta(new_meta)
|
|
579
|
-
self._meta_handler.
|
|
791
|
+
self._meta_handler.update_meta(meta)
|
|
792
|
+
|
|
793
|
+
def set_channel_windows_with_percentiles(
|
|
794
|
+
self,
|
|
795
|
+
percentiles: tuple[float, float] | list[tuple[float, float]] = (0.1, 99.9),
|
|
796
|
+
) -> None:
|
|
797
|
+
"""Update the channel windows using percentiles.
|
|
798
|
+
|
|
799
|
+
Args:
|
|
800
|
+
percentiles (tuple[float, float] | list[tuple[float, float]]):
|
|
801
|
+
The start and end percentiles for each channel.
|
|
802
|
+
If a single tuple is provided,
|
|
803
|
+
the same percentiles will be used for all channels.
|
|
804
|
+
"""
|
|
805
|
+
if self.meta._channels_meta is None:
|
|
806
|
+
raise NgioValueError("The channels meta is not initialized.")
|
|
807
|
+
|
|
808
|
+
low_res_dataset = self.meta.get_lowest_resolution_dataset()
|
|
809
|
+
ref_image = self.get(path=low_res_dataset.path)
|
|
810
|
+
starts_ends = compute_image_percentile(ref_image, percentiles=percentiles)
|
|
811
|
+
self.set_channel_windows(starts_ends=starts_ends)
|
|
580
812
|
|
|
581
813
|
def set_axes_unit(
|
|
582
814
|
self,
|
|
@@ -589,61 +821,127 @@ class ImagesContainer:
|
|
|
589
821
|
space_unit (SpaceUnits): The space unit of the image.
|
|
590
822
|
time_unit (TimeUnits): The time unit of the image.
|
|
591
823
|
"""
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
824
|
+
self.get().set_axes_unit(space_unit=space_unit, time_unit=time_unit)
|
|
825
|
+
|
|
826
|
+
def set_axes_names(
|
|
827
|
+
self,
|
|
828
|
+
axes_names: Sequence[str],
|
|
829
|
+
) -> None:
|
|
830
|
+
"""Set the axes names of the image.
|
|
831
|
+
|
|
832
|
+
Args:
|
|
833
|
+
axes_names (Sequence[str]): The axes names of the image.
|
|
834
|
+
"""
|
|
835
|
+
image = self.get()
|
|
836
|
+
image.set_axes_names(axes_names=axes_names)
|
|
837
|
+
self._meta_handler._axes_setup = image.meta.axes_handler.axes_setup
|
|
838
|
+
|
|
839
|
+
def set_name(
|
|
840
|
+
self,
|
|
841
|
+
name: str,
|
|
842
|
+
) -> None:
|
|
843
|
+
"""Set the name of the image in the metadata.
|
|
844
|
+
|
|
845
|
+
This does not change the group name or any paths.
|
|
846
|
+
|
|
847
|
+
Args:
|
|
848
|
+
name (str): The name of the image.
|
|
849
|
+
"""
|
|
850
|
+
self.get().set_name(name=name)
|
|
595
851
|
|
|
596
852
|
def derive(
|
|
597
853
|
self,
|
|
598
854
|
store: StoreOrGroup,
|
|
599
855
|
ref_path: str | None = None,
|
|
856
|
+
# Metadata parameters
|
|
600
857
|
shape: Sequence[int] | None = None,
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
858
|
+
pixelsize: float | tuple[float, float] | None = None,
|
|
859
|
+
z_spacing: float | None = None,
|
|
860
|
+
time_spacing: float | None = None,
|
|
604
861
|
name: str | None = None,
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
862
|
+
translation: Sequence[float] | None = None,
|
|
863
|
+
channels_meta: Sequence[str | Channel] | None = None,
|
|
864
|
+
channels_policy: Literal["same", "squeeze", "singleton"] | int = "same",
|
|
865
|
+
ngff_version: NgffVersions | None = None,
|
|
866
|
+
# Zarr Array parameters
|
|
867
|
+
chunks: ChunksLike | None = None,
|
|
868
|
+
shards: ShardsLike | None = None,
|
|
869
|
+
dtype: str = "uint16",
|
|
870
|
+
dimension_separator: Literal[".", "/"] = "/",
|
|
871
|
+
compressors: CompressorLike = "auto",
|
|
872
|
+
extra_array_kwargs: Mapping[str, Any] | None = None,
|
|
609
873
|
overwrite: bool = False,
|
|
874
|
+
# Deprecated arguments
|
|
875
|
+
labels: Sequence[str] | None = None,
|
|
876
|
+
pixel_size: PixelSize | None = None,
|
|
610
877
|
) -> "ImagesContainer":
|
|
611
878
|
"""Create an empty OME-Zarr image from an existing image.
|
|
612
879
|
|
|
880
|
+
If a kwarg is not provided, the value from the reference image will be used.
|
|
881
|
+
|
|
613
882
|
Args:
|
|
614
883
|
store (StoreOrGroup): The Zarr store or group to create the image in.
|
|
615
|
-
ref_path (str | None): The path to the reference image in
|
|
616
|
-
|
|
884
|
+
ref_path (str | None): The path to the reference image in the image
|
|
885
|
+
container.
|
|
617
886
|
shape (Sequence[int] | None): The shape of the new image.
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
887
|
+
pixelsize (float | tuple[float, float] | None): The pixel size of the new
|
|
888
|
+
image.
|
|
889
|
+
z_spacing (float | None): The z spacing of the new image.
|
|
890
|
+
time_spacing (float | None): The time spacing of the new image.
|
|
621
891
|
name (str | None): The name of the new image.
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
892
|
+
translation (Sequence[float] | None): The translation for each axis
|
|
893
|
+
at the highest resolution level. Defaults to None.
|
|
894
|
+
channels_meta (Sequence[str | Channel] | None): The channels metadata
|
|
895
|
+
of the new image.
|
|
896
|
+
channels_policy (Literal["same", "squeeze", "singleton"] | int):
|
|
897
|
+
Possible policies:
|
|
898
|
+
- If "squeeze", the channels axis will be removed (no matter its size).
|
|
899
|
+
- If "same", the channels axis will be kept as is (if it exists).
|
|
900
|
+
- If "singleton", the channels axis will be set to size 1.
|
|
901
|
+
- If an integer is provided, the channels axis will be changed to have
|
|
902
|
+
that size.
|
|
903
|
+
ngff_version (NgffVersions | None): The NGFF version to use.
|
|
904
|
+
chunks (ChunksLike | None): The chunk shape of the new image.
|
|
905
|
+
shards (ShardsLike | None): The shard shape of the new image.
|
|
627
906
|
dtype (str | None): The data type of the new image.
|
|
907
|
+
dimension_separator (Literal[".", "/"] | None): The separator to use for
|
|
908
|
+
dimensions.
|
|
909
|
+
compressors (CompressorLike | None): The compressors to use.
|
|
910
|
+
extra_array_kwargs (Mapping[str, Any] | None): Extra arguments to pass to
|
|
911
|
+
the zarr array creation.
|
|
628
912
|
overwrite (bool): Whether to overwrite an existing image.
|
|
913
|
+
labels (Sequence[str] | None): The labels of the new image.
|
|
914
|
+
This argument is deprecated please use channels_meta instead.
|
|
915
|
+
pixel_size (PixelSize | None): The pixel size of the new image.
|
|
916
|
+
This argument is deprecated please use pixelsize, z_spacing,
|
|
917
|
+
and time_spacing instead.
|
|
629
918
|
|
|
630
919
|
Returns:
|
|
631
|
-
ImagesContainer: The new image
|
|
920
|
+
ImagesContainer: The new derived image.
|
|
921
|
+
|
|
632
922
|
"""
|
|
633
923
|
return derive_image_container(
|
|
634
924
|
image_container=self,
|
|
635
925
|
store=store,
|
|
636
926
|
ref_path=ref_path,
|
|
637
927
|
shape=shape,
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
928
|
+
pixelsize=pixelsize,
|
|
929
|
+
z_spacing=z_spacing,
|
|
930
|
+
time_spacing=time_spacing,
|
|
641
931
|
name=name,
|
|
932
|
+
translation=translation,
|
|
933
|
+
channels_meta=channels_meta,
|
|
934
|
+
channels_policy=channels_policy,
|
|
935
|
+
ngff_version=ngff_version,
|
|
642
936
|
chunks=chunks,
|
|
937
|
+
shards=shards,
|
|
643
938
|
dtype=dtype,
|
|
644
939
|
dimension_separator=dimension_separator,
|
|
645
|
-
|
|
940
|
+
compressors=compressors,
|
|
941
|
+
extra_array_kwargs=extra_array_kwargs,
|
|
646
942
|
overwrite=overwrite,
|
|
943
|
+
labels=labels,
|
|
944
|
+
pixel_size=pixel_size,
|
|
647
945
|
)
|
|
648
946
|
|
|
649
947
|
def get(
|
|
@@ -662,7 +960,7 @@ class ImagesContainer:
|
|
|
662
960
|
closest pixel size level will be returned.
|
|
663
961
|
|
|
664
962
|
"""
|
|
665
|
-
dataset = self._meta_handler.
|
|
963
|
+
dataset = self._meta_handler.get_meta().get_dataset(
|
|
666
964
|
path=path, pixel_size=pixel_size, strict=strict
|
|
667
965
|
)
|
|
668
966
|
return Image(
|
|
@@ -674,34 +972,53 @@ class ImagesContainer:
|
|
|
674
972
|
|
|
675
973
|
def compute_image_percentile(
|
|
676
974
|
image: Image,
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
) -> tuple[list[float], list[float]]:
|
|
975
|
+
percentiles: tuple[float, float] | list[tuple[float, float]] = (0.1, 99.9),
|
|
976
|
+
) -> list[tuple[float, float]]:
|
|
680
977
|
"""Compute the start and end percentiles for each channel of an image.
|
|
681
978
|
|
|
682
979
|
Args:
|
|
683
980
|
image: The image to compute the percentiles for.
|
|
684
|
-
|
|
685
|
-
|
|
981
|
+
percentiles: The start and end percentiles for each channel.
|
|
982
|
+
If a single tuple is provided, the same percentiles will be used
|
|
983
|
+
for all channels.
|
|
686
984
|
|
|
687
985
|
Returns:
|
|
688
986
|
A tuple containing the start and end percentiles for each channel.
|
|
689
987
|
"""
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
988
|
+
num_channels = image.num_channels
|
|
989
|
+
# handle the case where a single tuple is provided
|
|
990
|
+
if isinstance(percentiles, tuple):
|
|
991
|
+
if len(percentiles) != 2:
|
|
992
|
+
raise NgioValueError(
|
|
993
|
+
"Percentiles must be a tuple of two floats: "
|
|
994
|
+
"(start_percentile, end_percentile) or "
|
|
995
|
+
"a list of such tuples with length equal to the number of channels."
|
|
996
|
+
)
|
|
997
|
+
if not isinstance(percentiles[0], float) or not isinstance(
|
|
998
|
+
percentiles[1], float
|
|
999
|
+
):
|
|
1000
|
+
raise NgioValueError(
|
|
1001
|
+
"Percentiles must be a tuple of two floats: "
|
|
1002
|
+
"(start_percentile, end_percentile) or "
|
|
1003
|
+
"a list of such tuples with length equal to the number of channels."
|
|
1004
|
+
)
|
|
1005
|
+
percentiles = [percentiles] * num_channels
|
|
696
1006
|
|
|
1007
|
+
if len(percentiles) != num_channels:
|
|
1008
|
+
raise NgioValueError(
|
|
1009
|
+
"If a list of percentiles is provided, its length must be equal "
|
|
1010
|
+
"to the number of channels."
|
|
1011
|
+
)
|
|
1012
|
+
starts_and_ends = []
|
|
1013
|
+
for c_idx, (start_percentile, end_percentile) in enumerate(percentiles):
|
|
1014
|
+
data = image.get_as_dask(c=c_idx)
|
|
697
1015
|
data = da.ravel(data)
|
|
698
1016
|
# remove all the zeros
|
|
699
1017
|
mask = data > 1e-16
|
|
700
1018
|
data = data[mask]
|
|
701
1019
|
_data = data.compute()
|
|
702
1020
|
if _data.size == 0:
|
|
703
|
-
|
|
704
|
-
ends.append(0.0)
|
|
1021
|
+
starts_and_ends.append((0.0, 0.0))
|
|
705
1022
|
continue
|
|
706
1023
|
|
|
707
1024
|
# compute the percentiles
|
|
@@ -709,148 +1026,107 @@ def compute_image_percentile(
|
|
|
709
1026
|
data, [start_percentile, end_percentile], method="nearest"
|
|
710
1027
|
).compute() # type: ignore (return type is a tuple of floats)
|
|
711
1028
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
return starts, ends
|
|
1029
|
+
starts_and_ends.append((float(_s_perc), float(_e_perc)))
|
|
1030
|
+
return starts_and_ends
|
|
715
1031
|
|
|
716
1032
|
|
|
717
1033
|
def derive_image_container(
|
|
1034
|
+
*,
|
|
718
1035
|
image_container: ImagesContainer,
|
|
719
1036
|
store: StoreOrGroup,
|
|
720
1037
|
ref_path: str | None = None,
|
|
1038
|
+
# Metadata parameters
|
|
721
1039
|
shape: Sequence[int] | None = None,
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1040
|
+
pixelsize: float | tuple[float, float] | None = None,
|
|
1041
|
+
z_spacing: float | None = None,
|
|
1042
|
+
time_spacing: float | None = None,
|
|
725
1043
|
name: str | None = None,
|
|
726
|
-
|
|
1044
|
+
translation: Sequence[float] | None = None,
|
|
1045
|
+
channels_policy: Literal["same", "squeeze", "singleton"] | int = "same",
|
|
1046
|
+
channels_meta: Sequence[str | Channel] | None = None,
|
|
1047
|
+
ngff_version: NgffVersions | None = None,
|
|
1048
|
+
# Zarr Array parameters
|
|
1049
|
+
chunks: ChunksLike | None = None,
|
|
1050
|
+
shards: ShardsLike | None = None,
|
|
727
1051
|
dtype: str | None = None,
|
|
728
|
-
dimension_separator:
|
|
729
|
-
|
|
1052
|
+
dimension_separator: Literal[".", "/"] | None = None,
|
|
1053
|
+
compressors: CompressorLike | None = None,
|
|
1054
|
+
extra_array_kwargs: Mapping[str, Any] | None = None,
|
|
730
1055
|
overwrite: bool = False,
|
|
1056
|
+
# Deprecated arguments
|
|
1057
|
+
labels: Sequence[str] | None = None,
|
|
1058
|
+
pixel_size: PixelSize | None = None,
|
|
731
1059
|
) -> ImagesContainer:
|
|
732
|
-
"""
|
|
1060
|
+
"""Derive a new OME-Zarr image container from an existing image.
|
|
1061
|
+
|
|
1062
|
+
If a kwarg is not provided, the value from the reference image will be used.
|
|
733
1063
|
|
|
734
1064
|
Args:
|
|
735
|
-
image_container (ImagesContainer): The image container to derive the new image
|
|
1065
|
+
image_container (ImagesContainer): The image container to derive the new image
|
|
1066
|
+
from.
|
|
736
1067
|
store (StoreOrGroup): The Zarr store or group to create the image in.
|
|
737
1068
|
ref_path (str | None): The path to the reference image in the image container.
|
|
738
1069
|
shape (Sequence[int] | None): The shape of the new image.
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1070
|
+
pixelsize (float | tuple[float, float] | None): The pixel size of the new image.
|
|
1071
|
+
z_spacing (float | None): The z spacing of the new image.
|
|
1072
|
+
time_spacing (float | None): The time spacing of the new image.
|
|
742
1073
|
name (str | None): The name of the new image.
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
1074
|
+
translation (Sequence[float] | None): The translation for each axis
|
|
1075
|
+
at the highest resolution level. Defaults to None.
|
|
1076
|
+
channels_policy (Literal["squeeze", "same", "singleton"] | int): Possible
|
|
1077
|
+
policies:
|
|
1078
|
+
- If "squeeze", the channels axis will be removed (no matter its size).
|
|
1079
|
+
- If "same", the channels axis will be kept as is (if it exists).
|
|
1080
|
+
- If "singleton", the channels axis will be set to size 1.
|
|
1081
|
+
- If an integer is provided, the channels axis will be changed to have
|
|
1082
|
+
that size.
|
|
1083
|
+
channels_meta (Sequence[str | Channel] | None): The channels metadata
|
|
1084
|
+
of the new image.
|
|
1085
|
+
ngff_version (NgffVersions | None): The NGFF version to use.
|
|
1086
|
+
chunks (ChunksLike | None): The chunk shape of the new image.
|
|
1087
|
+
shards (ShardsLike | None): The shard shape of the new image.
|
|
748
1088
|
dtype (str | None): The data type of the new image.
|
|
749
|
-
|
|
1089
|
+
dimension_separator (Literal[".", "/"] | None): The separator to use for
|
|
1090
|
+
dimensions.
|
|
1091
|
+
compressors (CompressorLike | None): The compressors to use.
|
|
1092
|
+
extra_array_kwargs (Mapping[str, Any] | None): Extra arguments to pass to
|
|
1093
|
+
the zarr array creation.
|
|
1094
|
+
overwrite (bool): Whether to overwrite an existing image. Defaults to False.
|
|
1095
|
+
labels (Sequence[str] | None): Deprecated. This argument is deprecated,
|
|
1096
|
+
please use channels_meta instead.
|
|
1097
|
+
pixel_size (PixelSize | None): Deprecated. The pixel size of the new image.
|
|
1098
|
+
This argument is deprecated, please use pixelsize, z_spacing,
|
|
1099
|
+
and time_spacing instead.
|
|
750
1100
|
|
|
751
1101
|
Returns:
|
|
752
|
-
ImagesContainer: The new image
|
|
1102
|
+
ImagesContainer: The new derived image container.
|
|
753
1103
|
|
|
754
1104
|
"""
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
ref_meta = ref_image.meta
|
|
761
|
-
|
|
762
|
-
if shape is None:
|
|
763
|
-
shape = ref_image.shape
|
|
764
|
-
|
|
765
|
-
if pixel_size is None:
|
|
766
|
-
pixel_size = ref_image.pixel_size
|
|
767
|
-
|
|
768
|
-
if axes_names is None:
|
|
769
|
-
axes_names = ref_meta.axes_handler.axes_names
|
|
770
|
-
|
|
771
|
-
if len(axes_names) != len(shape):
|
|
772
|
-
raise NgioValidationError(
|
|
773
|
-
"The axes names of the new image does not match the reference image."
|
|
774
|
-
f"Got {axes_names} for shape {shape}."
|
|
775
|
-
)
|
|
776
|
-
|
|
777
|
-
if chunks is None:
|
|
778
|
-
chunks = ref_image.chunks
|
|
779
|
-
|
|
780
|
-
if len(chunks) != len(shape):
|
|
781
|
-
raise NgioValidationError(
|
|
782
|
-
"The chunks of the new image does not match the reference image."
|
|
783
|
-
f"Got {chunks} for shape {shape}."
|
|
784
|
-
)
|
|
785
|
-
|
|
786
|
-
if name is None:
|
|
787
|
-
name = ref_meta.name
|
|
788
|
-
|
|
789
|
-
if dtype is None:
|
|
790
|
-
dtype = ref_image.dtype
|
|
791
|
-
|
|
792
|
-
if dimension_separator is None:
|
|
793
|
-
dimension_separator = ref_image.zarr_array._dimension_separator # type: ignore
|
|
794
|
-
|
|
795
|
-
if compressor is None:
|
|
796
|
-
compressor = ref_image.zarr_array.compressor # type: ignore
|
|
797
|
-
|
|
798
|
-
handler = create_empty_image_container(
|
|
1105
|
+
ref_image = image_container.get(path=ref_path)
|
|
1106
|
+
group_handler, axes_setup = abstract_derive(
|
|
1107
|
+
ref_image=ref_image,
|
|
1108
|
+
meta_type=NgioImageMeta,
|
|
799
1109
|
store=store,
|
|
800
1110
|
shape=shape,
|
|
801
|
-
pixelsize=
|
|
802
|
-
z_spacing=
|
|
803
|
-
time_spacing=
|
|
804
|
-
levels=ref_meta.paths,
|
|
805
|
-
yx_scaling_factor=ref_meta.yx_scaling(),
|
|
806
|
-
z_scaling_factor=ref_meta.z_scaling(),
|
|
807
|
-
time_unit=pixel_size.time_unit,
|
|
808
|
-
space_unit=pixel_size.space_unit,
|
|
809
|
-
axes_names=axes_names,
|
|
1111
|
+
pixelsize=pixelsize,
|
|
1112
|
+
z_spacing=z_spacing,
|
|
1113
|
+
time_spacing=time_spacing,
|
|
810
1114
|
name=name,
|
|
1115
|
+
translation=translation,
|
|
1116
|
+
channels_meta=channels_meta,
|
|
1117
|
+
channels_policy=channels_policy,
|
|
1118
|
+
ngff_version=ngff_version,
|
|
811
1119
|
chunks=chunks,
|
|
1120
|
+
shards=shards,
|
|
812
1121
|
dtype=dtype,
|
|
813
|
-
dimension_separator=dimension_separator,
|
|
814
|
-
|
|
1122
|
+
dimension_separator=dimension_separator,
|
|
1123
|
+
compressors=compressors,
|
|
1124
|
+
extra_array_kwargs=extra_array_kwargs,
|
|
815
1125
|
overwrite=overwrite,
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
image_container = ImagesContainer(handler)
|
|
819
|
-
|
|
820
|
-
if ref_image.num_channels == image_container.num_channels:
|
|
821
|
-
_labels = ref_image.channel_labels
|
|
822
|
-
wavelength_id = ref_image.wavelength_ids
|
|
823
|
-
|
|
824
|
-
channel_meta = ref_image.channels_meta
|
|
825
|
-
colors = [c.channel_visualisation.color for c in channel_meta.channels]
|
|
826
|
-
active = [c.channel_visualisation.active for c in channel_meta.channels]
|
|
827
|
-
start = [c.channel_visualisation.start for c in channel_meta.channels]
|
|
828
|
-
end = [c.channel_visualisation.end for c in channel_meta.channels]
|
|
829
|
-
else:
|
|
830
|
-
_labels = None
|
|
831
|
-
wavelength_id = None
|
|
832
|
-
colors = None
|
|
833
|
-
active = None
|
|
834
|
-
start = None
|
|
835
|
-
end = None
|
|
836
|
-
|
|
837
|
-
if labels is not None:
|
|
838
|
-
if len(labels) != image_container.num_channels:
|
|
839
|
-
raise NgioValidationError(
|
|
840
|
-
"The number of labels does not match the number of channels."
|
|
841
|
-
)
|
|
842
|
-
_labels = labels
|
|
843
|
-
|
|
844
|
-
image_container.set_channel_meta(
|
|
845
|
-
labels=_labels,
|
|
846
|
-
wavelength_id=wavelength_id,
|
|
847
|
-
percentiles=None,
|
|
848
|
-
colors=colors,
|
|
849
|
-
active=active,
|
|
850
|
-
start=start,
|
|
851
|
-
end=end,
|
|
1126
|
+
labels=labels,
|
|
1127
|
+
pixel_size=pixel_size,
|
|
852
1128
|
)
|
|
853
|
-
return
|
|
1129
|
+
return ImagesContainer(group_handler=group_handler, axes_setup=axes_setup)
|
|
854
1130
|
|
|
855
1131
|
|
|
856
1132
|
def _parse_str_or_model(
|
|
@@ -859,9 +1135,9 @@ def _parse_str_or_model(
|
|
|
859
1135
|
"""Parse a string or ChannelSelectionModel to an integer channel index."""
|
|
860
1136
|
if isinstance(channel_selection, int):
|
|
861
1137
|
if channel_selection < 0:
|
|
862
|
-
raise
|
|
1138
|
+
raise NgioValueError("Channel index must be a non-negative integer.")
|
|
863
1139
|
if channel_selection >= image.num_channels:
|
|
864
|
-
raise
|
|
1140
|
+
raise NgioValueError(
|
|
865
1141
|
"Channel index must be less than the number "
|
|
866
1142
|
f"of channels ({image.num_channels})."
|
|
867
1143
|
)
|
|
@@ -879,7 +1155,7 @@ def _parse_str_or_model(
|
|
|
879
1155
|
)
|
|
880
1156
|
elif channel_selection.mode == "index":
|
|
881
1157
|
return int(channel_selection.identifier)
|
|
882
|
-
raise
|
|
1158
|
+
raise NgioValueError(
|
|
883
1159
|
"Invalid channel selection type. "
|
|
884
1160
|
f"{channel_selection} is of type {type(channel_selection)} ",
|
|
885
1161
|
"supported types are str, ChannelSelectionModel, and int.",
|
|
@@ -898,7 +1174,7 @@ def _parse_channel_selection(
|
|
|
898
1174
|
elif isinstance(channel_selection, Sequence):
|
|
899
1175
|
_sequence = [_parse_str_or_model(image, cs) for cs in channel_selection]
|
|
900
1176
|
return {"c": _sequence}
|
|
901
|
-
raise
|
|
1177
|
+
raise NgioValueError(
|
|
902
1178
|
f"Invalid channel selection type {type(channel_selection)}. "
|
|
903
1179
|
"Supported types are int, str, ChannelSelectionModel, and Sequence."
|
|
904
1180
|
)
|
|
@@ -912,7 +1188,7 @@ def add_channel_selection_to_slicing_dict(
|
|
|
912
1188
|
"""Add channel selection information to the slicing dictionary."""
|
|
913
1189
|
channel_info = _parse_channel_selection(image, channel_selection)
|
|
914
1190
|
if "c" in slicing_dict and channel_info:
|
|
915
|
-
raise
|
|
1191
|
+
raise NgioValueError(
|
|
916
1192
|
"Both channel_selection and 'c' in slicing_kwargs are provided. "
|
|
917
1193
|
"Which channel selection should be used is ambiguous. "
|
|
918
1194
|
"Please provide only one."
|