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] Fix import of outside track shapes in Datumaro-based formats #7669

Merged
merged 51 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
bfce63b
disable reading of track id
Yeek020407 Mar 23, 2024
099fe0c
Fix switch outside
Yeek020407 Apr 3, 2024
341fd80
Update changelog.d/20240403_135916_yeek020407_switch_outside.md
Yeek020407 Apr 3, 2024
96735a3
Merge branch 'opencv:develop' into switch-outside
Yeek020407 Apr 3, 2024
3c61355
Merge branch 'cvat-ai:develop' into switch-outside
Yeek020407 Apr 5, 2024
a13aa4a
Merge branch 'cvat-ai:develop' into switch-outside
Yeek020407 Apr 7, 2024
11655a7
add test import of outside track shapes
Yeek020407 Apr 7, 2024
946bca3
fix skeleton format bug
Yeek020407 Apr 7, 2024
2ad165f
fixed issues by CI
Yeek020407 Apr 8, 2024
c94b4a6
Update changelog.d/20240403_135916_yeek020407_switch_outside.md
zhiltsov-max Apr 8, 2024
bda889a
add outside attribute when it is coco keypoints
Yeek020407 Apr 9, 2024
fdc0033
Merge branch 'switch-outside' of https://github.com/Yeek020407/cvat i…
Yeek020407 Apr 9, 2024
d2f2ac1
test for coco keypoints
Yeek020407 Apr 9, 2024
bc727aa
added test for coco keypoints with outside true
Yeek020407 Apr 10, 2024
bb71350
use ending_tracks to track last frame with outside true
Yeek020407 Apr 11, 2024
bb77078
Update cvat/apps/dataset_manager/bindings.py
Yeek020407 Apr 11, 2024
9a70214
Update cvat/apps/dataset_manager/bindings.py
Yeek020407 Apr 11, 2024
8772fbc
change to_boolean to to_bool
Yeek020407 Apr 11, 2024
b6c509e
rearrage tracks into finalized_tracks
Yeek020407 Apr 11, 2024
3ed07a1
Update cvat/apps/dataset_manager/bindings.py
Yeek020407 Apr 12, 2024
7000254
remove finalized_tracks
Yeek020407 Apr 12, 2024
fc3a225
Merge branch 'switch-outside' of github.com:Yeek020407/cvat into swit…
Yeek020407 Apr 12, 2024
9232455
remove element_keyframe
Yeek020407 Apr 12, 2024
397b6cc
only add keyframe true to non-outside first shape
Yeek020407 Apr 13, 2024
ff55b20
handle last track with outside true or keyframe true
Yeek020407 Apr 15, 2024
feddc51
minor fix
Yeek020407 Apr 15, 2024
667f2ac
only append shapes with keyframe equals to true
Yeek020407 Apr 15, 2024
67920da
Update cvat/apps/dataset_manager/bindings.py
Yeek020407 Apr 15, 2024
37eb05d
refactor code
Yeek020407 Apr 15, 2024
8c5662b
fix minor bug
Yeek020407 Apr 15, 2024
3cd7dcc
Update cvat/apps/dataset_manager/bindings.py
Yeek020407 Apr 15, 2024
a8d5c72
Update cvat/apps/dataset_manager/bindings.py
Yeek020407 Apr 15, 2024
7a4d8de
add corner tests
Yeek020407 Apr 15, 2024
f9cbe17
Update cvat/apps/dataset_manager/bindings.py
Yeek020407 Apr 15, 2024
c471921
remove delete_method before import annotations
Yeek020407 Apr 15, 2024
68ed992
fix bug
Yeek020407 Apr 15, 2024
4e5dcbe
use DeepDiff
Yeek020407 Apr 15, 2024
45ec847
format code
Yeek020407 Apr 15, 2024
9d1651a
more corner tests
Yeek020407 Apr 16, 2024
f91c2eb
add test for outside true
Yeek020407 Apr 16, 2024
dcf6b59
check for outside true
Yeek020407 Apr 16, 2024
18a5b2d
add more complex tests
Yeek020407 Apr 16, 2024
d13b23f
Merge branch 'develop' into switch-outside
zhiltsov-max Apr 17, 2024
ac65678
Update cvat/apps/dataset_manager/bindings.py
Yeek020407 Apr 18, 2024
034cd12
add test for element with gap for coco keypoints
Yeek020407 Apr 18, 2024
408b68b
Update cvat/apps/dataset_manager/bindings.py
Yeek020407 Apr 18, 2024
8ae6cce
add complex coco annotations test
Yeek020407 Apr 18, 2024
f06f560
Move shape sorting into the shape validation function
zhiltsov-max Apr 19, 2024
d4388c5
Refactor element shapes update
zhiltsov-max Apr 19, 2024
07ebdd6
Merge branch 'develop' into switch-outside
zhiltsov-max Apr 19, 2024
525d489
Remove whitespace
zhiltsov-max Apr 19, 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
4 changes: 4 additions & 0 deletions changelog.d/20240403_135916_yeek020407_switch_outside.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Fixed

