mlarray 0.0.32__tar.gz → 0.0.33__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.
- {mlarray-0.0.32 → mlarray-0.0.33}/PKG-INFO +1 -1
- {mlarray-0.0.32 → mlarray-0.0.33}/docs/schema.md +6 -4
- {mlarray-0.0.32 → mlarray-0.0.33}/examples/example_channel.py +2 -2
- {mlarray-0.0.32 → mlarray-0.0.33}/examples/example_metadata_only.py +2 -2
- {mlarray-0.0.32 → mlarray-0.0.33}/examples/example_open.py +2 -2
- {mlarray-0.0.32 → mlarray-0.0.33}/examples/example_save_load.py +2 -3
- {mlarray-0.0.32 → mlarray-0.0.33}/mlarray/meta.py +45 -22
- {mlarray-0.0.32 → mlarray-0.0.33}/mlarray.egg-info/PKG-INFO +1 -1
- {mlarray-0.0.32 → mlarray-0.0.33}/mlarray.egg-info/SOURCES.txt +1 -0
- mlarray-0.0.33/tests/test_bboxes.py +67 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/.github/workflows/workflow.yml +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/.gitignore +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/LICENSE +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/MANIFEST.in +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/README.md +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/assets/banner.png +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/assets/banner.png~ +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/docs/api.md +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/docs/cli.md +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/docs/index.md +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/docs/optimization.md +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/docs/usage.md +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/docs/why.md +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/mkdocs.yml +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/mlarray/__init__.py +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/mlarray/cli.py +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/mlarray/mlarray.py +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/mlarray/utils.py +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/mlarray.egg-info/dependency_links.txt +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/mlarray.egg-info/entry_points.txt +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/mlarray.egg-info/requires.txt +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/mlarray.egg-info/top_level.txt +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/pyproject.toml +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/setup.cfg +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/tests/test_metadata.py +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/tests/test_optimization.py +0 -0
- {mlarray-0.0.32 → mlarray-0.0.33}/tests/test_usage.py +0 -0
|
@@ -97,15 +97,17 @@ This section stores precomputed global statistics for the array, which can be us
|
|
|
97
97
|
### bbox
|
|
98
98
|
|
|
99
99
|
* **Description:** Bounding boxes for objects/regions in the image.
|
|
100
|
-
* **Dataclass:** `MetaBbox
|
|
100
|
+
* **Dataclass:** `MetaBbox`.
|
|
101
101
|
* **Structure:** List of bboxes, each bbox is a list with length equal to image `ndims`,
|
|
102
102
|
and each entry is `[min, max]`.
|
|
103
103
|
|
|
104
104
|
Bounding boxes are stored in a normalized, axis-aligned representation that works across dimensionalities (2D, 3D, …). This is especially useful for detection-style workflows, ROI cropping, dataset summaries, and interactive visualization.
|
|
105
105
|
|
|
106
|
-
| field | type
|
|
107
|
-
| ------ |
|
|
108
|
-
| bboxes | Optional[List[List[List[int]]]]
|
|
106
|
+
| field | type | description |
|
|
107
|
+
| ------ | ----------------------------------------------- | --------------------------------------------------------------------------- |
|
|
108
|
+
| bboxes | Optional[List[List[List[Union[int, float]]]]] | Bounding boxes shaped `[num_bboxes][ndims][2]` (min/max), ints or floats. |
|
|
109
|
+
| scores | Optional[List[Union[int, float]]] | Optional confidence scores aligned with `bboxes`. |
|
|
110
|
+
| labels | Optional[List[Union[str, int, float]]] | Optional labels aligned with `bboxes`. |
|
|
109
111
|
|
|
110
112
|
---
|
|
111
113
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import os
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from mlarray import MLArray, Meta
|
|
4
|
+
from mlarray import MLArray, Meta, MetaBbox
|
|
5
5
|
import json
|
|
6
6
|
|
|
7
7
|
|
|
@@ -20,7 +20,7 @@ if __name__ == '__main__':
|
|
|
20
20
|
os.remove(filepath)
|
|
21
21
|
|
|
22
22
|
print("Initializing image...")
|
|
23
|
-
image = MLArray(array, spacing=spacing, origin=origin, direction=direction, channel_axis=channel_axis, meta=Meta(original=image_meta, bbox=bboxes))
|
|
23
|
+
image = MLArray(array, spacing=spacing, origin=origin, direction=direction, channel_axis=channel_axis, meta=Meta(original=image_meta, bbox=MetaBbox(bboxes)))
|
|
24
24
|
print("Saving image...")
|
|
25
25
|
image.save(filepath)
|
|
26
26
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import os
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from mlarray import MLArray, Meta
|
|
4
|
+
from mlarray import MLArray, Meta, MetaBbox
|
|
5
5
|
import json
|
|
6
6
|
|
|
7
7
|
|
|
@@ -19,7 +19,7 @@ if __name__ == '__main__':
|
|
|
19
19
|
os.remove(filepath)
|
|
20
20
|
|
|
21
21
|
print("Initializing image...")
|
|
22
|
-
image = MLArray(spacing=spacing, origin=origin, direction=direction, meta=Meta(original=image_meta, bbox=bboxes))
|
|
22
|
+
image = MLArray(spacing=spacing, origin=origin, direction=direction, meta=Meta(original=image_meta, bbox=MetaBbox(bboxes)))
|
|
23
23
|
print("Saving image...")
|
|
24
24
|
image.save(filepath)
|
|
25
25
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import os
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from mlarray import MLArray, Meta, MetaSpatial
|
|
4
|
+
from mlarray import MLArray, Meta, MetaSpatial, MetaBbox
|
|
5
5
|
import json
|
|
6
6
|
|
|
7
7
|
|
|
@@ -22,7 +22,7 @@ if __name__ == '__main__':
|
|
|
22
22
|
image = MLArray.open(filepath, shape=array.shape, dtype=array.dtype, mmap='w+')
|
|
23
23
|
print("Saving image...")
|
|
24
24
|
image[...] = array
|
|
25
|
-
image.meta.copy_from(Meta(original=image_meta, spatial=MetaSpatial(spacing=spacing, origin=origin, direction=direction), bbox=bboxes))
|
|
25
|
+
image.meta.copy_from(Meta(original=image_meta, spatial=MetaSpatial(spacing=spacing, origin=origin, direction=direction), bbox=MetaBbox(bboxes)))
|
|
26
26
|
image.meta.is_seg = True
|
|
27
27
|
image.close()
|
|
28
28
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import os
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from mlarray import MLArray, Meta
|
|
5
|
-
from mlarray.meta import MetaOriginal, MetaIsSeg
|
|
4
|
+
from mlarray import MLArray, Meta, MetaBbox
|
|
6
5
|
import json
|
|
7
6
|
|
|
8
7
|
|
|
@@ -20,7 +19,7 @@ if __name__ == '__main__':
|
|
|
20
19
|
os.remove(filepath)
|
|
21
20
|
|
|
22
21
|
print("Initializing image...")
|
|
23
|
-
image = MLArray(array, spacing=spacing, origin=origin, direction=direction, meta=Meta(original=image_meta, bbox=bboxes, is_seg=True))
|
|
22
|
+
image = MLArray(array, spacing=spacing, origin=origin, direction=direction, meta=Meta(original=image_meta, bbox=MetaBbox(bboxes), is_seg=True))
|
|
24
23
|
print("Saving image...")
|
|
25
24
|
image.save(filepath)
|
|
26
25
|
|
|
@@ -575,34 +575,57 @@ class MetaStatistics(BaseMeta):
|
|
|
575
575
|
|
|
576
576
|
|
|
577
577
|
@dataclass(slots=True)
|
|
578
|
-
class MetaBbox(
|
|
579
|
-
"""Bounding
|
|
578
|
+
class MetaBbox(BaseMeta):
|
|
579
|
+
"""Bounding box metadata with optional scores and labels.
|
|
580
580
|
|
|
581
581
|
Attributes:
|
|
582
582
|
bboxes: List of bounding boxes with shape [n_boxes, ndims, 2], where
|
|
583
|
-
each inner pair is [min, max] for a dimension. Values must be ints
|
|
583
|
+
each inner pair is [min, max] for a dimension. Values must be ints
|
|
584
|
+
or floats.
|
|
585
|
+
scores: Optional confidence scores aligned with bboxes (ints or floats).
|
|
586
|
+
labels: Optional labels aligned with bboxes. Each label may be a string,
|
|
587
|
+
int, or float.
|
|
584
588
|
"""
|
|
585
|
-
bboxes: Optional[List[List[List[int]]]] = None
|
|
589
|
+
bboxes: Optional[List[List[List[Union[int, float]]]]] = None
|
|
590
|
+
scores: Optional[List[Union[int, float]]] = None
|
|
591
|
+
labels: Optional[List[Union[str, int, float]]] = None
|
|
586
592
|
|
|
587
593
|
def _validate_and_cast(self, **_: Any) -> None:
|
|
588
|
-
"""Validate bounding box structure and
|
|
589
|
-
if self.bboxes is None:
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
594
|
+
"""Validate bounding box structure and related fields."""
|
|
595
|
+
if self.bboxes is not None:
|
|
596
|
+
self.bboxes = _cast_to_list(self.bboxes, "meta.bbox.bboxes")
|
|
597
|
+
|
|
598
|
+
if not isinstance(self.bboxes, list):
|
|
599
|
+
raise TypeError("meta.bbox.bboxes must be a list")
|
|
600
|
+
|
|
601
|
+
for b_i, bbox in enumerate(self.bboxes):
|
|
602
|
+
if not isinstance(bbox, list):
|
|
603
|
+
raise TypeError("meta.bbox.bboxes must be a list of lists")
|
|
604
|
+
for r_i, row in enumerate(bbox):
|
|
605
|
+
if not isinstance(row, list) or len(row) != 2:
|
|
606
|
+
raise ValueError("meta.bbox.bboxes rows must have length 2")
|
|
607
|
+
for v in row:
|
|
608
|
+
if isinstance(v, bool) or not isinstance(v, (float, int)):
|
|
609
|
+
raise TypeError("meta.bbox.bboxes must contain ints or floats only")
|
|
610
|
+
|
|
611
|
+
if self.scores is not None:
|
|
612
|
+
self.scores = _cast_to_list(self.scores, "meta.bbox.scores")
|
|
613
|
+
_validate_float_int_list(self.scores, "meta.bbox.scores")
|
|
614
|
+
|
|
615
|
+
if self.labels is not None:
|
|
616
|
+
self.labels = _cast_to_list(self.labels, "meta.bbox.labels")
|
|
617
|
+
if not isinstance(self.labels, list):
|
|
618
|
+
raise TypeError("meta.bbox.labels must be a list")
|
|
619
|
+
for v in self.labels:
|
|
620
|
+
if isinstance(v, bool) or not isinstance(v, (str, int, float)):
|
|
621
|
+
raise TypeError("meta.bbox.labels must contain only str, int, or float")
|
|
622
|
+
|
|
623
|
+
if self.bboxes is not None:
|
|
624
|
+
n = len(self.bboxes)
|
|
625
|
+
if self.scores is not None and len(self.scores) != n:
|
|
626
|
+
raise ValueError("meta.bbox.scores must have same length as bboxes")
|
|
627
|
+
if self.labels is not None and len(self.labels) != n:
|
|
628
|
+
raise ValueError("meta.bbox.labels must have same length as bboxes")
|
|
606
629
|
|
|
607
630
|
|
|
608
631
|
@dataclass(slots=True)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
import unittest
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from mlarray import MLArray
|
|
8
|
+
from mlarray.meta import Meta, MetaBbox
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _make_array(shape=(8, 16, 16), seed=0):
|
|
12
|
+
rng = np.random.default_rng(seed)
|
|
13
|
+
return rng.random(shape, dtype=np.float32)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestMetaBbox(unittest.TestCase):
|
|
17
|
+
def test_bbox_accepts_ints_and_floats(self):
|
|
18
|
+
bbox = MetaBbox(bboxes=[[[0, 1.5], [2, 3], [4.0, 5]]])
|
|
19
|
+
self.assertEqual(bbox.bboxes, [[[0, 1.5], [2, 3], [4.0, 5]]])
|
|
20
|
+
|
|
21
|
+
def test_bbox_scores_and_labels(self):
|
|
22
|
+
bbox = MetaBbox(
|
|
23
|
+
bboxes=[[[0, 1], [2, 3]]],
|
|
24
|
+
scores=[0.9],
|
|
25
|
+
labels=["lesion"],
|
|
26
|
+
)
|
|
27
|
+
self.assertEqual(bbox.scores, [0.9])
|
|
28
|
+
self.assertEqual(bbox.labels, ["lesion"])
|
|
29
|
+
|
|
30
|
+
def test_bbox_scores_labels_length_mismatch(self):
|
|
31
|
+
with self.assertRaises(ValueError):
|
|
32
|
+
MetaBbox(bboxes=[[[0, 1], [2, 3]]], scores=[0.9, 0.8])
|
|
33
|
+
with self.assertRaises(ValueError):
|
|
34
|
+
MetaBbox(bboxes=[[[0, 1], [2, 3]]], labels=["a", "b"])
|
|
35
|
+
|
|
36
|
+
def test_bbox_labels_type_validation(self):
|
|
37
|
+
with self.assertRaises(TypeError):
|
|
38
|
+
MetaBbox(bboxes=[[[0, 1], [2, 3]]], labels=[True])
|
|
39
|
+
|
|
40
|
+
def test_bbox_casts_numpy_and_tuple_inputs(self):
|
|
41
|
+
bboxes = np.array([[[0, 1], [2, 3]]], dtype=np.int64)
|
|
42
|
+
bbox = MetaBbox(bboxes=bboxes, scores=(0.5,), labels=("a",))
|
|
43
|
+
self.assertEqual(bbox.bboxes, [[[0, 1], [2, 3]]])
|
|
44
|
+
self.assertEqual(bbox.scores, [0.5])
|
|
45
|
+
self.assertEqual(bbox.labels, ["a"])
|
|
46
|
+
|
|
47
|
+
def test_bbox_roundtrip_mlarray(self):
|
|
48
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
49
|
+
array = _make_array()
|
|
50
|
+
meta = Meta(
|
|
51
|
+
bbox=MetaBbox(
|
|
52
|
+
bboxes=[[[0, 1], [2, 3], [4, 5]]],
|
|
53
|
+
scores=[0.7],
|
|
54
|
+
labels=[1],
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
image = MLArray(array, meta=meta)
|
|
58
|
+
|
|
59
|
+
path = Path(tmpdir) / "bbox.mla"
|
|
60
|
+
image.save(path)
|
|
61
|
+
|
|
62
|
+
loaded = MLArray(path)
|
|
63
|
+
self.assertEqual(loaded.meta.bbox.to_plain(), meta.bbox.to_plain())
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
unittest.main()
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|