Fix object correspondence bug

Before, objects could correspond to objects listed in `pin_labels` even if the label was different. Now, objects listed in `pin_labels` are prevented from being the corresponding objects of objects with different labels. This is overly more consistent and intuitive.
This commit is contained in:
Leonid Kostrykin
2024-03-29 16:48:42 +01:00
parent cdfc3f0e57
commit 65e71234ef
2 changed files with 19 additions and 14 deletions
+18 -13
View File
@@ -457,6 +457,13 @@ def files_contains(file1, file2, attributes=None):
raise AssertionError(f"Failed to find '{contains}' in history data. (lines_diff={lines_diff}).")
def _singleobject_intersection_over_union(
mask1: "numpy.typing.NDArray",
mask2: "numpy.typing.NDArray",
) -> "numpy.floating":
return numpy.logical_and(mask1, mask2).sum() / numpy.logical_or(mask1, mask2).sum()
def _multiobject_intersection_over_union(
mask1: "numpy.typing.NDArray",
mask2: "numpy.typing.NDArray",
@@ -470,14 +477,16 @@ def _multiobject_intersection_over_union(
# If the label is in `pin_labels`, then use the same label value to find the corresponding object in the second mask.
if pin_labels is not None and label1 in pin_labels:
cc2 = mask2 == label1
iou_list.append(intersection_over_union(cc1, cc2))
iou_list.append(_singleobject_intersection_over_union(cc1, cc2))
# Otherwise, use the object with the largest IoU value.
# Otherwise, use the object with the largest IoU value, excluding the pinned labels.
else:
cc1_iou_list = []
for label2 in numpy.unique(mask2[cc1]):
if pin_labels is not None and label2 in pin_labels:
continue
cc2 = mask2 == label2
cc1_iou_list.append(intersection_over_union(cc1, cc2))
cc1_iou_list.append(_singleobject_intersection_over_union(cc1, cc2))
iou_list.append(max(cc1_iou_list))
if repeat_reverse:
@@ -491,15 +500,14 @@ def intersection_over_union(
) -> "numpy.floating":
"""Compute the intersection over union (IoU) for the objects in two masks containing lables.
Generally, the IoU is computed for each uniquely labeled image region (object).
For each object each mask, the corresponding object in the other mask is determined as the one with the largest IoU value.
This yields a series of IoU values, one for each object in the two masks. The final IoU value is the overall lowest value obtained.
The IoU is computed for each uniquely labeled image region (object), and the overall minimum value is returned (i.e. the worst value).
To compute the IoU for each object, the corresponding object in the other mask needs to be determined.
The object correspondences are not necessarily symmetric.
Note that the argument `pin_labels` changes how the object correspondences are determined.
By default, the corresponding object in the other mask is determined as the one with the largest IoU value.
If the label of an object is listed in `pin_labels`, then the corresponding object in the other mask is determined as the object with the same label value.
Objects with labels listed in `pin_labels` also cannot correspond to objects with different labels.
This is particularly useful when specific image regions must always be labeled with a designated label value (e.g., the image background is often labeled with 0 or -1).
For masks with bool data type, the only possible labels `True` and `False` are always assumed to be pinned, and the arugment `pin_labels` is ignored.
"""
assert mask1.dtype == mask2.dtype
assert mask1.ndim == mask2.ndim == 2
@@ -508,10 +516,7 @@ def intersection_over_union(
count = sum(label in mask for mask in (mask1, mask2))
count_str = {1: "one", 2: "both"}
assert count == 2, f"Label {label} is pinned but missing in {count_str[2 - count]} of the images."
if mask1.dtype == bool:
return numpy.logical_and(mask1, mask2).sum() / numpy.logical_or(mask1, mask2).sum()
else:
return min(_multiobject_intersection_over_union(mask1, mask2, pin_labels))
return min(_multiobject_intersection_over_union(mask1, mask2, pin_labels))
def _parse_label_list(label_list_str: str) -> List[int]:
+1 -1
View File
@@ -1827,7 +1827,7 @@ If you specify a `checksum`, it will be also used to check the integrity of the
</xs:attribute>
<xs:attribute name="pin_labels" type="xs:string" use="optional">
<xs:annotation>
<xs:documentation xml:lang="en">If ``comparpe`` is set to ``image_diff`` and ``metric`` is set to ``iou``, by default, object correspondances are established by maximizing the pairwise intersection over the union. If, however, the label of an object is listed in ``pin_labels``, then the corresponding object is determined according to the same label value.</xs:documentation>
<xs:documentation xml:lang="en">If ``comparpe`` is set to ``image_diff`` and ``metric`` is set to ``iou``, by default, object correspondances are established by maximizing the pairwise intersection over the union. If, however, the label of an object is listed in ``pin_labels``, then the corresponding object is determined according to the same label value (and that object cannot be the corresponding object of any other with a different label).</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>