napari-mlarray 0.0.1__py3-none-any.whl → 0.0.2__py3-none-any.whl

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/_reader.py CHANGED
@@ -6,6 +6,8 @@ implement multiple readers or even other plugin contributions. see:
6
6
  https://napari.org/stable/plugins/building_a_plugin/guides.html#readers
7
7
  """
8
8
  from mlarray import MLArray
9
+ from pathlib import Path
10
+ import numpy as np
9
11
 
10
12
 
11
13
  def napari_get_reader(path):
@@ -69,9 +71,201 @@ def reader_function(path):
69
71
  layer. Both "meta", and "layer_type" are optional. napari will
70
72
  default to layer_type=="image" if not provided
71
73
  """
72
- # handle both a string and a list of strings
73
74
  paths = [path] if isinstance(path, str) else path
74
- # load all files into array
75
- mlarrays = [MLArray.open(_path) for _path in paths]
76
- layer_data = [(mlarray, {"affine": mlarray.affine, "metadata": mlarray.meta.to_dict()}, "labels" if mlarray.meta.is_seg.is_seg == True else "image") for mlarray in mlarrays]
75
+ layer_data = []
76
+ for path in paths:
77
+ name = Path(path).stem
78
+ mlarray = MLArray.open(path)
79
+ if mlarray.meta._has_array.has_array == True:
80
+ data = mlarray
81
+ metadata = {"name": f"{name}", "affine": mlarray.affine, "metadata": mlarray.meta.to_mapping()}
82
+ layer_type = "labels" if mlarray.meta.is_seg.is_seg == True else "image"
83
+ layer_data.append((data, metadata, layer_type))
84
+ if mlarray.meta.bbox.bboxes is not None:
85
+ data = bboxes_minmax_to_napari_rectangles_2d(mlarray.meta.bbox.bboxes)
86
+ edge_color = _napari_bbox_edge_colors(
87
+ data,
88
+ labels=getattr(mlarray.meta.bbox, "labels", None),
89
+ )
90
+ text = _napari_bbox_score_text(
91
+ scores=getattr(mlarray.meta.bbox, "scores", None),
92
+ labels=getattr(mlarray.meta.bbox, "labels", None),
93
+ count=len(data),
94
+ edge_color=edge_color,
95
+ rectangles=data,
96
+ )
97
+ metadata = {
98
+ "name": f"{name} (BBoxes)",
99
+ "shape_type": "rectangle",
100
+ "affine": mlarray.affine,
101
+ "metadata": mlarray.meta.to_mapping(),
102
+ "face_color": "transparent",
103
+ "edge_color": edge_color,
104
+ }
105
+ if text is not None:
106
+ metadata["text"] = text
107
+ layer_type = "shapes"
108
+ layer_data.append((data, metadata, layer_type))
77
109
  return layer_data
