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.
Files changed (37) hide show
  1. {mlarray-0.0.32 → mlarray-0.0.33}/PKG-INFO +1 -1
  2. {mlarray-0.0.32 → mlarray-0.0.33}/docs/schema.md +6 -4
  3. {mlarray-0.0.32 → mlarray-0.0.33}/examples/example_channel.py +2 -2
  4. {mlarray-0.0.32 → mlarray-0.0.33}/examples/example_metadata_only.py +2 -2
  5. {mlarray-0.0.32 → mlarray-0.0.33}/examples/example_open.py +2 -2
  6. {mlarray-0.0.32 → mlarray-0.0.33}/examples/example_save_load.py +2 -3
  7. {mlarray-0.0.32 → mlarray-0.0.33}/mlarray/meta.py +45 -22
  8. {mlarray-0.0.32 → mlarray-0.0.33}/mlarray.egg-info/PKG-INFO +1 -1
  9. {mlarray-0.0.32 → mlarray-0.0.33}/mlarray.egg-info/SOURCES.txt +1 -0
  10. mlarray-0.0.33/tests/test_bboxes.py +67 -0
  11. {mlarray-0.0.32 → mlarray-0.0.33}/.github/workflows/workflow.yml +0 -0
  12. {mlarray-0.0.32 → mlarray-0.0.33}/.gitignore +0 -0
  13. {mlarray-0.0.32 → mlarray-0.0.33}/LICENSE +0 -0
  14. {mlarray-0.0.32 → mlarray-0.0.33}/MANIFEST.in +0 -0
  15. {mlarray-0.0.32 → mlarray-0.0.33}/README.md +0 -0
  16. {mlarray-0.0.32 → mlarray-0.0.33}/assets/banner.png +0 -0
  17. {mlarray-0.0.32 → mlarray-0.0.33}/assets/banner.png~ +0 -0
  18. {mlarray-0.0.32 → mlarray-0.0.33}/docs/api.md +0 -0
  19. {mlarray-0.0.32 → mlarray-0.0.33}/docs/cli.md +0 -0
  20. {mlarray-0.0.32 → mlarray-0.0.33}/docs/index.md +0 -0
  21. {mlarray-0.0.32 → mlarray-0.0.33}/docs/optimization.md +0 -0
  22. {mlarray-0.0.32 → mlarray-0.0.33}/docs/usage.md +0 -0
  23. {mlarray-0.0.32 → mlarray-0.0.33}/docs/why.md +0 -0
  24. {mlarray-0.0.32 → mlarray-0.0.33}/mkdocs.yml +0 -0
  25. {mlarray-0.0.32 → mlarray-0.0.33}/mlarray/__init__.py +0 -0
  26. {mlarray-0.0.32 → mlarray-0.0.33}/mlarray/cli.py +0 -0
  27. {mlarray-0.0.32 → mlarray-0.0.33}/mlarray/mlarray.py +0 -0
  28. {mlarray-0.0.32 → mlarray-0.0.33}/mlarray/utils.py +0 -0
  29. {mlarray-0.0.32 → mlarray-0.0.33}/mlarray.egg-info/dependency_links.txt +0 -0
  30. {mlarray-0.0.32 → mlarray-0.0.33}/mlarray.egg-info/entry_points.txt +0 -0
  31. {mlarray-0.0.32 → mlarray-0.0.33}/mlarray.egg-info/requires.txt +0 -0
  32. {mlarray-0.0.32 → mlarray-0.0.33}/mlarray.egg-info/top_level.txt +0 -0
  33. {mlarray-0.0.32 → mlarray-0.0.33}/pyproject.toml +0 -0
  34. {mlarray-0.0.32 → mlarray-0.0.33}/setup.cfg +0 -0
  35. {mlarray-0.0.32 → mlarray-0.0.33}/tests/test_metadata.py +0 -0
  36. {mlarray-0.0.32 → mlarray-0.0.33}/tests/test_optimization.py +0 -0
  37. {mlarray-0.0.32 → mlarray-0.0.33}/tests/test_usage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlarray
3
- Version: 0.0.32
3
+ Version: 0.0.33
4
4
  Summary: Array format specialized for Machine Learning with Blosc2 backend and standardized metadata.
5
5
  Author-email: Karol Gotkowski <karol.gotkowski@dkfz.de>
6
6
  License: MIT
@@ -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` (single-key wrapper).
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 | description |
107
- | ------ | ------------------------------- | -------------------------------------------------------------------- |
108
- | bboxes | Optional[List[List[List[int]]]] | Bounding boxes shaped `[num_bboxes][ndims][2]` (min/max), ints only. |
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(SingleKeyBaseMeta):
579
- """Bounding boxes represented as per-dimension min/max pairs.
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 types."""
589
- if self.bboxes is None:
590
- return
591
-
592
- self.bboxes = _cast_to_list(self.bboxes, "meta.bbox.bboxes")
593
-
594
- if not isinstance(self.bboxes, list):
595
- raise TypeError("meta.bbox.bboxes must be a list")
596
-
597
- for bbox in self.bboxes:
598
- if not isinstance(bbox, list):
599
- raise TypeError("meta.bbox.bboxes must be a list of lists")
600
- for row in bbox:
601
- if not isinstance(row, list) or len(row) != 2:
602
- raise ValueError("meta.bbox.bboxes rows must have length 2")
603
- for v in row:
604
- if isinstance(v, bool) or not isinstance(v, int):
605
- raise TypeError("meta.bbox.bboxes must contain ints only")
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlarray
3
- Version: 0.0.32
3
+ Version: 0.0.33
4
4
  Summary: Array format specialized for Machine Learning with Blosc2 backend and standardized metadata.
5
5
  Author-email: Karol Gotkowski <karol.gotkowski@dkfz.de>
6
6
  License: MIT
@@ -34,6 +34,7 @@ mlarray.egg-info/dependency_links.txt
34
34
  mlarray.egg-info/entry_points.txt
35
35
  mlarray.egg-info/requires.txt
36
36
  mlarray.egg-info/top_level.txt
37
+ tests/test_bboxes.py
37
38
  tests/test_metadata.py
38
39
  tests/test_optimization.py
39
40
  tests/test_usage.py
@@ -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