-
Notifications
You must be signed in to change notification settings - Fork 21
/
_reader.py
168 lines (137 loc) · 5.84 KB
/
_reader.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
"""This module is a napari plugin.
It implements the ``napari_get_reader`` hook specification, (to create a reader plugin).
"""
import logging
import warnings
from typing import Any, Dict, Iterator, List, Optional
import numpy as np
from ome_zarr.io import parse_url
from ome_zarr.reader import Label, Node, Reader
from ome_zarr.types import LayerData, PathLike, ReaderFunction
from vispy.color import Colormap
LOGGER = logging.getLogger("napari_ome_zarr.reader")
# NB: color for labels, colormap for images
METADATA_KEYS = ("name", "visible", "contrast_limits", "colormap", "color", "metadata")
def napari_get_reader(path: PathLike) -> Optional[ReaderFunction]:
"""Returns a reader for supported paths that include IDR ID.
- URL of the form: https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.1/ID.zarr/
"""
if isinstance(path, list):
if len(path) > 1:
warnings.warn("more than one path is not currently supported")
path = path[0]
zarr = parse_url(path)
if zarr:
reader = Reader(zarr)
return transform(reader())
# Ignoring this path
return None
def transform_properties(
props: Optional[Dict[str, Dict]] = None
) -> Optional[Dict[str, List]]:
"""
Transform properties
Transform a dict of {label_id : {key: value, key2: value2}}
with a key for every LABEL
into a dict of a key for every VALUE, with a list of values for each
.. code::
{
"index": [1381342, 1381343...]
"omero:roiId": [1381342, 1381343...],
"omero:shapeId": [1682567, 1682567...]
}
"""
if props is None:
return None
properties: Dict[str, List] = {}
# First, create lists for all existing keys...
for label_id, props_dict in props.items():
for key in props_dict.keys():
properties[key] = []
keys = list(properties.keys())
properties["index"] = []
for label_id, props_dict in props.items():
properties["index"].append(label_id)
# ...in case some objects don't have all the keys
for key in keys:
properties[key].append(props_dict.get(key, None))
return properties
def transform_scale(
node_metadata: Dict, metadata: Dict, channel_axis: Optional[int]
) -> None:
"""
e.g. transformation is {"scale": [0.2, 0.06, 0.06]}
Get a list of these for each level in data. Just use first?
"""
if "coordinateTransformations" in node_metadata:
level_0_transforms = node_metadata["coordinateTransformations"][0]
for transf in level_0_transforms:
if "scale" in transf:
scale = transf["scale"]
if channel_axis is not None:
scale.pop(channel_axis)
metadata["scale"] = tuple(scale)
if "translation" in transf:
translate = transf["translation"]
if channel_axis is not None:
translate.pop(channel_axis)
metadata["translate"] = tuple(translate)
def transform(nodes: Iterator[Node]) -> Optional[ReaderFunction]:
def f(*args: Any, **kwargs: Any) -> List[LayerData]:
results: List[LayerData] = list()
for node in nodes:
data: List[Any] = node.data
metadata: Dict[str, Any] = {}
if data is None or len(data) < 1:
LOGGER.debug("skipping non-data %s", node)
else:
LOGGER.debug("transforming %s", node)
LOGGER.debug("node.metadata: %s", node.metadata)
layer_type: str = "image"
channel_axis = None
try:
ch_types = [axis["type"] for axis in node.metadata["axes"]]
if "channel" in ch_types:
channel_axis = ch_types.index("channel")
except Exception:
LOGGER.error("Error reading axes: Please update ome-zarr")
raise
transform_scale(node.metadata, metadata, channel_axis)
if node.load(Label):
layer_type = "labels"
for x in METADATA_KEYS:
if x in node.metadata:
metadata[x] = node.metadata[x]
if channel_axis is not None:
data = [
np.squeeze(level, axis=channel_axis) for level in node.data
]
else:
# Handle the removal of vispy requirement from ome-zarr-py
cms = node.metadata.get("colormap", [])
for idx, cm in enumerate(cms):
if not isinstance(cm, Colormap):
cms[idx] = Colormap(cm)
if channel_axis is not None:
# multi-channel; Copy known metadata values
metadata["channel_axis"] = channel_axis
for x in METADATA_KEYS:
if x in node.metadata:
metadata[x] = node.metadata[x]
else:
# single channel image, so metadata just needs
# single items (not lists)
for x in METADATA_KEYS:
if x in node.metadata:
try:
metadata[x] = node.metadata[x][0]
except Exception:
pass
properties = transform_properties(node.metadata.get("properties"))
if properties is not None:
metadata["properties"] = properties
rv: LayerData = (data, metadata, layer_type)
LOGGER.debug("Transformed: %s", rv)
results.append(rv)
return results
return f