ngio 0.5.0b7__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/hcs/_plate.py +30 -24
- ngio/images/_abstract_image.py +65 -12
- ngio/images/_create_synt_container.py +1 -1
- ngio/images/_create_utils.py +60 -61
- ngio/images/_image.py +342 -86
- ngio/images/_label.py +31 -31
- ngio/images/_masked_image.py +2 -2
- ngio/images/_ome_zarr_container.py +247 -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 +2 -21
- ngio/ome_zarr_meta/v05/_v05_spec.py +4 -22
- 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.0b7.dist-info → ngio-0.5.1.dist-info}/METADATA +3 -2
- {ngio-0.5.0b7.dist-info → ngio-0.5.1.dist-info}/RECORD +28 -28
- {ngio-0.5.0b7.dist-info → ngio-0.5.1.dist-info}/WHEEL +0 -0
- {ngio-0.5.0b7.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,
|
|
@@ -633,8 +873,6 @@ class ImagesContainer:
|
|
|
633
873
|
If a kwarg is not provided, the value from the reference image will be used.
|
|
634
874
|
|
|
635
875
|
Args:
|
|
636
|
-
image_container (ImagesContainer): The image container to derive the new
|
|
637
|
-
image.
|
|
638
876
|
store (StoreOrGroup): The Zarr store or group to create the image in.
|
|
639
877
|
ref_path (str | None): The path to the reference image in the image
|
|
640
878
|
container.
|
|
@@ -727,34 +965,53 @@ class ImagesContainer:
|
|
|
727
965
|
|
|
728
966
|
def compute_image_percentile(
|
|
729
967
|
image: Image,
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
) -> tuple[list[float], list[float]]:
|
|
968
|
+
percentiles: tuple[float, float] | list[tuple[float, float]] = (0.1, 99.9),
|
|
969
|
+
) -> list[tuple[float, float]]:
|
|
733
970
|
"""Compute the start and end percentiles for each channel of an image.
|
|
734
971
|
|
|
735
972
|
Args:
|
|
736
973
|
image: The image to compute the percentiles for.
|
|
737
|
-
|
|
738
|
-
|
|
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.
|
|
739
977
|
|
|
740
978
|
Returns:
|
|
741
979
|
A tuple containing the start and end percentiles for each channel.
|
|
742
980
|
"""
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
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
|
|
749
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)
|
|
750
1008
|
data = da.ravel(data)
|
|
751
1009
|
# remove all the zeros
|
|
752
1010
|
mask = data > 1e-16
|
|
753
1011
|
data = data[mask]
|
|
754
1012
|
_data = data.compute()
|
|
755
1013
|
if _data.size == 0:
|
|
756
|
-
|
|
757
|
-
ends.append(0.0)
|
|
1014
|
+
starts_and_ends.append((0.0, 0.0))
|
|
758
1015
|
continue
|
|
759
1016
|
|
|
760
1017
|
# compute the percentiles
|
|
@@ -762,9 +1019,8 @@ def compute_image_percentile(
|
|
|
762
1019
|
data, [start_percentile, end_percentile], method="nearest"
|
|
763
1020
|
).compute() # type: ignore (return type is a tuple of floats)
|
|
764
1021
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
return starts, ends
|
|
1022
|
+
starts_and_ends.append((float(_s_perc), float(_e_perc)))
|
|
1023
|
+
return starts_and_ends
|
|
768
1024
|
|
|
769
1025
|
|
|
770
1026
|
def derive_image_container(
|
|
@@ -840,7 +1096,7 @@ def derive_image_container(
|
|
|
840
1096
|
|
|
841
1097
|
"""
|
|
842
1098
|
ref_image = image_container.get(path=ref_path)
|
|
843
|
-
group_handler = abstract_derive(
|
|
1099
|
+
group_handler, axes_setup = abstract_derive(
|
|
844
1100
|
ref_image=ref_image,
|
|
845
1101
|
meta_type=NgioImageMeta,
|
|
846
1102
|
store=store,
|
|
@@ -863,7 +1119,7 @@ def derive_image_container(
|
|
|
863
1119
|
labels=labels,
|
|
864
1120
|
pixel_size=pixel_size,
|
|
865
1121
|
)
|
|
866
|
-
return ImagesContainer(group_handler=group_handler)
|
|
1122
|
+
return ImagesContainer(group_handler=group_handler, axes_setup=axes_setup)
|
|
867
1123
|
|
|
868
1124
|
|
|
869
1125
|
def _parse_str_or_model(
|