diff --git a/src/ngio/core/label_handler.py b/src/ngio/core/label_handler.py index 8fe678c..108d4e4 100644 --- a/src/ngio/core/label_handler.py +++ b/src/ngio/core/label_handler.py @@ -216,19 +216,22 @@ def __init__( if not isinstance(group, zarr.Group): group = zarr.open_group(group, mode=self._mode) - if "labels" not in group: - self._group = group.create_group("labels") - self._group.attrs["labels"] = [] # initialize the labels attribute - else: - self._group = group["labels"] - assert isinstance(self._group, zarr.Group) + label_group = group.get("labels", None) + if label_group is None and mode != "r": + label_group = group.create_group("labels") + label_group.attrs["labels"] = [] # initialize the labels attribute + + assert isinstance(label_group, zarr.Group) or label_group is None + self._label_group = label_group self._image_ref = image_ref self._metadata_cache = cache def list(self) -> list[str]: """List all labels in the group.""" - _labels = self._group.attrs.get("labels", []) + if self._label_group is None: + return [] + _labels = self._label_group.attrs.get("labels", []) assert isinstance(_labels, list) return _labels @@ -259,6 +262,9 @@ def get_label( highest_resolution (bool, optional): Whether to get the highest resolution level """ + if self._label_group is None: + raise ValueError("No labels found in the group.") + if name not in self.list(): raise ValueError(f"Label {name} not found in the group.") @@ -266,7 +272,7 @@ def get_label( highest_resolution = False return Label( - store=self._group[name], + store=self._label_group[name], path=path, pixel_size=pixel_size, highest_resolution=highest_resolution, @@ -287,17 +293,20 @@ def derive( Default is False. **kwargs: Additional keyword arguments to pass to the new label. """ + if self._label_group is None: + raise ValueError("Cannot derive a new label. Group is empty or read-only.") + list_of_labels = self.list() if overwrite and name in list_of_labels: - self._group.attrs["label"] = [ + self._label_group.attrs["label"] = [ label for label in list_of_labels if label != name ] elif not overwrite and name in list_of_labels: raise ValueError(f"Label {name} already exists in the group.") # create the new label - new_label_group = self._group.create_group(name, overwrite=overwrite) + new_label_group = self._label_group.create_group(name, overwrite=overwrite) if self._image_ref is None: label_0 = self.get_label(list_of_labels[0]) @@ -349,5 +358,5 @@ def derive( ) if name not in self.list(): - self._group.attrs["labels"] = [*list_of_labels, name] + self._label_group.attrs["labels"] = [*list_of_labels, name] return self.get_label(name) diff --git a/src/ngio/tables/tables_group.py b/src/ngio/tables/tables_group.py index 45bdb70..c70fdde 100644 --- a/src/ngio/tables/tables_group.py +++ b/src/ngio/tables/tables_group.py @@ -11,6 +11,7 @@ from ngio.io import AccessModeLiteral, StoreLike from ngio.tables.v1 import FeatureTableV1, MaskingROITableV1, ROITableV1 +from ngio.utils import ngio_logger from ngio.utils._pydantic_utils import BaseWithExtraFields ROITable = ROITableV1 @@ -95,22 +96,37 @@ def __init__( if not isinstance(group, zarr.Group): group = zarr.open_group(group, mode=self._mode) - if "tables" not in group: - self._group = group.create_group("tables") - else: - self._group: zarr.Group = group["tables"] + table_group = group.get("tables", None) + if table_group is None and mode != "r": + table_group = group.create_group("tables") + table_group.attrs["tables"] = [] + + assert isinstance(table_group, zarr.Group) or table_group is None + self._table_group = table_group def _validate_list_of_tables(self, list_of_tables: list[str]) -> None: - """Validate the list of tables.""" - list_of_groups = list(self._group.group_keys()) + """Validate the list of tables. + + Args: + list_of_tables (list[str]): The list of tables to validate. + """ + if self._table_group is None: + return None for table_name in list_of_tables: - if table_name not in list_of_groups: - raise ValueError(f"Table {table_name} not found in the group.") + table = self._table_group.get(table_name, None) + if table is None: + ngio_logger.warning( + f"Table {table_name} not found in the group. " + "Consider removing it from the list of tables." + ) def _get_list_of_tables(self) -> list[str]: """Return the list of tables.""" - list_of_tables = self._group.attrs.get("tables", []) + if self._table_group is None: + return [] + + list_of_tables = self._table_group.attrs.get("tables", []) self._validate_list_of_tables(list_of_tables) assert isinstance(list_of_tables, list) assert all(isinstance(table_name, str) for table_name in list_of_tables) @@ -127,6 +143,9 @@ def list( If None, all tables are listed. Allowed values are: 'roi_table', 'feature_table', 'masking_roi_table'. """ + if self._table_group is None: + return [] + list_of_tables = self._get_list_of_tables() self._validate_list_of_tables(list_of_tables=list_of_tables) if table_type is None: @@ -140,7 +159,7 @@ def list( ) list_of_typed_tables = [] for table_name in list_of_tables: - table = self._group[table_name] + table = self._table_group[table_name] try: common_meta = CommonMeta(**table.attrs) if common_meta.type == table_type: @@ -173,12 +192,15 @@ def get_table( This is usually defined in the metadata of the table, if given here, it will overwrite the metadata. """ + if self._table_group is None: + raise ValueError("No tables group found in the group.") + list_of_tables = self._get_list_of_tables() if name not in list_of_tables: raise ValueError(f"Table {name} not found in the group.") return _get_table_impl( - group=self._group[name], + group=self._table_group[name], validate_metadata=validate_metadata, table_type=table_type, validate_table=validate_table, @@ -194,6 +216,9 @@ def new( **type_specific_kwargs: dict, ) -> Table: """Add a new table to the group.""" + if self._table_group is None: + raise ValueError("No tables group found in the group.") + list_of_tables = self._get_list_of_tables() if not overwrite and name in list_of_tables: raise ValueError(f"Table {name} already exists in the group.") @@ -203,13 +228,13 @@ def new( table_impl = _find_table_impl(table_type=table_type, version=version) new_table = table_impl._new( - parent_group=self._group, + parent_group=self._table_group, name=name, overwrite=overwrite, **type_specific_kwargs, ) - self._group.attrs["tables"] = [*list_of_tables, name] + self._table_group.attrs["tables"] = [*list_of_tables, name] assert isinstance(new_table, ROITable | FeatureTable | MaskingROITable) return new_table