Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GSoC2024] Added quality reporting for Tag annotations #7582

Merged
merged 42 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
ed4ccf1
fixed quality report of label type Tag by emulating them as Bbox
Viditagarwal7479 Mar 9, 2024
693eaba
fixed black formating
Viditagarwal7479 Mar 9, 2024
dbd9852
Merge branch 'opencv:develop' into quality_report
Viditagarwal7479 Mar 10, 2024
3e0679f
Merge branch 'develop' into quality_report
Viditagarwal7479 Mar 11, 2024
2aa6c41
Fixed sending `/events` requests from logged out users (#7608)
klakhov Mar 29, 2024
32bb06a
Revert "Merge branch 'develop' into quality_report"
Viditagarwal7479 Mar 30, 2024
fb8e4ba
merged origin/develop
Viditagarwal7479 Mar 30, 2024
5ae8e5d
Merge branch 'develop' into quality_report
Viditagarwal7479 Mar 30, 2024
04fe596
added custom label matching function
Viditagarwal7479 Mar 30, 2024
366e60f
not to use annotations of type label for grouping conflict
Viditagarwal7479 Mar 30, 2024
817b65c
removed earlier bbox emulation changes for label annotation
Viditagarwal7479 Mar 30, 2024
19c5ad5
removed unused imports
Viditagarwal7479 Mar 30, 2024
3fceef5
reverted unnecessary changes, occurred while reverting to a previous …
Viditagarwal7479 Mar 30, 2024
a01f90b
added some information about Jaccard Index for developers
Viditagarwal7479 Mar 30, 2024
f987998
fixed file changed while reverting to previous commit
Viditagarwal7479 Mar 30, 2024
58d1a6c
added changelog entry
Viditagarwal7479 Mar 31, 2024
10df009
fixed implementation error while filter annotations which can have group
Viditagarwal7479 Mar 31, 2024
7686d5e
updated the test db
Viditagarwal7479 Mar 31, 2024
1246f82
dumped json files after test db update
Viditagarwal7479 Mar 31, 2024
d3689a6
redumped the json as had accidently reverted a change
Viditagarwal7479 Mar 31, 2024
0a94ea3
fixed the custom label matcher function to use class function
Viditagarwal7479 Apr 1, 2024
dfd791f
modified the output of frame result and computation of annotation_com…
Viditagarwal7479 Apr 1, 2024
e7fc6fc
fixed misuse of overall pairwise distance instead of shape type pairw…
Viditagarwal7479 Apr 1, 2024
06b48df
redumped the testing db by recomputing the quality report for task ha…
Viditagarwal7479 Apr 1, 2024
b5f58cd
dumped json files for the updated testing db
Viditagarwal7479 Apr 1, 2024
63ef2be
Merge branch 'develop' into quality_report
zhiltsov-max Apr 2, 2024
9ecc2b9
redumped testing db and json
Viditagarwal7479 Apr 3, 2024
6eb0fdf
reverted the dump to the origin/develop and again made changes in tes…
Viditagarwal7479 Apr 3, 2024
6ec3d2b
updated the distance function while matching labels
Viditagarwal7479 Apr 3, 2024
17e171a
Merge branch 'develop' into quality_report
Viditagarwal7479 Apr 3, 2024
3317cb8
Merge branch 'develop' into quality_report
Viditagarwal7479 Apr 4, 2024
90ac5aa
added dist_thresh as a function parameter for label based match segment
Viditagarwal7479 Apr 8, 2024
f93a79a
Merge branch 'develop' into quality_report
Viditagarwal7479 Apr 8, 2024
fc5cead
Update cvat/apps/quality_control/quality_reports.py
zhiltsov-max Apr 9, 2024
cd39b9e
fixed incorrect usage of shape_unmatched_ann instead of unmatched_ann
Viditagarwal7479 Apr 9, 2024
0c70c6b
Merge branch 'develop' into quality_report
Viditagarwal7479 Apr 9, 2024
72fe5af
Merge branch 'develop' into quality_report
Viditagarwal7479 Apr 10, 2024
6784587
Merge branch 'develop' into quality_report
zhiltsov-max Apr 10, 2024
11bff88
Merge branch 'develop' into quality_report
zhiltsov-max Apr 11, 2024
28196c3
Add missing type annotation
zhiltsov-max Apr 11, 2024
5b73e63
Merge branch 'develop' into quality_report
zhiltsov-max May 7, 2024
f1050b3
Merge branch 'develop' into quality_report
zhiltsov-max May 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Added

- Quality Report calculation will now also include annotation of type Tag.
(<https://github.com/opencv/cvat/pull/7582>)
103 changes: 77 additions & 26 deletions cvat/apps/quality_control/quality_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,12 @@ class ComparisonParameters(_Serializable):
dm.AnnotationType.polygon,
dm.AnnotationType.polyline,
dm.AnnotationType.skeleton,
dm.AnnotationType.label,
Viditagarwal7479 marked this conversation as resolved.
Show resolved Hide resolved
]

non_groupable_ann_type = dm.AnnotationType.label
"Annotation type that can't be grouped"

compare_attributes: bool = True
"Enables or disables attribute checks"

Expand Down Expand Up @@ -1000,6 +1004,21 @@ def _match_ann_type(self, t, *args):
else:
return None

def match_labels(self, item_a, item_b):
def label_distance(a, b):
if a is None or b is None:
return 0
return 0.5 + (a.label == b.label) / 2
Viditagarwal7479 marked this conversation as resolved.
Show resolved Hide resolved
zhiltsov-max marked this conversation as resolved.
Show resolved Hide resolved

return self._match_segments(
dm.AnnotationType.label,
item_a,
item_b,
distance=label_distance,
label_matcher=lambda a, b: a.label == b.label,
dist_thresh=0.5,
)

def _match_segments(
self,
t,
Expand All @@ -1010,6 +1029,7 @@ def _match_segments(
label_matcher: Callable = None,
a_objs: Optional[Sequence[dm.Annotation]] = None,
b_objs: Optional[Sequence[dm.Annotation]] = None,
dist_thresh=None,
zhiltsov-max marked this conversation as resolved.
Show resolved Hide resolved
):
if a_objs is None:
a_objs = self._get_ann_type(t, item_a)
Expand All @@ -1028,7 +1048,11 @@ def _match_segments(
extra_args["label_matcher"] = label_matcher

returned_values = _match_segments(
a_objs, b_objs, distance=distance, dist_thresh=self.iou_threshold, **extra_args
a_objs,
b_objs,
distance=distance,
dist_thresh=dist_thresh if dist_thresh is not None else self.iou_threshold,
**extra_args,
)

if self.return_distances:
Expand Down Expand Up @@ -1482,6 +1506,7 @@ def __init__(self, categories: dm.CategoriesInfo, *, settings: ComparisonParamet
"outside", # handled by other means
}
self.included_ann_types = settings.included_annotation_types
self.non_groupable_ann_type = settings.non_groupable_ann_type
self._annotation_comparator = _DistanceComparator(
categories,
included_ann_types=set(self.included_ann_types)
Expand Down Expand Up @@ -1528,7 +1553,11 @@ def find_groups(
self, item: dm.DatasetItem
) -> Tuple[Dict[int, List[dm.Annotation]], Dict[int, int]]:
ann_groups = dm.ops.find_instances(
[ann for ann in item.annotations if ann.type in self.included_ann_types]
[
ann
for ann in item.annotations
if ann.type in self.included_ann_types and ann.type != self.non_groupable_ann_type
]
)

groups = {}
Expand Down Expand Up @@ -1603,6 +1632,7 @@ def match_annotations(self, item_a: dm.DatasetItem, item_b: dm.DatasetItem):
per_type_results = self._annotation_comparator.match_annotations(item_a, item_b)

merged_results = [[], [], [], [], {}]
shape_merged_results = [[], [], [], [], {}]
for shape_type in self.included_ann_types:
shape_type_results = per_type_results.get(shape_type, None)
if shape_type_results is None:
Expand All @@ -1611,9 +1641,14 @@ def match_annotations(self, item_a: dm.DatasetItem, item_b: dm.DatasetItem):
for merged_field, field in zip(merged_results, shape_type_results[:-1]):
merged_field.extend(field)

if shape_type != dm.AnnotationType.label:
for merged_field, field in zip(shape_merged_results, shape_type_results[:-1]):
merged_field.extend(field)
shape_merged_results[-1].update(per_type_results[shape_type][-1])

merged_results[-1].update(per_type_results[shape_type][-1])

return merged_results
return {"all_ann_types": merged_results, "all_shape_ann_types": shape_merged_results}

def get_distance(
self, pairwise_distances, gt_ann: dm.Annotation, ds_ann: dm.Annotation
Expand Down Expand Up @@ -1687,21 +1722,30 @@ def _generate_frame_annotation_conflicts(
) -> List[AnnotationConflict]:
conflicts = []

matches, mismatches, gt_unmatched, ds_unmatched, pairwise_distances = frame_results
matches, mismatches, gt_unmatched, ds_unmatched, _ = frame_results["all_ann_types"]
(
shape_matches,
shape_mismatches,
shape_gt_unmatched,
shape_ds_unmatched,
shape_pairwise_distances,
) = frame_results["all_shape_ann_types"]

def _get_similarity(gt_ann: dm.Annotation, ds_ann: dm.Annotation) -> Optional[float]:
return self.comparator.get_distance(pairwise_distances, gt_ann, ds_ann)
return self.comparator.get_distance(shape_pairwise_distances, gt_ann, ds_ann)

_matched_shapes = set(
id(shape) for shape_pair in itertools.chain(matches, mismatches) for shape in shape_pair
id(shape)
for shape_pair in itertools.chain(shape_matches, shape_mismatches)
for shape in shape_pair
)

def _find_closest_unmatched_shape(shape: dm.Annotation):
this_shape_id = id(shape)

this_shape_distances = []

for (gt_shape_id, ds_shape_id), dist in pairwise_distances.items():
for (gt_shape_id, ds_shape_id), dist in shape_pairwise_distances.items():
if gt_shape_id == this_shape_id:
other_shape_id = ds_shape_id
elif ds_shape_id == this_shape_id:
Expand All @@ -1728,21 +1772,21 @@ def _find_closest_unmatched_shape(shape: dm.Annotation):
)
)

for unmatched_ann in gt_unmatched:
for shape_unmatched_ann in gt_unmatched:
Viditagarwal7479 marked this conversation as resolved.
Show resolved Hide resolved
conflicts.append(
AnnotationConflict(
frame_id=frame_id,
type=AnnotationConflictType.MISSING_ANNOTATION,
annotation_ids=[self._dm_ann_to_ann_id(unmatched_ann, self._gt_dataset)],
annotation_ids=[self._dm_ann_to_ann_id(shape_unmatched_ann, self._gt_dataset)],
Viditagarwal7479 marked this conversation as resolved.
Show resolved Hide resolved
)
)

for unmatched_ann in ds_unmatched:
for shape_unmatched_ann in ds_unmatched:
Viditagarwal7479 marked this conversation as resolved.
Show resolved Hide resolved
conflicts.append(
AnnotationConflict(
frame_id=frame_id,
type=AnnotationConflictType.EXTRA_ANNOTATION,
annotation_ids=[self._dm_ann_to_ann_id(unmatched_ann, self._ds_dataset)],
annotation_ids=[self._dm_ann_to_ann_id(shape_unmatched_ann, self._ds_dataset)],
Viditagarwal7479 marked this conversation as resolved.
Show resolved Hide resolved
)
)

Expand All @@ -1759,14 +1803,14 @@ def _find_closest_unmatched_shape(shape: dm.Annotation):
)

resulting_distances = [
_get_similarity(gt_ann, ds_ann)
for gt_ann, ds_ann in itertools.chain(matches, mismatches)
_get_similarity(shape_gt_ann, shape_ds_ann)
for shape_gt_ann, shape_ds_ann in itertools.chain(shape_matches, shape_mismatches)
]

for unmatched_ann in itertools.chain(gt_unmatched, ds_unmatched):
matched_ann_id, similarity = _find_closest_unmatched_shape(unmatched_ann)
if matched_ann_id is not None:
_matched_shapes.add(matched_ann_id)
for shape_unmatched_ann in itertools.chain(shape_gt_unmatched, shape_ds_unmatched):
shape_matched_ann_id, similarity = _find_closest_unmatched_shape(shape_unmatched_ann)
if shape_matched_ann_id is not None:
_matched_shapes.add(shape_matched_ann_id)
resulting_distances.append(similarity)

resulting_distances = [
Expand Down Expand Up @@ -1842,10 +1886,12 @@ def _find_closest_unmatched_shape(shape: dm.Annotation):
if self.settings.compare_groups:
gt_groups, gt_group_map = self.comparator.find_groups(gt_item)
ds_groups, ds_group_map = self.comparator.find_groups(ds_item)
matched_objects = matches + mismatches
ds_to_gt_groups = self.comparator.match_groups(gt_groups, ds_groups, matched_objects)
shape_matched_objects = shape_matches + shape_mismatches
ds_to_gt_groups = self.comparator.match_groups(
gt_groups, ds_groups, shape_matched_objects
)

for gt_ann, ds_ann in matched_objects:
for gt_ann, ds_ann in shape_matched_objects:
gt_group = gt_groups.get(gt_group_map[id(gt_ann)], [gt_ann])
ds_group = ds_groups.get(ds_group_map[id(ds_ann)], [ds_ann])
ds_gt_group = ds_to_gt_groups.get(ds_group_map[id(ds_ann)], None)
Expand All @@ -1868,12 +1914,17 @@ def _find_closest_unmatched_shape(shape: dm.Annotation):
)
)

valid_shapes_count = len(matches) + len(mismatches)
missing_shapes_count = len(gt_unmatched)
extra_shapes_count = len(ds_unmatched)
total_shapes_count = len(matches) + len(mismatches) + len(gt_unmatched) + len(ds_unmatched)
ds_shapes_count = len(matches) + len(mismatches) + len(ds_unmatched)
gt_shapes_count = len(matches) + len(mismatches) + len(gt_unmatched)
valid_shapes_count = len(shape_matches) + len(shape_mismatches)
missing_shapes_count = len(shape_gt_unmatched)
extra_shapes_count = len(shape_ds_unmatched)
total_shapes_count = (
len(shape_matches)
+ len(shape_mismatches)
+ len(shape_gt_unmatched)
+ len(shape_ds_unmatched)
)
ds_shapes_count = len(shape_matches) + len(shape_mismatches) + len(shape_ds_unmatched)
gt_shapes_count = len(shape_matches) + len(shape_mismatches) + len(shape_gt_unmatched)

valid_labels_count = len(matches)
invalid_labels_count = len(mismatches)
Expand Down
Binary file modified tests/python/shared/assets/cvat_db/cvat_data.tar.bz2
Binary file not shown.
2,080 changes: 1,601 additions & 479 deletions tests/python/shared/assets/cvat_db/data.json

Large diffs are not rendered by default.

Loading
Loading