From 7e77cf9c0bdb8f4345a7dc721ce41b3ac68080b9 Mon Sep 17 00:00:00 2001 From: Draga Doncila Pop Date: Mon, 16 Nov 2020 18:12:52 +1100 Subject: [PATCH 1/7] Read properties from zattrs and pass to napari reader --- ome_zarr/napari.py | 18 ++++++++++++++++++ ome_zarr/reader.py | 9 +++++++++ 2 files changed, 27 insertions(+) diff --git a/ome_zarr/napari.py b/ome_zarr/napari.py index ec172b5f..f56ab52e 100644 --- a/ome_zarr/napari.py +++ b/ome_zarr/napari.py @@ -62,6 +62,24 @@ def f(*args: Any, **kwargs: Any) -> List[LayerData]: layer_type = "labels" if "colormap" in metadata: del metadata["colormap"] + if "properties" in metadata: + props = metadata["properties"] + reader_props = {} + label_indices = list(props.keys()) + reader_props["index"] = label_indices + + # properties may be ragged, so we need to know all possible properties + all_keys = set() + for index in label_indices: + all_keys = all_keys.union(set(props[index].keys())) + + # napari expects lists of equal length so we must fill with None + for prop_key in all_keys: + reader_props[prop_key] = [ + props[i][prop_key] if prop_key in props[i] else "None" + for i in label_indices + ] + metadata['properties'] = reader_props elif shape[CHANNEL_DIMENSION] > 1: metadata["channel_axis"] = CHANNEL_DIMENSION diff --git a/ome_zarr/reader.py b/ome_zarr/reader.py index 2cd93a7b..d8ab8d6e 100644 --- a/ome_zarr/reader.py +++ b/ome_zarr/reader.py @@ -216,6 +216,14 @@ def __init__(self, node: Node) -> None: except Exception as e: LOGGER.error(f"invalid color - {color}: {e}") + properties: Dict[int, Dict[str, str]] = {} + props_list = image_label.get('properties', []) + if props_list: + for props in props_list: + label_val = props['label-value'] + del props['label-value'] + properties[label_val] = props + # TODO: a metadata transform should be provided by specific impls. name = self.zarr.basename() node.metadata.update( @@ -223,6 +231,7 @@ def __init__(self, node: Node) -> None: "visible": node.visible, "name": name, "color": colors, + "properties": properties, "metadata": {"image": self.lookup("image", {}), "path": name}, } ) From d1d8373c7de495145f806e3871538b0d4fc0fc32 Mon Sep 17 00:00:00 2001 From: Draga Doncila Pop Date: Tue, 17 Nov 2020 16:54:13 +1100 Subject: [PATCH 2/7] Resolving comments --- ome_zarr/napari.py | 2 +- ome_zarr/reader.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ome_zarr/napari.py b/ome_zarr/napari.py index f56ab52e..74eea9b6 100644 --- a/ome_zarr/napari.py +++ b/ome_zarr/napari.py @@ -76,7 +76,7 @@ def f(*args: Any, **kwargs: Any) -> List[LayerData]: # napari expects lists of equal length so we must fill with None for prop_key in all_keys: reader_props[prop_key] = [ - props[i][prop_key] if prop_key in props[i] else "None" + props[i][prop_key] if prop_key in props[i] else None for i in label_indices ] metadata['properties'] = reader_props diff --git a/ome_zarr/reader.py b/ome_zarr/reader.py index d8ab8d6e..61180873 100644 --- a/ome_zarr/reader.py +++ b/ome_zarr/reader.py @@ -221,8 +221,8 @@ def __init__(self, node: Node) -> None: if props_list: for props in props_list: label_val = props['label-value'] - del props['label-value'] - properties[label_val] = props + properties[label_val] = dict(props) + del properties[label_val]['label-value'] # TODO: a metadata transform should be provided by specific impls. name = self.zarr.basename() @@ -231,10 +231,16 @@ def __init__(self, node: Node) -> None: "visible": node.visible, "name": name, "color": colors, - "properties": properties, "metadata": {"image": self.lookup("image", {}), "path": name}, } ) + if properties: + node.metadata.update( + { + "properties": properties + + } + ) class Multiscales(Spec): From d99ae0a07cb36987437e069cd3e36eaf9c7cb076 Mon Sep 17 00:00:00 2001 From: Draga Doncila Pop Date: Tue, 17 Nov 2020 17:07:17 +1100 Subject: [PATCH 3/7] Fix formatting --- ome_zarr/reader.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ome_zarr/reader.py b/ome_zarr/reader.py index 61180873..c21a0b20 100644 --- a/ome_zarr/reader.py +++ b/ome_zarr/reader.py @@ -242,7 +242,6 @@ def __init__(self, node: Node) -> None: } ) - class Multiscales(Spec): @staticmethod def matches(zarr: BaseZarrLocation) -> bool: From 59c031eb9cb0bf80b25158f29bf318078f441d72 Mon Sep 17 00:00:00 2001 From: Draga Doncila Pop Date: Tue, 17 Nov 2020 17:14:46 +1100 Subject: [PATCH 4/7] Fix precommit issues --- ome_zarr/napari.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ome_zarr/napari.py b/ome_zarr/napari.py index 74eea9b6..31185af3 100644 --- a/ome_zarr/napari.py +++ b/ome_zarr/napari.py @@ -6,7 +6,7 @@ import logging import warnings -from typing import Any, Callable, Dict, Iterator, List, Optional +from typing import Any, Callable, Dict, Iterator, List, Optional, Set from .data import CHANNEL_DIMENSION from .io import parse_url @@ -68,7 +68,8 @@ def f(*args: Any, **kwargs: Any) -> List[LayerData]: label_indices = list(props.keys()) reader_props["index"] = label_indices - # properties may be ragged, so we need to know all possible properties + # properties may be ragged, so we need all possible properties + all_keys: Set[str] all_keys = set() for index in label_indices: all_keys = all_keys.union(set(props[index].keys())) @@ -79,7 +80,7 @@ def f(*args: Any, **kwargs: Any) -> List[LayerData]: props[i][prop_key] if prop_key in props[i] else None for i in label_indices ] - metadata['properties'] = reader_props + metadata["properties"] = reader_props elif shape[CHANNEL_DIMENSION] > 1: metadata["channel_axis"] = CHANNEL_DIMENSION From a4b5a3ef1fdb13b17ff7506bb5a2928ea7cf30f5 Mon Sep 17 00:00:00 2001 From: jmoore Date: Tue, 17 Nov 2020 17:00:36 +0100 Subject: [PATCH 5/7] More precommit issues --- ome_zarr/reader.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/ome_zarr/reader.py b/ome_zarr/reader.py index c21a0b20..9a6cb3c9 100644 --- a/ome_zarr/reader.py +++ b/ome_zarr/reader.py @@ -217,12 +217,12 @@ def __init__(self, node: Node) -> None: LOGGER.error(f"invalid color - {color}: {e}") properties: Dict[int, Dict[str, str]] = {} - props_list = image_label.get('properties', []) + props_list = image_label.get("properties", []) if props_list: for props in props_list: - label_val = props['label-value'] + label_val = props["label-value"] properties[label_val] = dict(props) - del properties[label_val]['label-value'] + del properties[label_val]["label-value"] # TODO: a metadata transform should be provided by specific impls. name = self.zarr.basename() @@ -235,12 +235,8 @@ def __init__(self, node: Node) -> None: } ) if properties: - node.metadata.update( - { - "properties": properties + node.metadata.update({"properties": properties}) - } - ) class Multiscales(Spec): @staticmethod From d1b8e3730f1d2f394d87431f7f6f64b2e33938d9 Mon Sep 17 00:00:00 2001 From: jmoore Date: Tue, 17 Nov 2020 18:39:59 +0100 Subject: [PATCH 6/7] Update setup-miniconda action --- .github/workflows/posix.yml | 2 +- .github/workflows/windows.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/posix.yml b/.github/workflows/posix.yml index 767900f6..a3e2bee2 100644 --- a/.github/workflows/posix.yml +++ b/.github/workflows/posix.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@v2 - name: Setup miniconda - uses: conda-incubator/setup-miniconda@v1 + uses: conda-incubator/setup-miniconda@v2 with: auto-update-conda: true channels: conda-forge,ome diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index aecee50f..d92d0c29 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v2 - name: Setup miniconda - uses: conda-incubator/setup-miniconda@v1 + uses: conda-incubator/setup-miniconda@v2 with: auto-update-conda: true channels: conda-forge,ome From 391545c43fab838b22a585a51704bc3bd57463d1 Mon Sep 17 00:00:00 2001 From: Draga Doncila Pop Date: Sat, 21 Nov 2020 10:30:21 +1100 Subject: [PATCH 7/7] Add test for label properties --- ome_zarr/data.py | 3 +++ tests/test_napari.py | 10 ++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/ome_zarr/data.py b/ome_zarr/data.py index cafa460f..b467f564 100644 --- a/ome_zarr/data.py +++ b/ome_zarr/data.py @@ -155,11 +155,14 @@ def create_zarr( write_multiscale(labels, label_grp) colors = [] + properties = [] for x in range(1, 9): rgba = [randrange(0, 256) for i in range(4)] colors.append({"label-value": x, "rgba": rgba}) + properties.append({"label-value": x, "class": f"class {x}"}) label_grp.attrs["image-label"] = { "version": "0.1", "colors": colors, + "properties": properties, "source": {"image": "../../"}, } diff --git a/tests/test_napari.py b/tests/test_napari.py index d30f20af..12388f53 100644 --- a/tests/test_napari.py +++ b/tests/test_napari.py @@ -13,7 +13,7 @@ def initdir(self, tmpdir): self.path = tmpdir.mkdir("data") create_zarr(str(self.path), astronaut, "astronaut") - def assert_layers(self, layers, visible_1, visible_2): + def assert_layers(self, layers, visible_1, visible_2, label_props=None): # TODO: check name assert len(layers) == 2 @@ -27,6 +27,8 @@ def assert_layers(self, layers, visible_1, visible_2): data, metadata, layer_type = self.assert_layer(label) assert visible_2 == metadata["visible"] + if label_props: + assert label_props == metadata["properties"] def assert_layer(self, layer_data): data, metadata, layer_type = layer_data @@ -47,7 +49,11 @@ def test_labels(self): def test_label(self): filename = str(self.path.join("labels", "astronaut")) layers = napari_get_reader(filename)() - self.assert_layers(layers, False, True) + properties = { + "index": [i for i in range(1, 9)], + "class": [f"class {i}" for i in range(1, 9)], + } + self.assert_layers(layers, False, True, properties) @pytest.mark.skipif( not sys.platform.startswith("darwin") or sys.version_info < (3, 7),