- Formats with the custom `track_id` attribute should import `outside` track shapes properly (e.g. `COCO`, `COCO Keypoints`, `Datumaro`, `PASCAL VOC`)
(<https://github.com/opencv/cvat/pull/7669>)
78 changes: 67 additions & 11 deletions cvat/apps/dataset_manager/bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from typing import (Any, Callable, DefaultDict, Dict, Iterable, List, Literal, Mapping,
NamedTuple, Optional, OrderedDict, Sequence, Set, Tuple, Union)

from attrs.converters import to_bool
import datumaro as dm
import defusedxml.ElementTree as ET
import numpy as np
Expand Down Expand Up @@ -1953,7 +1954,8 @@ def import_dm_annotations(dm_dataset: dm.Dataset, instance_data: Union[ProjectDa
'sly_pointcloud',
'coco',
'coco_instances',
'coco_person_keypoints'
'coco_person_keypoints',
'voc'
]

label_cat = dm_dataset.categories()[dm.AnnotationType.label]
Expand Down Expand Up @@ -2031,9 +2033,9 @@ def reduce_fn(acc, v):
# because in some formats return type can be different
# from bool / None
# https://github.com/openvinotoolkit/datumaro/issues/719
occluded = dm.util.cast(ann.attributes.pop('occluded', None), bool) is True
keyframe = dm.util.cast(ann.attributes.get('keyframe', None), bool) is True
outside = dm.util.cast(ann.attributes.pop('outside', None), bool) is True
occluded = dm.util.cast(ann.attributes.pop('occluded', None), to_bool) is True
keyframe = dm.util.cast(ann.attributes.get('keyframe', None), to_bool) is True
outside = dm.util.cast(ann.attributes.pop('outside', None), to_bool) is True

track_id = ann.attributes.pop('track_id', None)
source = ann.attributes.pop('source').lower() \
Expand Down Expand Up @@ -2080,7 +2082,7 @@ def reduce_fn(acc, v):
))
continue

if keyframe or outside:
if dm_dataset.format in track_formats:
if track_id not in tracks:
tracks[track_id] = {
'label': label_cat.items[ann.label].name,
Expand All @@ -2107,11 +2109,8 @@ def reduce_fn(acc, v):

if ann.type == dm.AnnotationType.skeleton:
for element in ann.elements:
element_keyframe = dm.util.cast(element.attributes.get('keyframe', None), bool, True)
element_occluded = element.visibility[0] == dm.Points.Visibility.hidden
element_outside = element.visibility[0] == dm.Points.Visibility.absent
if not element_keyframe and not element_outside:
continue

if element.label not in tracks[track_id]['elements']:
tracks[track_id]['elements'][element.label] = instance_data.Track(
Expand All @@ -2120,6 +2119,7 @@ def reduce_fn(acc, v):
source=source,
shapes=[],
)

element_attributes = [
instance_data.Attribute(name=n, value=str(v))
for n, v in element.attributes.items()
Expand Down Expand Up @@ -2151,10 +2151,66 @@ def reduce_fn(acc, v):
raise CvatImportError("Image {}: can't import annotation "
"#{} ({}): {}".format(item.id, idx, ann.type.name, e)) from e

for track in tracks.values():
track['elements'] = list(track['elements'].values())
instance_data.add_track(instance_data.Track(**track))
def _validate_track_shapes(shapes):
zhiltsov-max marked this conversation as resolved.
Show resolved Hide resolved
new_shapes = []
prev_shape = None
# infer the keyframe shapes and keep only them
for shape in shapes:
prev_is_visible = prev_shape and not prev_shape.outside
cur_is_visible = shape and not shape.outside
if not cur_is_visible:
if prev_is_visible:
shape = shape._replace(keyframe=True)
Yeek020407 marked this conversation as resolved.
Show resolved Hide resolved
new_shapes.append(shape)
prev_shape = shape
continue

has_gap = False
if prev_is_visible:
has_gap = prev_shape.frame + instance_data.frame_step < shape.frame

if not prev_is_visible or has_gap or shape.keyframe:
if has_gap:
prev_shape = prev_shape._replace(outside=True, keyframe=True,
frame=prev_shape.frame + instance_data.frame_step)
new_shapes.append(prev_shape)

shape = shape._replace(keyframe=True)
new_shapes.append(shape)

prev_shape = shape
Yeek020407 marked this conversation as resolved.
Show resolved Hide resolved

Yeek020407 marked this conversation as resolved.
Show resolved Hide resolved
if prev_shape and not prev_shape.outside and (
prev_shape.frame + instance_data.frame_step <= stop_frame
# has a gap before the current instance segment end
):
prev_shape = prev_shape._replace(outside=True, keyframe=True,
frame=prev_shape.frame + instance_data.frame_step)
new_shapes.append(prev_shape)

return new_shapes

stop_frame = int(instance_data.meta[instance_data.META_FIELD]['stop_frame'])
for track_id, track in tracks.items():
track['shapes'].sort(key=lambda t: t.frame)
zhiltsov-max marked this conversation as resolved.
Show resolved Hide resolved
track['shapes'] = _validate_track_shapes(track['shapes'])

Yeek020407 marked this conversation as resolved.
Show resolved Hide resolved
if ann.type == dm.AnnotationType.skeleton:
new_elements = {}
for element_id, element in track['elements'].items():
element.shapes.sort(key=lambda t: t.frame)
zhiltsov-max marked this conversation as resolved.
Show resolved Hide resolved
new_element_shapes = _validate_track_shapes(element.shapes)
new_elements[element_id] = instance_data.Track(
label=element.label,
group=element.group,
source=element.source,
shapes=new_element_shapes,
)
zhiltsov-max marked this conversation as resolved.
Show resolved Hide resolved
track['elements'] = new_elements

if track['shapes'] or track['elements']:
track['elements'] = list(track['elements'].values())
instance_data.add_track(instance_data.Track(**track))

def import_labels_to_project(project_annotation, dataset: dm.Dataset):
labels = []
Expand Down
Loading