110
+
111
+
112
+ def bboxes_minmax_to_napari_rectangles_2d(
113
+ bboxes,
114
+ *,
115
+ dtype=np.float32,
116
+ validate: bool = True,
117
+ ) -> 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
+ """
137
+ arr = np.asarray(bboxes)
138
+
139
+ # Normalize input to shape (N, 2, 2)
140
+ if arr.ndim == 2 and arr.shape[1] == 4:
141
+ # (N, 4) -> (N, 2, 2)
142
+ arr = np.stack(
143
+ [
144
+ arr[:, [0, 2]], # dim0: [min0, max0]
145
+ arr[:, [1, 3]], # dim1: [min1, max1]
146
+ ],
147
+ axis=1,
148
+ )
149
+ elif arr.ndim == 3 and arr.shape[1:] == (2, 2):
150
+ pass
151
+ else:
152
+ raise ValueError(
153
+ f"Expected bboxes of shape (N, 2, 2) or (N, 4). Got {arr.shape}."
154
+ )
155
+
156
+ N, D, two = arr.shape
157
+ if D != 2 or two != 2:
158
+ # Defensive; should never hit because of checks above.
159
+ raise ValueError(f"Only 2D bboxes are supported. Got (N, {D}, {two}).")
160
+
161
+ mins = arr[:, :, 0]
162
+ maxs = arr[:, :, 1]
163
+
164
+ if validate and np.any(maxs < mins):
165
+ bad = np.argwhere(maxs < mins)
166
+ raise ValueError(
167
+ "Found bbox with max < min at indices (bbox_index, dim): "
168
+ f"{bad[:10].tolist()}" + (" ..." if len(bad) > 10 else "")
169
+ )
170
+
171
+ min0, min1 = mins[:, 0], mins[:, 1]
172
+ max0, max1 = maxs[:, 0], maxs[:, 1]
173
+
174
+ # Cyclic order (no twisting):
175
+ rects = np.stack(
176
+ [
177
+ np.stack([min0, min1], axis=1),
178
+ np.stack([min0, max1], axis=1),
179
+ np.stack([max0, max1], axis=1),
180
+ np.stack([max0, min1], axis=1),
181
+ ],
182
+ axis=1,
183
+ ).astype(dtype, copy=False)
184
+
185
+ return rects
186
+
187
+
188
+ def _napari_bbox_edge_colors(rectangles, labels):
189
+ """Return RGBA edge colors for each bbox."""
190
+ count = len(rectangles)
191
+ if count == 0:
192
+ return np.empty((0, 4), dtype=np.float32)
193
+
194
+ if labels is not None and len(labels) == count:
195
+ unique_labels = list(dict.fromkeys(labels))
196
+ label_to_color = {
197
+ label: _palette_rgba(idx) for idx, label in enumerate(unique_labels)
198
+ }
199
+ colors = np.array([label_to_color[label] for label in labels], dtype=np.float32)
200
+ else:
201
+ colors = np.array([_palette_rgba(idx) for idx in range(count)], dtype=np.float32)
202
+
203
+ return colors
204
+
205
+
206
+ def _napari_bbox_score_text(scores, labels, count, edge_color, rectangles):
207
+ """Return napari Shapes text metadata if scores are provided."""
208
+ have_scores = scores is not None and len(scores) == count
209
+ have_labels = labels is not None and len(labels) == count
210
+ if not have_scores and not have_labels:
211
+ return None
212
+
213
+ # Place text at the top-left corner of each rectangle.
214
+ top_left = rectangles[:, 0, :]
215
+ top_left = np.maximum(top_left - np.array([4.0, 0.0], dtype=top_left.dtype), 0)
216
+
217
+ strings = []
218
+ for idx in range(count):
219
+ parts = []
220
+ if have_labels:
221
+ parts.append(f"Label: {labels[idx]}")
222
+ if have_scores:
223
+ parts.append(f"Score: {scores[idx]:.3f}")
224
+ # Add a trailing empty line to create spacing below the score.
225
+ parts.append("\n")
226
+ strings.append("\n".join(parts))
227
+
228
+ return {
229
+ "string": strings,
230
+ "color": edge_color,
231
+ "size": 12,
232
+ "anchor": "upper_left",
233
+ "position": top_left,
234
+ }
235
+
236
+
237
+ def _palette_rgba(index):
238
+ """Simple, distinct-ish palette; returns RGBA in 0..1."""
239
+ palette = [
240
+ (0.90, 0.10, 0.12, 1.0),
241
+ (0.00, 0.48, 1.00, 1.0),
242
+ (0.20, 0.80, 0.20, 1.0),
243
+ (0.98, 0.60, 0.00, 1.0),
244
+ (0.60, 0.20, 0.80, 1.0),
245
+ (0.10, 0.75, 0.80, 1.0),
246
+ (0.80, 0.80, 0.00, 1.0),
247
+ (0.95, 0.40, 0.60, 1.0),
248
+ (0.90, 0.30, 0.00, 1.0),
249
+ (0.00, 0.70, 0.40, 1.0),
250
+ (0.40, 0.80, 1.00, 1.0),
251
+ (1.00, 0.20, 0.70, 1.0),
252
+ (0.50, 0.90, 0.20, 1.0),
253
+ (0.20, 0.90, 0.70, 1.0),
254
+ (0.70, 0.50, 1.00, 1.0),
255
+ (1.00, 0.50, 0.20, 1.0),
256
+ (0.20, 0.60, 1.00, 1.0),
257
+ (1.00, 0.70, 0.20, 1.0),
258
+ (0.60, 1.00, 0.20, 1.0),
259
+ (0.20, 1.00, 0.40, 1.0),
260
+ (0.20, 1.00, 0.90, 1.0),
261
+ (0.20, 0.90, 1.00, 1.0),
262
+ (0.40, 0.60, 1.00, 1.0),
263
+ (0.80, 0.20, 1.00, 1.0),
264
+ (1.00, 0.20, 0.30, 1.0),
265
+ (1.00, 0.30, 0.50, 1.0),
266
+ (1.00, 0.60, 0.60, 1.0),
267
+ (1.00, 0.90, 0.30, 1.0),
268
+ (0.60, 1.00, 0.60, 1.0),
269
+ (0.60, 0.90, 1.00, 1.0),
270
+ ]
271
+ return palette[index % len(palette)]
@@ -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.1'
32
- __version_tuple__ = version_tuple = (0, 0, 1)
31
+ __version__ = version = '0.0.2'
32
+ __version_tuple__ = version_tuple = (0, 0, 2)
33
33
 
34
34
  __commit_id__ = commit_id = None
napari_mlarray/_writer.py CHANGED
@@ -34,7 +34,7 @@ def write_single_image(path: str, data: Any, meta: dict) -> list[str]:
34
34
  -------
35
35
  [path] : A list containing the string path to the saved file.
36
36
  """
