napari-mlarray 0.0.2__tar.gz → 0.0.3__tar.gz
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.
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/PKG-INFO +2 -1
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/pyproject.toml +2 -1
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/src/napari_mlarray/_reader.py +122 -101
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/src/napari_mlarray/_version.py +3 -3
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/src/napari_mlarray.egg-info/PKG-INFO +2 -1
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/src/napari_mlarray.egg-info/requires.txt +1 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/.copier-answers.yml +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/.github/ISSUE_TEMPLATE/task.md +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/.github/dependabot.yml +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/.github/workflows/test_and_deploy.yml +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/.gitignore +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/.napari-hub/DESCRIPTION.md +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/.napari-hub/config.yml +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/LICENSE +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/MANIFEST.in +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/README.md +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/setup.cfg +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/src/napari_mlarray/__init__.py +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/src/napari_mlarray/_widget.py +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/src/napari_mlarray/_writer.py +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/src/napari_mlarray/napari.yaml +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/src/napari_mlarray.egg-info/SOURCES.txt +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/src/napari_mlarray.egg-info/dependency_links.txt +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/src/napari_mlarray.egg-info/entry_points.txt +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/src/napari_mlarray.egg-info/top_level.txt +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/tests/__init__.py +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/tests/test_default.py +0 -0
- {napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: napari-mlarray
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: A reader/writer Napari plugin for MLArray images.
|
|
5
5
|
Author: Karol-G
|
|
6
6
|
Author-email: karol.gotkowski@dkfz.de
|
|
@@ -45,6 +45,7 @@ Description-Content-Type: text/markdown
|
|
|
45
45
|
License-File: LICENSE
|
|
46
46
|
Requires-Dist: mlarray
|
|
47
47
|
Requires-Dist: numpy
|
|
48
|
+
Requires-Dist: napari-bbox-fix
|
|
48
49
|
Provides-Extra: all
|
|
49
50
|
Requires-Dist: napari[all]; extra == "all"
|
|
50
51
|
Dynamic: license-file
|
|
@@ -9,68 +9,26 @@ from mlarray import MLArray
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
import numpy as np
|
|
11
11
|
|
|
12
|
+
# Ensure napari-bbox registers its custom layer type.
|
|
13
|
+
import napari_bbox # noqa: F401
|
|
14
|
+
|
|
12
15
|
|
|
13
16
|
def napari_get_reader(path):
|
|
14
|
-
"""A basic implementation of a Reader contribution.
|
|
15
|
-
|
|
16
|
-
Parameters
|
|
17
|
-
----------
|
|
18
|
-
path : str or list of str
|
|
19
|
-
Path to file, or list of paths.
|
|
20
|
-
|
|
21
|
-
Returns
|
|
22
|
-
-------
|
|
23
|
-
function or None
|
|
24
|
-
If the path is a recognized format, return a function that accepts the
|
|
25
|
-
same path or list of paths, and returns a list of layer data tuples.
|
|
26
|
-
"""
|
|
17
|
+
"""A basic implementation of a Reader contribution."""
|
|
27
18
|
if isinstance(path, list):
|
|
28
|
-
# reader plugins may be handed single path, or a list of paths.
|
|
29
|
-
# if it is a list, it is assumed to be an image stack...
|
|
30
|
-
# so we are only going to look at the first file.
|
|
31
19
|
path = path[0]
|
|
32
20
|
|
|
33
|
-
# the get_reader function should make as many checks as possible
|
|
34
|
-
# (without loading the full file) to determine if it can read
|
|
35
|
-
# the path. Here, we check the dtype of the array by loading
|
|
36
|
-
# it with memmap, so that we don't actually load the full array into memory.
|
|
37
|
-
# We pretend that this reader can only read integer arrays.
|
|
38
21
|
try:
|
|
39
22
|
if not str(path).endswith(".mla"):
|
|
40
23
|
return None
|
|
41
|
-
# napari_get_reader should never raise an exception, because napari
|
|
42
|
-
# raises its own specific errors depending on what plugins are
|
|
43
|
-
# available for the given path, so we catch
|
|
44
|
-
# the OSError that np.load might raise if the file is malformed
|
|
45
24
|
except OSError:
|
|
46
25
|
return None
|
|
47
26
|
|
|
48
|
-
# otherwise we return the *function* that can read ``path``.
|
|
49
27
|
return reader_function
|
|
50
28
|
|
|
51
29
|
|
|
52
30
|
def reader_function(path):
|
|
53
|
-
"""Take a path or list of paths and return a list of LayerData tuples.
|
|
54
|
-
|
|
55
|
-
Readers are expected to return data as a list of tuples, where each tuple
|
|
56
|
-
is (data, [add_kwargs, [layer_type]]), "add_kwargs" and "layer_type" are
|
|
57
|
-
both optional.
|
|
58
|
-
|
|
59
|
-
Parameters
|
|
60
|
-
----------
|
|
61
|
-
path : str or list of str
|
|
62
|
-
Path to file, or list of paths.
|
|
63
|
-
|
|
64
|
-
Returns
|
|
65
|
-
-------
|
|
66
|
-
layer_data : list of tuples
|
|
67
|
-
A list of LayerData tuples where each tuple in the list contains
|
|
68
|
-
(data, metadata, layer_type), where data is a numpy array, metadata is
|
|
69
|
-
a dict of keyword arguments for the corresponding viewer.add_* method
|
|
70
|
-
in napari, and layer_type is a lower-case string naming the type of
|
|
71
|
-
layer. Both "meta", and "layer_type" are optional. napari will
|
|
72
|
-
default to layer_type=="image" if not provided
|
|
73
|
-
"""
|
|
31
|
+
"""Take a path or list of paths and return a list of LayerData tuples."""
|
|
74
32
|
paths = [path] if isinstance(path, str) else path
|
|
75
33
|
layer_data = []
|
|
76
34
|
for path in paths:
|
|
@@ -82,30 +40,58 @@ def reader_function(path):
|
|
|
82
40
|
layer_type = "labels" if mlarray.meta.is_seg.is_seg == True else "image"
|
|
83
41
|
layer_data.append((data, metadata, layer_type))
|
|
84
42
|
if mlarray.meta.bbox.bboxes is not None:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
43
|
+
bboxes = np.asarray(mlarray.meta.bbox.bboxes)
|
|
44
|
+
|
|
45
|
+
# MLArray bboxes are always (N, D, 2)
|
|
46
|
+
if bboxes.ndim != 3 or bboxes.shape[2] != 2:
|
|
47
|
+
raise ValueError(f"Unsupported bbox shape: {bboxes.shape}")
|
|
48
|
+
|
|
49
|
+
dims = bboxes.shape[1]
|
|
50
|
+
|
|
51
|
+
# 2D -> keep shapes rectangles (original behavior)
|
|
52
|
+
if dims == 2:
|
|
53
|
+
data = bboxes_minmax_to_napari_rectangles_2d(bboxes)
|
|
54
|
+
edge_color = _napari_bbox_edge_colors(
|
|
55
|
+
data,
|
|
56
|
+
labels=getattr(mlarray.meta.bbox, "labels", None),
|
|
57
|
+
)
|
|
58
|
+
text = _napari_bbox_score_text(
|
|
59
|
+
scores=getattr(mlarray.meta.bbox, "scores", None),
|
|
60
|
+
labels=getattr(mlarray.meta.bbox, "labels", None),
|
|
61
|
+
count=len(data),
|
|
62
|
+
edge_color=edge_color,
|
|
63
|
+
rectangles=data,
|
|
64
|
+
)
|
|
65
|
+
metadata = {
|
|
66
|
+
"name": f"{name} (BBoxes)",
|
|
67
|
+
"shape_type": "rectangle",
|
|
68
|
+
"affine": mlarray.affine,
|
|
69
|
+
"metadata": mlarray.meta.to_mapping(),
|
|
70
|
+
"face_color": "transparent",
|
|
71
|
+
"edge_color": edge_color,
|
|
72
|
+
}
|
|
73
|
+
if text is not None:
|
|
74
|
+
metadata["text"] = text
|
|
75
|
+
layer_type = "shapes"
|
|
76
|
+
layer_data.append((data, metadata, layer_type))
|
|
77
|
+
|
|
78
|
+
# 3D+ -> napari-bbox layer
|
|
79
|
+
elif dims >= 3:
|
|
80
|
+
data = bboxes_minmax_to_napari_bboxes_nd(bboxes)
|
|
81
|
+
edge_color = _napari_bbox_edge_colors_count(
|
|
82
|
+
count=len(data),
|
|
83
|
+
labels=getattr(mlarray.meta.bbox, "labels", None),
|
|
84
|
+
)
|
|
85
|
+
metadata = {
|
|
86
|
+
"name": f"{name} (BBoxes)",
|
|
87
|
+
"affine": mlarray.affine,
|
|
88
|
+
"metadata": mlarray.meta.to_mapping(),
|
|
89
|
+
"face_color": "transparent",
|
|
90
|
+
"edge_color": edge_color,
|
|
91
|
+
# "edge_width": 2,
|
|
92
|
+
}
|
|
93
|
+
layer_type = "boundingboxlayer"
|
|
94
|
+
layer_data.append((data, metadata, layer_type))
|
|
109
95
|
return layer_data
|
|
110
96
|
|
|
111
97
|
|
|
@@ -115,34 +101,14 @@ def bboxes_minmax_to_napari_rectangles_2d(
|
|
|
115
101
|
dtype=np.float32,
|
|
116
102
|
validate: bool = True,
|
|
117
103
|
) -> np.ndarray:
|
|
118
|
-
"""
|
|
119
|
-
Convert 2D axis-aligned bounding boxes from min/max format to napari Shapes rectangles.
|
|
120
|
-
|
|
121
|
-
Accepted input formats (both mean the same thing):
|
|
122
|
-
1) (N, 2, 2): [[min_dim0, max_dim0], [min_dim1, max_dim1]]
|
|
123
|
-
Example (dim order is whatever you use, e.g. (y, x)):
|
|
124
|
-
[[[ymin, ymax], [xmin, xmax]], ...]
|
|
125
|
-
|
|
126
|
-
2) (N, 4): [min_dim0, min_dim1, max_dim0, max_dim1]
|
|
127
|
-
Example:
|
|
128
|
-
[[ymin, xmin, ymax, xmax], ...]
|
|
129
|
-
|
|
130
|
-
Output format (napari Shapes rectangle vertices):
|
|
131
|
-
(N, 4, 2) with vertices in non-twisting cyclic order:
|
|
132
|
-
(min0, min1) -> (min0, max1) -> (max0, max1) -> (max0, min1)
|
|
133
|
-
|
|
134
|
-
Raises:
|
|
135
|
-
ValueError if bboxes are not 2D (i.e., D != 2) or shapes are invalid.
|
|
136
|
-
"""
|
|
104
|
+
"""Convert 2D axis-aligned bounding boxes from min/max format to napari Shapes rectangles."""
|
|
137
105
|
arr = np.asarray(bboxes)
|
|
138
106
|
|
|
139
|
-
# Normalize input to shape (N, 2, 2)
|
|
140
107
|
if arr.ndim == 2 and arr.shape[1] == 4:
|
|
141
|
-
# (N, 4) -> (N, 2, 2)
|
|
142
108
|
arr = np.stack(
|
|
143
109
|
[
|
|
144
|
-
arr[:, [0, 2]],
|
|
145
|
-
arr[:, [1, 3]],
|
|
110
|
+
arr[:, [0, 2]],
|
|
111
|
+
arr[:, [1, 3]],
|
|
146
112
|
],
|
|
147
113
|
axis=1,
|
|
148
114
|
)
|
|
@@ -153,13 +119,20 @@ def bboxes_minmax_to_napari_rectangles_2d(
|
|
|
153
119
|
f"Expected bboxes of shape (N, 2, 2) or (N, 4). Got {arr.shape}."
|
|
154
120
|
)
|
|
155
121
|
|
|
156
|
-
N, D,
|
|
122
|
+
# MLArray uses (N, D, 2) -> convert to (N, 2, 2)
|
|
123
|
+
if arr.shape == (arr.shape[0], 2, 2):
|
|
124
|
+
arr2 = arr
|
|
125
|
+
else:
|
|
126
|
+
arr2 = np.transpose(arr, (0, 2, 1))
|
|
127
|
+
|
|
128
|
+
N, D, two = arr2.shape
|
|
157
129
|
if D != 2 or two != 2:
|
|
158
|
-
# Defensive; should never hit because of checks above.
|
|
159
130
|
raise ValueError(f"Only 2D bboxes are supported. Got (N, {D}, {two}).")
|
|
160
131
|
|
|
161
|
-
mins =
|
|
162
|
-
maxs =
|
|
132
|
+
mins = arr2[:, 0, :]
|
|
133
|
+
maxs = arr2[:, 1, :]
|
|
134
|
+
# Ensure proper min/max ordering even if input is flipped
|
|
135
|
+
mins, maxs = np.minimum(mins, maxs), np.maximum(mins, maxs)
|
|
163
136
|
|
|
164
137
|
if validate and np.any(maxs < mins):
|
|
165
138
|
bad = np.argwhere(maxs < mins)
|
|
@@ -171,7 +144,6 @@ def bboxes_minmax_to_napari_rectangles_2d(
|
|
|
171
144
|
min0, min1 = mins[:, 0], mins[:, 1]
|
|
172
145
|
max0, max1 = maxs[:, 0], maxs[:, 1]
|
|
173
146
|
|
|
174
|
-
# Cyclic order (no twisting):
|
|
175
147
|
rects = np.stack(
|
|
176
148
|
[
|
|
177
149
|
np.stack([min0, min1], axis=1),
|
|
@@ -185,6 +157,40 @@ def bboxes_minmax_to_napari_rectangles_2d(
|
|
|
185
157
|
return rects
|
|
186
158
|
|
|
187
159
|
|
|
160
|
+
def bboxes_minmax_to_napari_bboxes_nd(
|
|
161
|
+
bboxes,
|
|
162
|
+
*,
|
|
163
|
+
dtype=np.float32,
|
|
164
|
+
validate: bool = True,
|
|
165
|
+
):
|
|
166
|
+
"""
|
|
167
|
+
Convert N-D axis-aligned bboxes from min/max to napari-bbox format.
|
|
168
|
+
Input (MLArray): (N, D, 2) where [:, :, 0] are mins and [:, :, 1] are maxs.
|
|
169
|
+
Returns:
|
|
170
|
+
- list of (2, D) arrays, one per bbox.
|
|
171
|
+
"""
|
|
172
|
+
arr = np.asarray(bboxes)
|
|
173
|
+
|
|
174
|
+
if arr.ndim != 3 or arr.shape[2] != 2:
|
|
175
|
+
raise ValueError(
|
|
176
|
+
f"Expected bboxes of shape (N, D, 2). Got {arr.shape}."
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
mins = arr[:, :, 0]
|
|
180
|
+
maxs = arr[:, :, 1]
|
|
181
|
+
# Ensure proper min/max ordering even if input is flipped
|
|
182
|
+
mins, maxs = np.minimum(mins, maxs), np.maximum(mins, maxs)
|
|
183
|
+
if validate and np.any(maxs < mins):
|
|
184
|
+
bad = np.argwhere(maxs < mins)
|
|
185
|
+
raise ValueError(
|
|
186
|
+
"Found bbox with max < min at indices (bbox_index, dim): "
|
|
187
|
+
f"{bad[:10].tolist()}" + (" ..." if len(bad) > 10 else "")
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
arr2 = np.stack([mins, maxs], axis=1).astype(dtype, copy=False)
|
|
191
|
+
return [arr2[i] for i in range(arr2.shape[0])]
|
|
192
|
+
|
|
193
|
+
|
|
188
194
|
def _napari_bbox_edge_colors(rectangles, labels):
|
|
189
195
|
"""Return RGBA edge colors for each bbox."""
|
|
190
196
|
count = len(rectangles)
|
|
@@ -203,6 +209,23 @@ def _napari_bbox_edge_colors(rectangles, labels):
|
|
|
203
209
|
return colors
|
|
204
210
|
|
|
205
211
|
|
|
212
|
+
def _napari_bbox_edge_colors_count(count, labels=None):
|
|
213
|
+
"""Return RGBA edge colors for each bbox (count-based)."""
|
|
214
|
+
if count == 0:
|
|
215
|
+
return np.empty((0, 4), dtype=np.float32)
|
|
216
|
+
|
|
217
|
+
if labels is not None and len(labels) == count:
|
|
218
|
+
unique_labels = list(dict.fromkeys(labels))
|
|
219
|
+
label_to_color = {
|
|
220
|
+
label: _palette_rgba(idx) for idx, label in enumerate(unique_labels)
|
|
221
|
+
}
|
|
222
|
+
colors = np.array([label_to_color[label] for label in labels], dtype=np.float32)
|
|
223
|
+
else:
|
|
224
|
+
colors = np.array([_palette_rgba(idx) for idx in range(count)], dtype=np.float32)
|
|
225
|
+
|
|
226
|
+
return colors
|
|
227
|
+
|
|
228
|
+
|
|
206
229
|
def _napari_bbox_score_text(scores, labels, count, edge_color, rectangles):
|
|
207
230
|
"""Return napari Shapes text metadata if scores are provided."""
|
|
208
231
|
have_scores = scores is not None and len(scores) == count
|
|
@@ -210,7 +233,6 @@ def _napari_bbox_score_text(scores, labels, count, edge_color, rectangles):
|
|
|
210
233
|
if not have_scores and not have_labels:
|
|
211
234
|
return None
|
|
212
235
|
|
|
213
|
-
# Place text at the top-left corner of each rectangle.
|
|
214
236
|
top_left = rectangles[:, 0, :]
|
|
215
237
|
top_left = np.maximum(top_left - np.array([4.0, 0.0], dtype=top_left.dtype), 0)
|
|
216
238
|
|
|
@@ -221,7 +243,6 @@ def _napari_bbox_score_text(scores, labels, count, edge_color, rectangles):
|
|
|
221
243
|
parts.append(f"Label: {labels[idx]}")
|
|
222
244
|
if have_scores:
|
|
223
245
|
parts.append(f"Score: {scores[idx]:.3f}")
|
|
224
|
-
# Add a trailing empty line to create spacing below the score.
|
|
225
246
|
parts.append("\n")
|
|
226
247
|
strings.append("\n".join(parts))
|
|
227
248
|
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
31
|
+
__version__ = version = '0.0.3'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 3)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g5aeee1d3c'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: napari-mlarray
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: A reader/writer Napari plugin for MLArray images.
|
|
5
5
|
Author: Karol-G
|
|
6
6
|
Author-email: karol.gotkowski@dkfz.de
|
|
@@ -45,6 +45,7 @@ Description-Content-Type: text/markdown
|
|
|
45
45
|
License-File: LICENSE
|
|
46
46
|
Requires-Dist: mlarray
|
|
47
47
|
Requires-Dist: numpy
|
|
48
|
+
Requires-Dist: napari-bbox-fix
|
|
48
49
|
Provides-Extra: all
|
|
49
50
|
Requires-Dist: napari[all]; extra == "all"
|
|
50
51
|
Dynamic: license-file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{napari_mlarray-0.0.2 → napari_mlarray-0.0.3}/src/napari_mlarray.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|