From e4c2bf0e43246e41fd9673f140192799370b451f Mon Sep 17 00:00:00 2001 From: lorenzo Date: Thu, 14 Nov 2024 16:45:45 +0100 Subject: [PATCH] minor refactor --- docs/notebooks/image.ipynb | 14 ++++---- docs/notebooks/processing.ipynb | 1 - src/ngio/core/image_handler.py | 3 +- src/ngio/core/label_handler.py | 3 +- src/ngio/core/ngff_image.py | 41 +++++++++++++++++------ src/ngio/tables/v1/feature_tables.py | 24 +++++++++----- src/ngio/tables/v1/masking_roi_tables.py | 42 +++++++++++++++++------- src/ngio/tables/v1/roi_tables.py | 19 +++++++++-- tests/core/test_label_handler.py | 6 ++-- tests/tables/test_table_group.py | 14 ++++---- tests/tables/test_v1_tables.py | 6 ++-- 11 files changed, 116 insertions(+), 57 deletions(-) diff --git a/docs/notebooks/image.ipynb b/docs/notebooks/image.ipynb index 0d5b5b5..f3c2705 100644 --- a/docs/notebooks/image.ipynb +++ b/docs/notebooks/image.ipynb @@ -108,7 +108,7 @@ "metadata": {}, "outputs": [], "source": [ - "roi_table = ngff_image.table.get_table(\"FOV_ROI_table\")\n", + "roi_table = ngff_image.tables.get_table(\"FOV_ROI_table\")\n", "roi = roi_table.get_roi(\"FOV_1\")\n", "print(f\"{roi=}\")\n", "\n", @@ -173,7 +173,7 @@ "\n", "\n", "# Load the image from disk and show the edited image\n", - "nuclei = ngff_image.label.get_label(\"nuclei\")\n", + "nuclei = ngff_image.labels.get_label(\"nuclei\")\n", "fig, axs = plt.subplots(1, 2, figsize=(10, 5))\n", "axs[0].imshow(image.on_disk_array[0, 0], cmap=\"gray\")\n", "axs[1].imshow(nuclei.on_disk_array[0])\n", @@ -202,7 +202,7 @@ "outputs": [], "source": [ "# Create a a new label object and set it to a simple segmentation\n", - "new_label = ngff_image.label.derive(\"new_label\", overwrite=True)\n", + "new_label = ngff_image.labels.derive(\"new_label\", overwrite=True)\n", "\n", "simple_segmentation = image.on_disk_array[0] > 100\n", "new_label.on_disk_array[...] = simple_segmentation\n", @@ -234,8 +234,8 @@ "metadata": {}, "outputs": [], "source": [ - "label_0 = ngff_image.label.get_label(\"new_label\", path=\"0\")\n", - "label_2 = ngff_image.label.get_label(\"new_label\", path=\"2\")\n", + "label_0 = ngff_image.labels.get_label(\"new_label\", path=\"0\")\n", + "label_2 = ngff_image.labels.get_label(\"new_label\", path=\"2\")\n", "\n", "label_before_consolidation = label_2.on_disk_array[...]\n", "\n", @@ -274,10 +274,10 @@ "import numpy as np\n", "import pandas as pd\n", "\n", - "print(f\"List of feature table: {ngff_image.table.list(table_type='feature_table')}\")\n", + "print(f\"List of feature table: {ngff_image.tables.list(table_type='feature_table')}\")\n", "\n", "\n", - "nuclei = ngff_image.label.get_label('nuclei')\n", + "nuclei = ngff_image.labels.get_label('nuclei')\n", "\n", "# Create a table with random features for each nuclei in each ROI\n", "list_of_records = []\n", diff --git a/docs/notebooks/processing.ipynb b/docs/notebooks/processing.ipynb index b32cbbb..8612f78 100644 --- a/docs/notebooks/processing.ipynb +++ b/docs/notebooks/processing.ipynb @@ -53,7 +53,6 @@ "metadata": {}, "outputs": [], "source": [ - "\n", "mip_ngff = ngff_image.derive_new_image(\"../../data/20200812-CardiomyocyteDifferentiation14-Cycle1.zarr/B/03/0_mip\",\n", " name=\"MIP\",\n", " on_disk_shape=(1, 1, 2160, 5120))" diff --git a/src/ngio/core/image_handler.py b/src/ngio/core/image_handler.py index a34c706..6bd7203 100644 --- a/src/ngio/core/image_handler.py +++ b/src/ngio/core/image_handler.py @@ -65,7 +65,8 @@ def __repr__(self) -> str: len_name = len(name) return ( f"{name}" - f"path={self.path},\n" + f"group_path={self.group_path}, \n" + f"{' ':>{len_name}}path={self.path},\n" f"{' ':>{len_name}}{self.pixel_size},\n" f"{' ':>{len_name}}{self.dimensions},\n" ")" diff --git a/src/ngio/core/label_handler.py b/src/ngio/core/label_handler.py index aaeee38..c6a0a9e 100644 --- a/src/ngio/core/label_handler.py +++ b/src/ngio/core/label_handler.py @@ -74,7 +74,8 @@ def __repr__(self) -> str: len_name = len(name) return ( f"{name}" - f"path={self.path},\n" + f"group_path={self.group_path}, \n" + f"{' ':>{len_name}}path={self.path},\n" f"{' ':>{len_name}}name={self.name},\n" f"{' ':>{len_name}}{self.pixel_size},\n" f"{' ':>{len_name}}{self.dimensions},\n" diff --git a/src/ngio/core/ngff_image.py b/src/ngio/core/ngff_image.py index 07e5bc0..6581330 100644 --- a/src/ngio/core/ngff_image.py +++ b/src/ngio/core/ngff_image.py @@ -4,6 +4,7 @@ import dask.array as da import numpy as np +import zarr from ngio.core.image_handler import Image from ngio.core.label_handler import LabelGroup @@ -24,17 +25,19 @@ def __init__( """Initialize the NGFFImage in read mode.""" self.store = store self._mode = mode - self.group = open_group_wrapper(store=store, mode=self._mode) + self._group = open_group_wrapper(store=store, mode=self._mode) - if self.group.read_only: + if self._group.read_only: self._mode = "r" self._image_meta = get_ngff_image_meta_handler( - self.group, meta_mode="image", cache=cache + self._group, meta_mode="image", cache=cache ) self._metadata_cache = cache - self.table = TableGroup(self.group, mode=self._mode) - self.label = LabelGroup(self.group, image_ref=self.get_image(), mode=self._mode) + self.tables = TableGroup(self._group, mode=self._mode) + self.labels = LabelGroup( + self._group, image_ref=self.get_image(), mode=self._mode + ) ngio_logger.info(f"Opened image located in store: {store}") ngio_logger.info(f"- Image number of levels: {self.num_levels}") @@ -45,13 +48,31 @@ def __repr__(self) -> str: len_name = len(name) return ( f"{name}" - f"store={self.store}, \n" + f"group_path={self.group_path}, \n" f"{' ':>{len_name}}paths={self.levels_paths}, \n" - f"{' ':>{len_name}}labels={self.label.list()}, \n" - f"{' ':>{len_name}}tables={self.table.list()}, \n" + f"{' ':>{len_name}}labels={self.labels.list()}, \n" + f"{' ':>{len_name}}tables={self.tables.list()}, \n" ")" ) + @property + def group(self) -> zarr.Group: + """Get the group of the image.""" + return self._group + + @property + def root_path(self) -> str: + """Get the root path of the image.""" + return str(self._group.store.path) + + @property + def group_path(self) -> str: + """Get the path of the group.""" + root = self.root_path + if root.endswith("/"): + root = root[:-1] + return f"{root}/{self._group.path}" + @property def image_meta(self) -> ImageMeta: """Get the image metadata.""" @@ -92,11 +113,11 @@ def get_image( highest_resolution = False image = Image( - store=self.group, + store=self._group, path=path, pixel_size=pixel_size, highest_resolution=highest_resolution, - label_group=LabelGroup(self.group, image_ref=None, mode=self._mode), + label_group=LabelGroup(self._group, image_ref=None, mode=self._mode), cache=self._metadata_cache, mode=self._mode, ) diff --git a/src/ngio/tables/v1/feature_tables.py b/src/ngio/tables/v1/feature_tables.py index 0a74d44..0d535a1 100644 --- a/src/ngio/tables/v1/feature_tables.py +++ b/src/ngio/tables/v1/feature_tables.py @@ -68,13 +68,16 @@ def __init__( def __repr__(self) -> str: """Return the string representation of the class.""" - region_path = self.meta.region["path"] - label_name = region_path.split("/")[-1] + name = "FeatureTableV1(" + len_name = len(name) return ( - f"FeatureTable(name={self.name}, " - f"source_label={label_name}, " - f"features={self.table.columns.to_list()}, " - f"num_labels={len(self.table.index)})" + f"{name}" + f"group_path={self.group_path}, \n" + f"{' ':>{len_name}}name={self.name},\n" + f"{' ':>{len_name}}features={self.table.columns.to_list()}, \n" + f"{' ':>{len_name}}source_label={self.source_label()}, \n" + f"{' ':>{len_name}}num_labels={len(self.table.index)}), \n" + ")" ) @classmethod @@ -156,7 +159,7 @@ def set_table(self, table: pd.DataFrame) -> None: """Set the feature table.""" self._table_handler.set_table(table) - def label_image_name(self, get_full_path: bool = False) -> str: + def source_label(self, get_full_path: bool = False) -> str | None: """Return the name of the label image. The name is assumed to be after the last '/' in the path. @@ -165,10 +168,13 @@ def label_image_name(self, get_full_path: bool = False) -> str: Args: get_full_path (bool): If True, the full path is returned. """ - path = self.meta.region["path"] + region = self.meta.region + if region is None: + return None + path = region["path"] + if get_full_path: return path - return path.split("/")[-1] def consolidate(self) -> None: diff --git a/src/ngio/tables/v1/masking_roi_tables.py b/src/ngio/tables/v1/masking_roi_tables.py index 2497c4c..7fdeb5c 100644 --- a/src/ngio/tables/v1/masking_roi_tables.py +++ b/src/ngio/tables/v1/masking_roi_tables.py @@ -12,7 +12,7 @@ from pydantic import BaseModel from ngio.core.label_handler import Label -from ngio.core.roi import WorldCooROI +from ngio.core.roi import SpaceUnits, WorldCooROI from ngio.tables._utils import validate_columns from ngio.tables.v1._generic_table import REQUIRED_COLUMNS, BaseTable, write_table_ad @@ -81,12 +81,15 @@ def __init__( def __repr__(self) -> str: """Return the string representation of the class.""" - region_path = self.meta.region["path"] - label_name = region_path.split("/")[-1] + name = "MaskingROITableV1(" + len_name = len(name) return ( - f"MaskingROITable(name={self.name}, " - f"source_label={label_name}, " - f"num_labels={len(self.table.index)})" + f"{name}" + f"group_path={self.group_path}, \n" + f"{' ':>{len_name}}name={self.name},\n" + f"{' ':>{len_name}}source_label={self.source_label()}, \n" + f"{' ':>{len_name}}num_labels={len(self.table.index)}), \n" + ")" ) @classmethod @@ -167,6 +170,24 @@ def table(self, table: pd.DataFrame) -> None: "Setting the table is not implemented. Please use the 'set_table' method." ) + def source_label(self, get_full_path: bool = False) -> str | None: + """Return the name of the label image. + + The name is assumed to be after the last '/' in the path. + Since this might fails, get_full_path is True, the full path is returned. + + Args: + get_full_path (bool): If True, the full path is returned. + """ + region = self.meta.region + if region is None: + return None + path = region["path"] + + if get_full_path: + return path + return path.split("/")[-1] + def set_table(self, table: pd.DataFrame) -> None: """Set the feature table.""" self._table_handler.set_table(table) @@ -196,9 +217,6 @@ def get_roi(self, label: int) -> WorldCooROI: table_df = self.table.loc[label] - region_path = self.meta.region["path"] - label_name = region_path.split("/")[-1] - roi = WorldCooROI( x=table_df.loc["x_micrometer"], y=table_df.loc["y_micrometer"], @@ -206,11 +224,11 @@ def get_roi(self, label: int) -> WorldCooROI: x_length=table_df.loc["len_x_micrometer"], y_length=table_df.loc["len_y_micrometer"], z_length=table_df.loc["len_z_micrometer"], - unit="micrometer", + unit=SpaceUnits.micrometer, infos={ "label": label, - "label_image": region_path, - "label_name": label_name, + "source_label_path": self.source_label(get_full_path=True), + "source_label": self.source_label(), }, ) return roi diff --git a/src/ngio/tables/v1/roi_tables.py b/src/ngio/tables/v1/roi_tables.py index 8c415a0..521a8f4 100644 --- a/src/ngio/tables/v1/roi_tables.py +++ b/src/ngio/tables/v1/roi_tables.py @@ -12,7 +12,7 @@ import zarr from pydantic import BaseModel -from ngio.core.roi import WorldCooROI +from ngio.core.roi import SpaceUnits, WorldCooROI from ngio.tables._utils import validate_columns from ngio.tables.v1._generic_table import REQUIRED_COLUMNS, BaseTable, write_table_ad @@ -141,7 +141,15 @@ def _new( def __repr__(self) -> str: """Return the string representation of the ROI table.""" - return f"ROITable(name={self.name}, num_rois={len(self.table.index)})" + name = "ROITableV1(" + len_name = len(name) + return ( + f"{name}" + f"group_path={self.group_path}, \n" + f"{' ':>{len_name}}name={self.name},\n" + f"{' ':>{len_name}}num_rois={self.num_rois}, \n" + ")" + ) @property def name(self) -> str: @@ -189,6 +197,11 @@ def field_indexes(self) -> list[str]: """Return a list of all field indexes in the table.""" return self.table.index.tolist() + @property + def num_rois(self) -> int: + """Return the number of ROIs in the table.""" + return len(self.field_indexes) + def set_rois( self, rois: Iterable[WorldCooROI] | WorldCooROI, @@ -257,7 +270,7 @@ def get_roi(self, field_index: str) -> WorldCooROI: x_length=table_df.loc["len_x_micrometer"], y_length=table_df.loc["len_y_micrometer"], z_length=table_df.loc["len_z_micrometer"], - unit="micrometer", + unit=SpaceUnits.micrometer, infos={"FieldIndex": field_index, **self._gater_optional_columns(table_df)}, ) return roi diff --git a/tests/core/test_label_handler.py b/tests/core/test_label_handler.py index 8e5805e..433fa83 100644 --- a/tests/core/test_label_handler.py +++ b/tests/core/test_label_handler.py @@ -10,11 +10,11 @@ def test_label_image(self, ome_zarr_image_v04_path: Path) -> None: ngff_image = NgffImage(ome_zarr_image_v04_path) image_handler = ngff_image.get_image(path="0") - label_handler = ngff_image.label.derive(name="label") + label_handler = ngff_image.labels.derive(name="label") label_handler.__repr__() assert label_handler.array_path == f"{ome_zarr_image_v04_path}/labels/label/0" - assert ngff_image.label.list() == ["label"] + assert ngff_image.labels.list() == ["label"] assert "c" not in label_handler.axes_names assert label_handler.shape == image_handler.shape[1:] @@ -26,5 +26,5 @@ def test_label_image(self, ome_zarr_image_v04_path: Path) -> None: label_handler._consolidate() - label_handler_1 = ngff_image.label.get_label(name="label") + label_handler_1 = ngff_image.labels.get_label(name="label") assert label_handler_1._get_array(t=0, z=0, x=0, y=0) == 1 diff --git a/tests/tables/test_table_group.py b/tests/tables/test_table_group.py index 7775079..8a2313d 100644 --- a/tests/tables/test_table_group.py +++ b/tests/tables/test_table_group.py @@ -7,33 +7,33 @@ def test_table_group(self, ome_zarr_image_v04_path: Path) -> None: ngff_image = NgffImage(ome_zarr_image_v04_path) - ngff_image.table.new( + ngff_image.tables.new( name="feat_table", table_type="feature_table", label_image="region", overwrite=True, ) - ngff_image.table.new( + ngff_image.tables.new( name="roi_table", table_type="roi_table", overwrite=False, ) - ngff_image.table.new( + ngff_image.tables.new( name="masking_roi_table", table_type="masking_roi_table", label_image="region", overwrite=False, ) - assert ngff_image.table.list() == [ + assert ngff_image.tables.list() == [ "feat_table", "roi_table", "masking_roi_table", ] - assert ngff_image.table.list(table_type="roi_table") == ["roi_table"] - assert ngff_image.table.list(table_type="feature_table") == ["feat_table"] - assert ngff_image.table.list(table_type="masking_roi_table") == [ + assert ngff_image.tables.list(table_type="roi_table") == ["roi_table"] + assert ngff_image.tables.list(table_type="feature_table") == ["feat_table"] + assert ngff_image.tables.list(table_type="masking_roi_table") == [ "masking_roi_table" ] diff --git a/tests/tables/test_v1_tables.py b/tests/tables/test_v1_tables.py index 9513ea5..a78e17b 100644 --- a/tests/tables/test_v1_tables.py +++ b/tests/tables/test_v1_tables.py @@ -8,7 +8,7 @@ def test_roi_table(self, ome_zarr_image_v04_path: Path) -> None: ngff_image = NgffImage(ome_zarr_image_v04_path) - roi_table = ngff_image.table.new( + roi_table = ngff_image.tables.new( name="roi_table", table_type="roi_table", overwrite=False, @@ -33,7 +33,7 @@ def test_masking_roi_table(self, ome_zarr_image_v04_path: Path) -> None: ngff_image = NgffImage(ome_zarr_image_v04_path) - masking_roi_table = ngff_image.table.new( + masking_roi_table = ngff_image.tables.new( name="masking_roi_table", table_type="masking_roi_table", label_image="region", @@ -46,7 +46,7 @@ def test_feature_table(self, ome_zarr_image_v04_path: Path) -> None: ngff_image = NgffImage(ome_zarr_image_v04_path) - feature_table = ngff_image.table.new( + feature_table = ngff_image.tables.new( name="feat_table", table_type="feature_table", label_image="region",