37
- mlarray = MLArray(data, meta=Meta.from_dict(meta["metadata"]))
37
+ mlarray = MLArray(data, meta=Meta.from_mapping(meta["metadata"]))
38
38
  mlarray.save(path)
39
39
 
40
40
  # return path to any file(s) that were successfully written
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: napari-mlarray
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: A reader/writer Napari plugin for MLArray images.
5
5
  Author: Karol-G
6
6
  Author-email: karol.gotkowski@dkfz.de
@@ -44,6 +44,7 @@ Requires-Python: >=3.10
44
44
  Description-Content-Type: text/markdown
45
45
  License-File: LICENSE
46
46
  Requires-Dist: mlarray
47
+ Requires-Dist: numpy
47
48
  Provides-Extra: all
48
49
  Requires-Dist: napari[all]; extra == "all"
49
50
  Dynamic: license-file
@@ -0,0 +1,12 @@
1
+ napari_mlarray/__init__.py,sha256=c2b0_m5sORovlHyyozJS1l00lXjFM7ULIkrHqojq5N4,249
2
+ napari_mlarray/_reader.py,sha256=IvFdBZnA3AaTt_wqh9B_q_7tXVV2N3nr722thUCAncY,9373
3
+ napari_mlarray/_version.py,sha256=huLsL1iGeXWQKZ8bjwDdIWC7JOkj3wnzBh-HFMZl1PY,704
4
+ napari_mlarray/_widget.py,sha256=K6MYwgFiQg7-dORp-kC_VivrfppiUwMmevbPMhwMT9c,4810
5
+ napari_mlarray/_writer.py,sha256=EG-013mGR14L5V7tygzoZg2EPyIwnZAP8zG8flg58hU,1209
6
+ napari_mlarray/napari.yaml,sha256=HILzIxDmFpRPgCmM7uVzzZ86Ayp2ecWiSXHTCC1qjx4,796
7
+ napari_mlarray-0.0.2.dist-info/licenses/LICENSE,sha256=LKlNG6Bx5z0YnzAp9GjCxCR0aypSPO_JcUHmuVGtwds,1162
8
+ napari_mlarray-0.0.2.dist-info/METADATA,sha256=LYZJf7Fan_qTRKnxY_KOxAJnBkfbpt8pTYn-_xa58Wk,5562
9
+ napari_mlarray-0.0.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
10
+ napari_mlarray-0.0.2.dist-info/entry_points.txt,sha256=ibu_ymiLzJPNpL0x0Fdendi5fQiq_baxx224E5pAW0c,62
11
+ napari_mlarray-0.0.2.dist-info/top_level.txt,sha256=ZfJPiLTSmZ9eakEU1J6znzWi6dS_OTGAXQSI08HOgQg,15
12
+ napari_mlarray-0.0.2.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- napari_mlarray/__init__.py,sha256=c2b0_m5sORovlHyyozJS1l00lXjFM7ULIkrHqojq5N4,249
2
- napari_mlarray/_reader.py,sha256=TT6-L8t7wlYRA5laoVH-frf-ogBxXlEQWE7y70IUHHs,3045
3
- napari_mlarray/_version.py,sha256=qf6R-J7-UyuABBo8c0HgaquJ8bejVbf07HodXgwAwgQ,704
4
- napari_mlarray/_widget.py,sha256=K6MYwgFiQg7-dORp-kC_VivrfppiUwMmevbPMhwMT9c,4810
5
- napari_mlarray/_writer.py,sha256=tAFCX1-LnmDqeDFpO2H22Xrju58-3D-d0ZrEvdAXMbE,1206
6
- napari_mlarray/napari.yaml,sha256=HILzIxDmFpRPgCmM7uVzzZ86Ayp2ecWiSXHTCC1qjx4,796
7
- napari_mlarray-0.0.1.dist-info/licenses/LICENSE,sha256=LKlNG6Bx5z0YnzAp9GjCxCR0aypSPO_JcUHmuVGtwds,1162
8
- napari_mlarray-0.0.1.dist-info/METADATA,sha256=tHD_AJuOHEEWg3OU6FepCJSMQDbVPHtCGoVa1y6ZBHg,5541
9
- napari_mlarray-0.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
10
- napari_mlarray-0.0.1.dist-info/entry_points.txt,sha256=ibu_ymiLzJPNpL0x0Fdendi5fQiq_baxx224E5pAW0c,62
11
- napari_mlarray-0.0.1.dist-info/top_level.txt,sha256=ZfJPiLTSmZ9eakEU1J6znzWi6dS_OTGAXQSI08HOgQg,15
12
- napari_mlarray-0.0.1.dist-info/RECORD,,