valor-lite 0.34.3__py3-none-any.whl → 0.36.0__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.

Potentially problematic release.


This version of valor-lite might be problematic. Click here for more details.

@@ -1,5 +1,5 @@
1
- from .annotation import Bitmask, Segmentation, generate_segmentation
2
- from .manager import DataLoader, Evaluator
1
+ from .annotation import Bitmask, Segmentation
2
+ from .manager import DataLoader, Evaluator, Filter
3
3
  from .metric import Metric, MetricType
4
4
 
5
5
  __all__ = [
@@ -9,5 +9,5 @@ __all__ = [
9
9
  "Bitmask",
10
10
  "Metric",
11
11
  "MetricType",
12
- "generate_segmentation",
12
+ "Filter",
13
13
  ]
@@ -68,112 +68,41 @@ class Segmentation:
68
68
  uid: str
69
69
  groundtruths: list[Bitmask]
70
70
  predictions: list[Bitmask]
71
- shape: tuple[int, ...] = field(default_factory=lambda: (0, 0))
71
+ shape: tuple[int, ...]
72
72
  size: int = field(default=0)
73
73
 
74
74
  def __post_init__(self):
75
75
 
76
- groundtruth_shape = {
77
- groundtruth.mask.shape for groundtruth in self.groundtruths
78
- }
79
- prediction_shape = {
80
- prediction.mask.shape for prediction in self.predictions
81
- }
82
- if len(groundtruth_shape) == 0:
83
- raise ValueError("The segmenation is missing ground truths.")
84
- elif len(prediction_shape) == 0:
85
- raise ValueError("The segmenation is missing predictions.")
86
- elif (
87
- len(groundtruth_shape) != 1
88
- or len(prediction_shape) != 1
89
- or groundtruth_shape != prediction_shape
90
- ):
76
+ if len(self.shape) != 2 or self.shape[0] <= 0 or self.shape[1] <= 0:
91
77
  raise ValueError(
92
- "A shape mismatch exists within the segmentation."
78
+ f"segmentations must be 2-dimensional and have non-zero dimensions. Recieved shape '{self.shape}'"
93
79
  )
94
-
95
- self.shape = groundtruth_shape.pop()
96
- self.size = int(np.prod(np.array(self.shape)))
97
-
98
-
99
- def generate_segmentation(
100
- datum_uid: str,
101
- number_of_unique_labels: int,
102
- mask_height: int,
103
- mask_width: int,
104
- ) -> Segmentation:
105
- """
106
- Generates a semantic segmentation annotation.
107
-
108
- Parameters
109
- ----------
110
- datum_uid : str
111
- The datum UID for the generated segmentation.
112
- number_of_unique_labels : int
113
- The number of unique labels.
114
- mask_height : int
115
- The height of the mask in pixels.
116
- mask_width : int
117
- The width of the mask in pixels.
118
-
119
- Returns
120
- -------
121
- Segmentation
122
- A generated semantic segmenatation annotation.
123
- """
124
-
125
- if number_of_unique_labels > 1:
126
- common_proba = 0.4 / (number_of_unique_labels - 1)
127
- min_proba = min(common_proba, 0.1)
128
- labels = [str(i) for i in range(number_of_unique_labels)] + [None]
129
- proba = (
130
- [0.5]
131
- + [common_proba for _ in range(number_of_unique_labels - 1)]
132
- + [0.1]
133
- )
134
- elif number_of_unique_labels == 1:
135
- labels = ["0", None]
136
- proba = [0.9, 0.1]
137
- min_proba = 0.1
138
- else:
139
- raise ValueError(
140
- "The number of unique labels should be greater than zero."
141
- )
142
-
143
- probabilities = np.array(proba, dtype=np.float64)
144
- weights = (probabilities / min_proba).astype(np.int32)
145
-
146
- indices = np.random.choice(
147
- np.arange(len(weights)),
148
- size=(mask_height * 2, mask_width),
149
- p=probabilities,
150
- )
151
-
152
- N = len(labels)
153
-
154
- masks = np.arange(N)[:, None, None] == indices
155
-
156
- gts = []
157
- pds = []
158
- for lidx in range(N):
159
- label = labels[lidx]
160
- if label is None:
161
- continue
162
- gts.append(
163
- Bitmask(
164
- mask=masks[lidx, :mask_height, :],
165
- label=label,
166
- )
167
- )
168
- pds.append(
169
- Bitmask(
170
- mask=masks[lidx, mask_height:, :],
171
- label=label,
172
- )
173
- )
174
-
175
- return Segmentation(
176
- uid=datum_uid,
177
- groundtruths=gts,
178
- predictions=pds,
179
- )
80
+ self.size = self.shape[0] * self.shape[1]
81
+
82
+ mask_accumulation = None
83
+ for groundtruth in self.groundtruths:
84
+ if self.shape != groundtruth.mask.shape:
85
+ raise ValueError(
86
+ f"ground truth masks for datum '{self.uid}' should have shape '{self.shape}'. Received mask with shape '{groundtruth.mask.shape}'"
87
+ )
88
+
89
+ if mask_accumulation is None:
90
+ mask_accumulation = groundtruth.mask.copy()
91
+ elif np.logical_and(mask_accumulation, groundtruth.mask).any():
92
+ raise ValueError("ground truth masks cannot overlap")
93
+ else:
94
+ mask_accumulation = mask_accumulation | groundtruth.mask
95
+
96
+ mask_accumulation = None
97
+ for prediction in self.predictions:
98
+ if self.shape != prediction.mask.shape:
99
+ raise ValueError(
100
+ f"prediction masks for datum '{self.uid}' should have shape '{self.shape}'. Received mask with shape '{prediction.mask.shape}'"
101
+ )
102
+
103
+ if mask_accumulation is None:
104
+ mask_accumulation = prediction.mask.copy()
105
+ elif np.logical_and(mask_accumulation, prediction.mask).any():
106
+ raise ValueError("prediction masks cannot overlap")
107
+ else:
108
+ mask_accumulation = mask_accumulation | prediction.mask
@@ -1,5 +1,91 @@
1
+ import numpy as np
2
+
1
3
  from valor_lite.profiling import create_runtime_profiler
2
- from valor_lite.semantic_segmentation import DataLoader, generate_segmentation
4
+ from valor_lite.semantic_segmentation import Bitmask, DataLoader, Segmentation
5
+
6
+
7
+ def generate_segmentation(
8
+ datum_uid: str,
9
+ number_of_unique_labels: int,
10
+ mask_height: int,
11
+ mask_width: int,
12
+ ) -> Segmentation:
13
+ """
14
+ Generates a semantic segmentation annotation.
15
+
16
+ Parameters
17
+ ----------
18
+ datum_uid : str
19
+ The datum UID for the generated segmentation.
20
+ number_of_unique_labels : int
21
+ The number of unique labels.
22
+ mask_height : int
23
+ The height of the mask in pixels.
24
+ mask_width : int
25
+ The width of the mask in pixels.
26
+
27
+ Returns
28
+ -------
29
+ Segmentation
30
+ A generated semantic segmenatation annotation.
31
+ """
32
+
33
+ if number_of_unique_labels > 1:
34
+ common_proba = 0.4 / (number_of_unique_labels - 1)
35
+ min_proba = min(common_proba, 0.1)
36
+ labels = [str(i) for i in range(number_of_unique_labels)] + [None]
37
+ proba = (
38
+ [0.5]
39
+ + [common_proba for _ in range(number_of_unique_labels - 1)]
40
+ + [0.1]
41
+ )
42
+ elif number_of_unique_labels == 1:
43
+ labels = ["0", None]
44
+ proba = [0.9, 0.1]
45
+ min_proba = 0.1
46
+ else:
47
+ raise ValueError(
48
+ "The number of unique labels should be greater than zero."
49
+ )
50
+
51
+ probabilities = np.array(proba, dtype=np.float64)
52
+ weights = (probabilities / min_proba).astype(np.int32)
53
+
54
+ indices = np.random.choice(
55
+ np.arange(len(weights)),
56
+ size=(mask_height * 2, mask_width),
57
+ p=probabilities,
58
+ )
59
+
60
+ N = len(labels)
61
+
62
+ masks = np.arange(N)[:, None, None] == indices
63
+
64
+ gts = []
65
+ pds = []
66
+ for lidx in range(N):
67
+ label = labels[lidx]
68
+ if label is None:
69
+ continue
70
+ gts.append(
71
+ Bitmask(
72
+ mask=masks[lidx, :mask_height, :],
73
+ label=label,
74
+ )
75
+ )
76
+ pds.append(
77
+ Bitmask(
78
+ mask=masks[lidx, mask_height:, :],
79
+ label=label,
80
+ )
81
+ )
82
+
83
+ return Segmentation(
84
+ uid=datum_uid,
85
+ groundtruths=gts,
86
+ predictions=pds,
87
+ shape=(mask_height, mask_width),
88
+ )
3
89
 
4
90
 
5
91
  def benchmark_add_data(
@@ -2,13 +2,93 @@ import numpy as np
2
2
  from numpy.typing import NDArray
3
3
 
4
4
 
5
+ def compute_label_metadata(
6
+ confusion_matrices: NDArray[np.int64],
7
+ n_labels: int,
8
+ ) -> NDArray[np.int64]:
9
+ """
10
+ Computes label metadata returning a count of annotations per label.
11
+
12
+ Parameters
13
+ ----------
14
+ confusion_matrices : NDArray[np.int64]
15
+ Confusion matrices per datum with shape (n_datums, n_labels + 1, n_labels + 1).
16
+ n_labels : int
17
+ The total number of unique labels.
18
+
19
+ Returns
20
+ -------
21
+ NDArray[np.int64]
22
+ The label metadata array with shape (n_labels, 2).
23
+ Index 0 - Ground truth label count
24
+ Index 1 - Prediction label count
25
+ """
26
+ label_metadata = np.zeros((n_labels, 2), dtype=np.int64)
27
+ label_metadata[:, 0] = confusion_matrices[:, 1:, :].sum(axis=(0, 2))
28
+ label_metadata[:, 1] = confusion_matrices[:, :, 1:].sum(axis=(0, 1))
29
+ return label_metadata
30
+
31
+
32
+ def filter_cache(
33
+ confusion_matrices: NDArray[np.int64],
34
+ datum_mask: NDArray[np.bool_],
35
+ label_mask: NDArray[np.bool_],
36
+ number_of_labels: int,
37
+ ) -> tuple[NDArray[np.int64], NDArray[np.int64]]:
38
+ """
39
+ Performs the filter operation over the internal cache.
40
+
41
+ Parameters
42
+ ----------
43
+ confusion_matrices : NDArray[int64]
44
+ The internal evaluator cache.
45
+ datum_mask : NDArray[bool]
46
+ A mask that filters out datums.
47
+ datum_mask : NDArray[bool]
48
+ A mask that filters out labels.
49
+
50
+ Returns
51
+ -------
52
+ NDArray[int64]
53
+ Filtered confusion matrices.
54
+ NDArray[int64]
55
+ Filtered label metadata.
56
+ """
57
+ if label_mask.any():
58
+ # add filtered labels to background
59
+ null_predictions = confusion_matrices[:, label_mask, :].sum(
60
+ axis=(1, 2)
61
+ )
62
+ null_groundtruths = confusion_matrices[:, :, label_mask].sum(
63
+ axis=(1, 2)
64
+ )
65
+ null_intersection = (
66
+ confusion_matrices[:, label_mask, label_mask]
67
+ .reshape(confusion_matrices.shape[0], -1)
68
+ .sum(axis=1)
69
+ )
70
+ confusion_matrices[:, 0, 0] += (
71
+ null_groundtruths + null_predictions - null_intersection
72
+ )
73
+ confusion_matrices[:, label_mask, :] = 0
74
+ confusion_matrices[:, :, label_mask] = 0
75
+
76
+ confusion_matrices = confusion_matrices[datum_mask]
77
+
78
+ label_metadata = compute_label_metadata(
79
+ confusion_matrices=confusion_matrices,
80
+ n_labels=number_of_labels,
81
+ )
82
+ return confusion_matrices, label_metadata
83
+
84
+
5
85
  def compute_intermediate_confusion_matrices(
6
86
  groundtruths: NDArray[np.bool_],
7
87
  predictions: NDArray[np.bool_],
8
- groundtruth_labels: NDArray[np.int32],
9
- prediction_labels: NDArray[np.int32],
88
+ groundtruth_labels: NDArray[np.int64],
89
+ prediction_labels: NDArray[np.int64],
10
90
  n_labels: int,
11
- ) -> NDArray[np.int32]:
91
+ ) -> NDArray[np.int64]:
12
92
  """
13
93
  Computes an intermediate confusion matrix containing label counts.
14
94
 
@@ -18,16 +98,16 @@ def compute_intermediate_confusion_matrices(
18
98
  A 2-D array containing flattened bitmasks for each label.
19
99
  predictions : NDArray[np.bool_]
20
100
  A 2-D array containing flattened bitmasks for each label.
21
- groundtruth_labels : NDArray[np.int32]
101
+ groundtruth_labels : NDArray[np.int64]
22
102
  A 1-D array containing label indices.
23
- groundtruth_labels : NDArray[np.int32]
103
+ groundtruth_labels : NDArray[np.int64]
24
104
  A 1-D array containing label indices.
25
105
  n_labels : int
26
106
  The number of unique labels.
27
107
 
28
108
  Returns
29
109
  -------
30
- NDArray[np.int32]
110
+ NDArray[np.int64]
31
111
  A 2-D confusion matrix with shape (n_labels + 1, n_labels + 1).
32
112
  """
33
113
 
@@ -45,7 +125,7 @@ def compute_intermediate_confusion_matrices(
45
125
  intersected_groundtruth_counts = intersection_counts.sum(axis=1)
46
126
  intersected_prediction_counts = intersection_counts.sum(axis=0)
47
127
 
48
- confusion_matrix = np.zeros((n_labels + 1, n_labels + 1), dtype=np.int32)
128
+ confusion_matrix = np.zeros((n_labels + 1, n_labels + 1), dtype=np.int64)
49
129
  confusion_matrix[0, 0] = background_counts
50
130
  confusion_matrix[
51
131
  np.ix_(groundtruth_labels + 1, prediction_labels + 1)
@@ -61,8 +141,8 @@ def compute_intermediate_confusion_matrices(
61
141
 
62
142
 
63
143
  def compute_metrics(
64
- data: NDArray[np.float64],
65
- label_metadata: NDArray[np.int32],
144
+ confusion_matrices: NDArray[np.int64],
145
+ label_metadata: NDArray[np.int64],
66
146
  n_pixels: int,
67
147
  ) -> tuple[
68
148
  NDArray[np.float64],
@@ -80,10 +160,12 @@ def compute_metrics(
80
160
 
81
161
  Parameters
82
162
  ----------
83
- data : NDArray[np.float64]
84
- A 3-D array containing confusion matrices for each datum.
85
- label_metadata : NDArray[np.int32]
86
- A 2-D array containing label metadata.
163
+ confusion_matrices : NDArray[np.int64]
164
+ A 3-D array containing confusion matrices for each datum with shape (n_datums, n_labels + 1, n_labels + 1).
165
+ label_metadata : NDArray[np.int64]
166
+ A 2-D array containing label metadata with shape (n_labels, 2).
167
+ Index 0: Ground Truth Label Count
168
+ Index 1: Prediction Label Count
87
169
 
88
170
  Returns
89
171
  -------
@@ -106,7 +188,7 @@ def compute_metrics(
106
188
  gt_counts = label_metadata[:, 0]
107
189
  pd_counts = label_metadata[:, 1]
108
190
 
109
- counts = data.sum(axis=0)
191
+ counts = confusion_matrices.sum(axis=0)
110
192
 
111
193
  # compute iou, unmatched_ground_truth and unmatched predictions
112
194
  intersection_ = counts[1:, 1:]