ngio 0.5.0b6__py3-none-any.whl → 0.5.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.
- ngio/common/_masking_roi.py +18 -5
- ngio/common/_pyramid.py +129 -30
- ngio/hcs/_plate.py +30 -24
- ngio/images/_abstract_image.py +182 -60
- ngio/images/_create_synt_container.py +5 -1
- ngio/images/_create_utils.py +69 -74
- ngio/images/_image.py +350 -86
- ngio/images/_label.py +39 -31
- ngio/images/_masked_image.py +2 -2
- ngio/images/_ome_zarr_container.py +263 -96
- ngio/io_pipes/_match_shape.py +10 -14
- ngio/io_pipes/_ops_slices.py +6 -4
- ngio/io_pipes/_ops_slices_utils.py +8 -7
- ngio/ome_zarr_meta/_meta_handlers.py +2 -26
- ngio/ome_zarr_meta/ngio_specs/__init__.py +2 -0
- ngio/ome_zarr_meta/ngio_specs/_axes.py +161 -58
- ngio/ome_zarr_meta/ngio_specs/_ngio_hcs.py +78 -32
- ngio/ome_zarr_meta/ngio_specs/_ngio_image.py +36 -0
- ngio/ome_zarr_meta/v04/_v04_spec.py +5 -22
- ngio/ome_zarr_meta/v05/_v05_spec.py +7 -23
- ngio/resources/__init__.py +1 -1
- ngio/resources/resource_model.py +1 -1
- ngio/tables/_tables_container.py +39 -7
- ngio/tables/v1/_roi_table.py +4 -4
- ngio/utils/_zarr_utils.py +8 -15
- {ngio-0.5.0b6.dist-info → ngio-0.5.1.dist-info}/METADATA +3 -2
- {ngio-0.5.0b6.dist-info → ngio-0.5.1.dist-info}/RECORD +29 -29
- {ngio-0.5.0b6.dist-info → ngio-0.5.1.dist-info}/WHEEL +0 -0
- {ngio-0.5.0b6.dist-info → ngio-0.5.1.dist-info}/licenses/LICENSE +0 -0
ngio/images/_image.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Generic class to handle Image-like data in a OME-NGFF file."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
from collections.abc import Mapping, Sequence
|
|
4
5
|
from typing import Any, Literal
|
|
5
6
|
|
|
@@ -27,26 +28,28 @@ from ngio.ome_zarr_meta import (
|
|
|
27
28
|
from ngio.ome_zarr_meta.ngio_specs import (
|
|
28
29
|
Channel,
|
|
29
30
|
ChannelsMeta,
|
|
30
|
-
ChannelVisualisation,
|
|
31
31
|
DefaultSpaceUnit,
|
|
32
32
|
DefaultTimeUnit,
|
|
33
33
|
NgffVersions,
|
|
34
34
|
SpaceUnits,
|
|
35
35
|
TimeUnits,
|
|
36
36
|
)
|
|
37
|
+
from ngio.ome_zarr_meta.ngio_specs._axes import AxesSetup
|
|
37
38
|
from ngio.utils import (
|
|
38
39
|
NgioValueError,
|
|
39
40
|
StoreOrGroup,
|
|
40
41
|
ZarrGroupHandler,
|
|
41
42
|
)
|
|
42
43
|
|
|
44
|
+
logger = logging.getLogger(f"ngio:{__name__}")
|
|
45
|
+
|
|
43
46
|
|
|
44
47
|
class ChannelSelectionModel(BaseModel):
|
|
45
48
|
"""Model for channel selection.
|
|
46
49
|
|
|
47
50
|
This model is used to select a channel by label, wavelength ID, or index.
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
Properties:
|
|
50
53
|
identifier (str): Unique identifier for the channel.
|
|
51
54
|
This can be a channel label, wavelength ID, or index.
|
|
52
55
|
mode (Literal["label", "wavelength_id", "index"]): Specifies how to
|
|
@@ -105,7 +108,7 @@ class Image(AbstractImage):
|
|
|
105
108
|
self,
|
|
106
109
|
group_handler: ZarrGroupHandler,
|
|
107
110
|
path: str,
|
|
108
|
-
meta_handler: ImageMetaHandler
|
|
111
|
+
meta_handler: ImageMetaHandler,
|
|
109
112
|
) -> None:
|
|
110
113
|
"""Initialize the Image at a single level.
|
|
111
114
|
|
|
@@ -115,8 +118,6 @@ class Image(AbstractImage):
|
|
|
115
118
|
meta_handler: The image metadata handler.
|
|
116
119
|
|
|
117
120
|
"""
|
|
118
|
-
if meta_handler is None:
|
|
119
|
-
meta_handler = ImageMetaHandler(group_handler)
|
|
120
121
|
super().__init__(
|
|
121
122
|
group_handler=group_handler, path=path, meta_handler=meta_handler
|
|
122
123
|
)
|
|
@@ -401,76 +402,133 @@ class Image(AbstractImage):
|
|
|
401
402
|
|
|
402
403
|
|
|
403
404
|
class ImagesContainer:
|
|
404
|
-
"""A class to handle the /
|
|
405
|
+
"""A class to handle the /images group in an OME-NGFF file."""
|
|
405
406
|
|
|
406
|
-
def __init__(
|
|
407
|
-
|
|
407
|
+
def __init__(
|
|
408
|
+
self,
|
|
409
|
+
group_handler: ZarrGroupHandler,
|
|
410
|
+
axes_setup: AxesSetup | None,
|
|
411
|
+
version: NgffVersions | None = None,
|
|
412
|
+
validate_paths: bool = True,
|
|
413
|
+
) -> None:
|
|
414
|
+
"""Initialize the ImagesContainer."""
|
|
408
415
|
self._group_handler = group_handler
|
|
409
|
-
self._meta_handler = ImageMetaHandler(
|
|
416
|
+
self._meta_handler = ImageMetaHandler(
|
|
417
|
+
group_handler=group_handler, axes_setup=axes_setup, version=version
|
|
418
|
+
)
|
|
419
|
+
if validate_paths:
|
|
420
|
+
for level_path in self._meta_handler.get_meta().paths:
|
|
421
|
+
self.get(path=level_path)
|
|
410
422
|
|
|
411
423
|
@property
|
|
412
424
|
def meta(self) -> NgioImageMeta:
|
|
413
425
|
"""Return the metadata."""
|
|
414
426
|
return self._meta_handler.get_meta()
|
|
415
427
|
|
|
428
|
+
@property
|
|
429
|
+
def channels_meta(self) -> ChannelsMeta:
|
|
430
|
+
"""Return the channels metadata."""
|
|
431
|
+
return self.get().channels_meta
|
|
432
|
+
|
|
433
|
+
@property
|
|
434
|
+
def axes_setup(self) -> AxesSetup:
|
|
435
|
+
"""Return the axes setup."""
|
|
436
|
+
return self.meta.axes_handler.axes_setup
|
|
437
|
+
|
|
438
|
+
@property
|
|
439
|
+
def level_paths(self) -> list[str]:
|
|
440
|
+
"""Return the paths of the levels in the image."""
|
|
441
|
+
return self.meta.paths
|
|
442
|
+
|
|
443
|
+
@property
|
|
444
|
+
def levels_paths(self) -> list[str]:
|
|
445
|
+
"""Deprecated: use 'level_paths' instead."""
|
|
446
|
+
logger.warning(
|
|
447
|
+
"'levels_paths' is deprecated and will be removed in ngio=0.6. "
|
|
448
|
+
"Please use 'level_paths' instead."
|
|
449
|
+
)
|
|
450
|
+
return self.level_paths
|
|
451
|
+
|
|
416
452
|
@property
|
|
417
453
|
def levels(self) -> int:
|
|
418
454
|
"""Return the number of levels in the image."""
|
|
419
|
-
return self.
|
|
455
|
+
return self.meta.levels
|
|
420
456
|
|
|
421
457
|
@property
|
|
422
|
-
def
|
|
423
|
-
"""Return
|
|
424
|
-
return self.
|
|
458
|
+
def is_3d(self) -> bool:
|
|
459
|
+
"""Return True if the image is 3D."""
|
|
460
|
+
return self.get().is_3d
|
|
425
461
|
|
|
426
462
|
@property
|
|
427
|
-
def
|
|
428
|
-
"""Return the
|
|
429
|
-
|
|
430
|
-
|
|
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
|
|
431
496
|
|
|
432
497
|
@property
|
|
433
498
|
def channel_labels(self) -> list[str]:
|
|
434
499
|
"""Return the channels of the image."""
|
|
435
|
-
|
|
436
|
-
return image.channel_labels
|
|
500
|
+
return self.get().channel_labels
|
|
437
501
|
|
|
438
502
|
@property
|
|
439
503
|
def wavelength_ids(self) -> list[str | None]:
|
|
440
|
-
"""Return the wavelength of the image."""
|
|
441
|
-
|
|
442
|
-
|
|
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
|
|
443
511
|
|
|
444
512
|
def get_channel_idx(
|
|
445
513
|
self, channel_label: str | None = None, wavelength_id: str | None = None
|
|
446
514
|
) -> int:
|
|
447
|
-
"""Get the index of a channel by label or wavelength ID.
|
|
448
|
-
|
|
449
|
-
Args:
|
|
450
|
-
channel_label (str | None): The label of the channel.
|
|
451
|
-
If None a wavelength ID must be provided.
|
|
452
|
-
wavelength_id (str | None): The wavelength ID of the channel.
|
|
453
|
-
If None a channel label must be provided.
|
|
454
|
-
|
|
455
|
-
Returns:
|
|
456
|
-
int: The index of the channel.
|
|
457
|
-
|
|
458
|
-
"""
|
|
459
|
-
image = self.get()
|
|
460
|
-
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(
|
|
461
517
|
channel_label=channel_label, wavelength_id=wavelength_id
|
|
462
518
|
)
|
|
463
519
|
|
|
464
520
|
def _set_channel_meta(
|
|
465
521
|
self,
|
|
466
|
-
channels_meta: ChannelsMeta,
|
|
522
|
+
channels_meta: ChannelsMeta | None = None,
|
|
467
523
|
) -> None:
|
|
468
524
|
"""Set the channels metadata."""
|
|
525
|
+
if channels_meta is None:
|
|
526
|
+
channels_meta = ChannelsMeta.default_init(labels=self.num_channels)
|
|
469
527
|
meta = self.meta
|
|
470
528
|
meta.set_channels_meta(channels_meta)
|
|
471
529
|
self._meta_handler.update_meta(meta)
|
|
472
530
|
|
|
473
|
-
def
|
|
531
|
+
def _set_channel_meta_legacy(
|
|
474
532
|
self,
|
|
475
533
|
labels: Sequence[str | None] | int | None = None,
|
|
476
534
|
wavelength_id: Sequence[str | None] | None = None,
|
|
@@ -515,12 +573,6 @@ class ImagesContainer:
|
|
|
515
573
|
"If start and end are provided, percentiles must be None."
|
|
516
574
|
)
|
|
517
575
|
|
|
518
|
-
if percentiles is not None:
|
|
519
|
-
start, end = compute_image_percentile(
|
|
520
|
-
ref_image,
|
|
521
|
-
start_percentile=percentiles[0],
|
|
522
|
-
end_percentile=percentiles[1],
|
|
523
|
-
)
|
|
524
576
|
elif start is not None and end is not None:
|
|
525
577
|
if len(start) != len(end):
|
|
526
578
|
raise NgioValueError(
|
|
@@ -552,41 +604,205 @@ class ImagesContainer:
|
|
|
552
604
|
**omero_kwargs,
|
|
553
605
|
)
|
|
554
606
|
self._set_channel_meta(channel_meta)
|
|
607
|
+
if percentiles is not None:
|
|
608
|
+
self.set_channel_windows_with_percentiles(percentiles=percentiles)
|
|
609
|
+
|
|
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
|
+
logger.warning(
|
|
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
|
+
)
|
|
666
|
+
self._set_channel_meta_legacy(
|
|
667
|
+
labels=labels,
|
|
668
|
+
wavelength_id=wavelength_id,
|
|
669
|
+
start=start,
|
|
670
|
+
end=end,
|
|
671
|
+
percentiles=percentiles,
|
|
672
|
+
colors=colors,
|
|
673
|
+
active=active,
|
|
674
|
+
**omero_kwargs,
|
|
675
|
+
)
|
|
676
|
+
return None
|
|
677
|
+
self._set_channel_meta(channel_meta)
|
|
678
|
+
|
|
679
|
+
def set_channel_labels(
|
|
680
|
+
self,
|
|
681
|
+
labels: Sequence[str],
|
|
682
|
+
) -> None:
|
|
683
|
+
"""Update the labels of the channels.
|
|
684
|
+
|
|
685
|
+
Args:
|
|
686
|
+
labels (Sequence[str]): The new labels for the channels.
|
|
687
|
+
"""
|
|
688
|
+
channels_meta = self.channels_meta
|
|
689
|
+
if len(labels) != len(channels_meta.channels):
|
|
690
|
+
raise NgioValueError(
|
|
691
|
+
"The number of labels must match the number of channels."
|
|
692
|
+
)
|
|
693
|
+
new_channels = []
|
|
694
|
+
for label, ch in zip(labels, channels_meta.channels, strict=True):
|
|
695
|
+
channel = ch.model_copy(update={"label": label})
|
|
696
|
+
new_channels.append(channel)
|
|
697
|
+
new_meta = channels_meta.model_copy(update={"channels": new_channels})
|
|
698
|
+
self._set_channel_meta(new_meta)
|
|
699
|
+
|
|
700
|
+
def set_channel_colors(
|
|
701
|
+
self,
|
|
702
|
+
colors: Sequence[str],
|
|
703
|
+
) -> None:
|
|
704
|
+
"""Update the colors of the channels.
|
|
705
|
+
|
|
706
|
+
Args:
|
|
707
|
+
colors (Sequence[str]): The new colors for the channels.
|
|
708
|
+
"""
|
|
709
|
+
channel_meta = self.channels_meta
|
|
710
|
+
if len(colors) != len(channel_meta.channels):
|
|
711
|
+
raise NgioValueError(
|
|
712
|
+
"The number of colors must match the number of channels."
|
|
713
|
+
)
|
|
714
|
+
new_channels = []
|
|
715
|
+
for color, ch in zip(colors, channel_meta.channels, strict=True):
|
|
716
|
+
ch_visualisation = ch.channel_visualisation.model_copy(
|
|
717
|
+
update={"color": color}
|
|
718
|
+
)
|
|
719
|
+
channel = ch.model_copy(update={"channel_visualisation": ch_visualisation})
|
|
720
|
+
new_channels.append(channel)
|
|
721
|
+
new_meta = channel_meta.model_copy(update={"channels": new_channels})
|
|
722
|
+
self._set_channel_meta(new_meta)
|
|
555
723
|
|
|
556
724
|
def set_channel_percentiles(
|
|
557
725
|
self,
|
|
558
726
|
start_percentile: float = 0.1,
|
|
559
727
|
end_percentile: float = 99.9,
|
|
560
728
|
) -> None:
|
|
561
|
-
"""Update the
|
|
562
|
-
if self.meta._channels_meta is None:
|
|
563
|
-
raise NgioValueError("The channels meta is not initialized.")
|
|
729
|
+
"""Deprecated: Update the channel windows using percentiles.
|
|
564
730
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
731
|
+
Args:
|
|
732
|
+
start_percentile (float): The start percentile.
|
|
733
|
+
end_percentile (float): The end percentile.
|
|
734
|
+
"""
|
|
735
|
+
logger.warning(
|
|
736
|
+
"The 'set_channel_percentiles' method is deprecated and will be removed in "
|
|
737
|
+
"ngio=0.6. Please use 'set_channel_windows_with_percentiles' instead."
|
|
738
|
+
)
|
|
739
|
+
self.set_channel_windows_with_percentiles(
|
|
740
|
+
percentiles=(start_percentile, end_percentile)
|
|
569
741
|
)
|
|
570
742
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
743
|
+
def set_channel_windows(
|
|
744
|
+
self,
|
|
745
|
+
starts_ends: Sequence[tuple[float, float]],
|
|
746
|
+
min_max: Sequence[tuple[float, float]] | None = None,
|
|
747
|
+
) -> None:
|
|
748
|
+
"""Update the channel windows.
|
|
749
|
+
|
|
750
|
+
These values are used by viewers to set the display
|
|
751
|
+
range of each channel.
|
|
752
|
+
|
|
753
|
+
Args:
|
|
754
|
+
starts_ends (Sequence[tuple[float, float]]): The start and end values
|
|
755
|
+
for each channel.
|
|
756
|
+
min_max (Sequence[tuple[float, float]] | None): The min and max values
|
|
757
|
+
for each channel. If None, the min and max values will not be updated.
|
|
758
|
+
"""
|
|
759
|
+
current_channels = self.channels_meta.channels
|
|
760
|
+
if len(starts_ends) != len(current_channels):
|
|
761
|
+
raise NgioValueError(
|
|
762
|
+
"The number of start-end pairs must match the number of channels."
|
|
577
763
|
)
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
764
|
+
if min_max is not None and len(min_max) != len(current_channels):
|
|
765
|
+
raise NgioValueError(
|
|
766
|
+
"The number of min-max pairs must match the number of channels."
|
|
581
767
|
)
|
|
582
|
-
|
|
583
|
-
|
|
768
|
+
if min_max is None:
|
|
769
|
+
min_max_ = [None] * len(current_channels)
|
|
770
|
+
else:
|
|
771
|
+
min_max_ = list(min_max)
|
|
772
|
+
channels = []
|
|
773
|
+
for se, mm, ch in zip(
|
|
774
|
+
starts_ends, min_max_, self.channels_meta.channels, strict=True
|
|
775
|
+
):
|
|
776
|
+
updates = {"start": se[0], "end": se[1]}
|
|
777
|
+
if mm is not None:
|
|
778
|
+
updates.update({"min": mm[0], "max": mm[1]})
|
|
779
|
+
channel_visualisation = ch.channel_visualisation.model_copy(update=updates)
|
|
780
|
+
channel = ch.model_copy(
|
|
781
|
+
update={"channel_visualisation": channel_visualisation}
|
|
782
|
+
)
|
|
783
|
+
channels.append(channel)
|
|
584
784
|
new_meta = ChannelsMeta(channels=channels)
|
|
585
|
-
|
|
586
785
|
meta = self.meta
|
|
587
786
|
meta.set_channels_meta(new_meta)
|
|
588
787
|
self._meta_handler.update_meta(meta)
|
|
589
788
|
|
|
789
|
+
def set_channel_windows_with_percentiles(
|
|
790
|
+
self,
|
|
791
|
+
percentiles: tuple[float, float] | list[tuple[float, float]] = (0.1, 99.9),
|
|
792
|
+
) -> None:
|
|
793
|
+
"""Update the channel windows using percentiles.
|
|
794
|
+
|
|
795
|
+
Args:
|
|
796
|
+
percentiles (tuple[float, float] | list[tuple[float, float]]):
|
|
797
|
+
The start and end percentiles for each channel.
|
|
798
|
+
If a single tuple is provided,
|
|
799
|
+
the same percentiles will be used for all channels.
|
|
800
|
+
"""
|
|
801
|
+
low_res_dataset = self.meta.get_lowest_resolution_dataset()
|
|
802
|
+
ref_image = self.get(path=low_res_dataset.path)
|
|
803
|
+
starts_ends = compute_image_percentile(ref_image, percentiles=percentiles)
|
|
804
|
+
self.set_channel_windows(starts_ends=starts_ends)
|
|
805
|
+
|
|
590
806
|
def set_axes_unit(
|
|
591
807
|
self,
|
|
592
808
|
space_unit: SpaceUnits = DefaultSpaceUnit,
|
|
@@ -598,9 +814,33 @@ class ImagesContainer:
|
|
|
598
814
|
space_unit (SpaceUnits): The space unit of the image.
|
|
599
815
|
time_unit (TimeUnits): The time unit of the image.
|
|
600
816
|
"""
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
817
|
+
self.get().set_axes_unit(space_unit=space_unit, time_unit=time_unit)
|
|
818
|
+
|
|
819
|
+
def set_axes_names(
|
|
820
|
+
self,
|
|
821
|
+
axes_names: Sequence[str],
|
|
822
|
+
) -> None:
|
|
823
|
+
"""Set the axes names of the image.
|
|
824
|
+
|
|
825
|
+
Args:
|
|
826
|
+
axes_names (Sequence[str]): The axes names of the image.
|
|
827
|
+
"""
|
|
828
|
+
image = self.get()
|
|
829
|
+
image.set_axes_names(axes_names=axes_names)
|
|
830
|
+
self._meta_handler._axes_setup = image.meta.axes_handler.axes_setup
|
|
831
|
+
|
|
832
|
+
def set_name(
|
|
833
|
+
self,
|
|
834
|
+
name: str,
|
|
835
|
+
) -> None:
|
|
836
|
+
"""Set the name of the image in the metadata.
|
|
837
|
+
|
|
838
|
+
This does not change the group name or any paths.
|
|
839
|
+
|
|
840
|
+
Args:
|
|
841
|
+
name (str): The name of the image.
|
|
842
|
+
"""
|
|
843
|
+
self.get().set_name(name=name)
|
|
604
844
|
|
|
605
845
|
def derive(
|
|
606
846
|
self,
|
|
@@ -612,6 +852,7 @@ class ImagesContainer:
|
|
|
612
852
|
z_spacing: float | None = None,
|
|
613
853
|
time_spacing: float | None = None,
|
|
614
854
|
name: str | None = None,
|
|
855
|
+
translation: Sequence[float] | None = None,
|
|
615
856
|
channels_meta: Sequence[str | Channel] | None = None,
|
|
616
857
|
channels_policy: Literal["same", "squeeze", "singleton"] | int = "same",
|
|
617
858
|
ngff_version: NgffVersions | None = None,
|
|
@@ -632,8 +873,6 @@ class ImagesContainer:
|
|
|
632
873
|
If a kwarg is not provided, the value from the reference image will be used.
|
|
633
874
|
|
|
634
875
|
Args:
|
|
635
|
-
image_container (ImagesContainer): The image container to derive the new
|
|
636
|
-
image.
|
|
637
876
|
store (StoreOrGroup): The Zarr store or group to create the image in.
|
|
638
877
|
ref_path (str | None): The path to the reference image in the image
|
|
639
878
|
container.
|
|
@@ -643,6 +882,8 @@ class ImagesContainer:
|
|
|
643
882
|
z_spacing (float | None): The z spacing of the new image.
|
|
644
883
|
time_spacing (float | None): The time spacing of the new image.
|
|
645
884
|
name (str | None): The name of the new image.
|
|
885
|
+
translation (Sequence[float] | None): The translation for each axis
|
|
886
|
+
at the highest resolution level. Defaults to None.
|
|
646
887
|
channels_meta (Sequence[str | Channel] | None): The channels metadata
|
|
647
888
|
of the new image.
|
|
648
889
|
channels_policy (Literal["same", "squeeze", "singleton"] | int):
|
|
@@ -681,6 +922,7 @@ class ImagesContainer:
|
|
|
681
922
|
z_spacing=z_spacing,
|
|
682
923
|
time_spacing=time_spacing,
|
|
683
924
|
name=name,
|
|
925
|
+
translation=translation,
|
|
684
926
|
channels_meta=channels_meta,
|
|
685
927
|
channels_policy=channels_policy,
|
|
686
928
|
ngff_version=ngff_version,
|
|
@@ -723,34 +965,53 @@ class ImagesContainer:
|
|
|
723
965
|
|
|
724
966
|
def compute_image_percentile(
|
|
725
967
|
image: Image,
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
) -> tuple[list[float], list[float]]:
|
|
968
|
+
percentiles: tuple[float, float] | list[tuple[float, float]] = (0.1, 99.9),
|
|
969
|
+
) -> list[tuple[float, float]]:
|
|
729
970
|
"""Compute the start and end percentiles for each channel of an image.
|
|
730
971
|
|
|
731
972
|
Args:
|
|
732
973
|
image: The image to compute the percentiles for.
|
|
733
|
-
|
|
734
|
-
|
|
974
|
+
percentiles: The start and end percentiles for each channel.
|
|
975
|
+
If a single tuple is provided, the same percentiles will be used
|
|
976
|
+
for all channels.
|
|
735
977
|
|
|
736
978
|
Returns:
|
|
737
979
|
A tuple containing the start and end percentiles for each channel.
|
|
738
980
|
"""
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
981
|
+
num_channels = image.num_channels
|
|
982
|
+
# handle the case where a single tuple is provided
|
|
983
|
+
if isinstance(percentiles, tuple):
|
|
984
|
+
if len(percentiles) != 2:
|
|
985
|
+
raise NgioValueError(
|
|
986
|
+
"Percentiles must be a tuple of two floats: "
|
|
987
|
+
"(start_percentile, end_percentile) or "
|
|
988
|
+
"a list of such tuples with length equal to the number of channels."
|
|
989
|
+
)
|
|
990
|
+
if not isinstance(percentiles[0], float) or not isinstance(
|
|
991
|
+
percentiles[1], float
|
|
992
|
+
):
|
|
993
|
+
raise NgioValueError(
|
|
994
|
+
"Percentiles must be a tuple of two floats: "
|
|
995
|
+
"(start_percentile, end_percentile) or "
|
|
996
|
+
"a list of such tuples with length equal to the number of channels."
|
|
997
|
+
)
|
|
998
|
+
percentiles = [percentiles] * num_channels
|
|
745
999
|
|
|
1000
|
+
if len(percentiles) != num_channels:
|
|
1001
|
+
raise NgioValueError(
|
|
1002
|
+
"If a list of percentiles is provided, its length must be equal "
|
|
1003
|
+
"to the number of channels."
|
|
1004
|
+
)
|
|
1005
|
+
starts_and_ends = []
|
|
1006
|
+
for c_idx, (start_percentile, end_percentile) in enumerate(percentiles):
|
|
1007
|
+
data = image.get_as_dask(c=c_idx)
|
|
746
1008
|
data = da.ravel(data)
|
|
747
1009
|
# remove all the zeros
|
|
748
1010
|
mask = data > 1e-16
|
|
749
1011
|
data = data[mask]
|
|
750
1012
|
_data = data.compute()
|
|
751
1013
|
if _data.size == 0:
|
|
752
|
-
|
|
753
|
-
ends.append(0.0)
|
|
1014
|
+
starts_and_ends.append((0.0, 0.0))
|
|
754
1015
|
continue
|
|
755
1016
|
|
|
756
1017
|
# compute the percentiles
|
|
@@ -758,9 +1019,8 @@ def compute_image_percentile(
|
|
|
758
1019
|
data, [start_percentile, end_percentile], method="nearest"
|
|
759
1020
|
).compute() # type: ignore (return type is a tuple of floats)
|
|
760
1021
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
return starts, ends
|
|
1022
|
+
starts_and_ends.append((float(_s_perc), float(_e_perc)))
|
|
1023
|
+
return starts_and_ends
|
|
764
1024
|
|
|
765
1025
|
|
|
766
1026
|
def derive_image_container(
|
|
@@ -774,6 +1034,7 @@ def derive_image_container(
|
|
|
774
1034
|
z_spacing: float | None = None,
|
|
775
1035
|
time_spacing: float | None = None,
|
|
776
1036
|
name: str | None = None,
|
|
1037
|
+
translation: Sequence[float] | None = None,
|
|
777
1038
|
channels_policy: Literal["same", "squeeze", "singleton"] | int = "same",
|
|
778
1039
|
channels_meta: Sequence[str | Channel] | None = None,
|
|
779
1040
|
ngff_version: NgffVersions | None = None,
|
|
@@ -803,6 +1064,8 @@ def derive_image_container(
|
|
|
803
1064
|
z_spacing (float | None): The z spacing of the new image.
|
|
804
1065
|
time_spacing (float | None): The time spacing of the new image.
|
|
805
1066
|
name (str | None): The name of the new image.
|
|
1067
|
+
translation (Sequence[float] | None): The translation for each axis
|
|
1068
|
+
at the highest resolution level. Defaults to None.
|
|
806
1069
|
channels_policy (Literal["squeeze", "same", "singleton"] | int): Possible
|
|
807
1070
|
policies:
|
|
808
1071
|
- If "squeeze", the channels axis will be removed (no matter its size).
|
|
@@ -833,7 +1096,7 @@ def derive_image_container(
|
|
|
833
1096
|
|
|
834
1097
|
"""
|
|
835
1098
|
ref_image = image_container.get(path=ref_path)
|
|
836
|
-
group_handler = abstract_derive(
|
|
1099
|
+
group_handler, axes_setup = abstract_derive(
|
|
837
1100
|
ref_image=ref_image,
|
|
838
1101
|
meta_type=NgioImageMeta,
|
|
839
1102
|
store=store,
|
|
@@ -842,6 +1105,7 @@ def derive_image_container(
|
|
|
842
1105
|
z_spacing=z_spacing,
|
|
843
1106
|
time_spacing=time_spacing,
|
|
844
1107
|
name=name,
|
|
1108
|
+
translation=translation,
|
|
845
1109
|
channels_meta=channels_meta,
|
|
846
1110
|
channels_policy=channels_policy,
|
|
847
1111
|
ngff_version=ngff_version,
|
|
@@ -855,7 +1119,7 @@ def derive_image_container(
|
|
|
855
1119
|
labels=labels,
|
|
856
1120
|
pixel_size=pixel_size,
|
|
857
1121
|
)
|
|
858
|
-
return ImagesContainer(group_handler=group_handler)
|
|
1122
|
+
return ImagesContainer(group_handler=group_handler, axes_setup=axes_setup)
|
|
859
1123
|
|
|
860
1124
|
|
|
861
1125
|
def _parse_str_or_model(
|