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

Fixing import and export issues with invalid tracks #8553

Merged
merged 14 commits into from
Oct 24, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
### Fixed

- Fixed export/import errors for tracks with duplicated shapes.
Fixed a bug which caused shape duplication on track import.
(<https://github.com/cvat-ai/cvat/pull/8553>)
8 changes: 7 additions & 1 deletion cvat/apps/dataset_manager/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ def filter_track_shapes(shapes):
if last_key >= stop and scoped_shapes[-1]['points'] != segment_shapes[-1]['points']:
segment_shapes.append(scoped_shapes[-1])
elif scoped_shapes[-1]['keyframe'] and \
scoped_shapes[-1]['outside']:
scoped_shapes[-1]['outside'] and \
(len(segment_shapes) == 0 or scoped_shapes[-1]['frame'] > segment_shapes[-1]['frame']):
segment_shapes.append(scoped_shapes[-1])
elif stop + 1 < len(interpolated_shapes) and \
interpolated_shapes[stop + 1]['outside']:
Expand Down Expand Up @@ -950,6 +951,11 @@ def propagate(shape, end_frame, *, included_frames=None):
break # The track finishes here

if prev_shape:
if (
curr_frame == prev_shape["frame"]
and dict(shape, id=None, keyframe=None) == dict(prev_shape, id=None, keyframe=None)
):
continue
assert curr_frame > prev_shape["frame"], f"{curr_frame} > {prev_shape['frame']}. Track id: {track['id']}" # Catch invalid tracks

# Propagate attributes
Expand Down
168 changes: 129 additions & 39 deletions cvat/apps/dataset_manager/tests/test_annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
#
# SPDX-License-Identifier: MIT

from cvat.apps.dataset_manager.annotation import TrackManager

from unittest import TestCase

from cvat.apps.dataset_manager.annotation import AnnotationIR, TrackManager
from cvat.apps.engine.models import DimensionType


class TrackManagerTest(TestCase):
def _check_interpolation(self, track):
interpolated = TrackManager.get_interpolated_shapes(track, 0, 7, '2d')
interpolated = TrackManager.get_interpolated_shapes(track, 0, 7, "2d")

self.assertEqual(
[
Expand All @@ -24,7 +25,7 @@ def _check_interpolation(self, track):
[
{k: v for k, v in shape.items() if k in ["frame", "keyframe", "outside"]}
for shape in interpolated
]
],
)

def test_point_interpolation(self):
Expand All @@ -41,25 +42,25 @@ def test_point_interpolation(self):
"type": "points",
"occluded": False,
"outside": False,
"attributes": []
"attributes": [],
},
{
"frame": 2,
"attributes": [],
"points": [3.0, 4.0, 5.0, 6.0],
"type": "points",
"occluded": False,
"outside": True
"outside": True,
},
{
"frame": 4,
"attributes": [],
"points": [3.0, 4.0, 5.0, 6.0],
"type": "points",
"occluded": False,
"outside": False
"outside": False,
},
]
],
}

self._check_interpolation(track)
Expand All @@ -78,25 +79,25 @@ def test_polygon_interpolation(self):
"type": "polygon",
"occluded": False,
"outside": False,
"attributes": []
"attributes": [],
},
{
"frame": 2,
"attributes": [],
"points": [3.0, 4.0, 5.0, 6.0, 7.0, 6.0, 4.0, 5.0],
"type": "polygon",
"occluded": False,
"outside": True
"outside": True,
},
{
"frame": 4,
"attributes": [],
"points": [3.0, 4.0, 5.0, 6.0, 7.0, 6.0, 4.0, 5.0],
"type": "polygon",
"occluded": False,
"outside": False
"outside": False,
},
]
],
}

self._check_interpolation(track)
Expand All @@ -116,7 +117,7 @@ def test_bbox_interpolation(self):
"type": "rectangle",
"occluded": False,
"outside": False,
"attributes": []
"attributes": [],
},
{
"frame": 2,
Expand All @@ -125,7 +126,7 @@ def test_bbox_interpolation(self):
"rotation": 0,
"type": "rectangle",
"occluded": False,
"outside": True
"outside": True,
},
{
"frame": 4,
Expand All @@ -134,9 +135,9 @@ def test_bbox_interpolation(self):
"rotation": 0,
"type": "rectangle",
"occluded": False,
"outside": False
"outside": False,
},
]
],
}

self._check_interpolation(track)
Expand All @@ -156,7 +157,7 @@ def test_line_interpolation(self):
"type": "polyline",
"occluded": False,
"outside": False,
"attributes": []
"attributes": [],
},
{
"frame": 2,
Expand All @@ -165,7 +166,7 @@ def test_line_interpolation(self):
"rotation": 0,
"type": "polyline",
"occluded": False,
"outside": True
"outside": True,
},
{
"frame": 4,
Expand All @@ -174,9 +175,9 @@ def test_line_interpolation(self):
"rotation": 0,
"type": "polyline",
"occluded": False,
"outside": False
"outside": False,
},
]
],
}

self._check_interpolation(track)
Expand All @@ -196,7 +197,7 @@ def test_outside_bbox_interpolation(self):
"type": "rectangle",
"occluded": False,
"outside": False,
"attributes": []
"attributes": [],
},
{
"frame": 2,
Expand All @@ -214,9 +215,9 @@ def test_outside_bbox_interpolation(self):
"type": "rectangle",
"occluded": False,
"outside": True,
"attributes": []
}
]
"attributes": [],
},
],
}

expected_shapes = [
Expand All @@ -228,7 +229,7 @@ def test_outside_bbox_interpolation(self):
"occluded": False,
"outside": False,
"attributes": [],
"keyframe": True
"keyframe": True,
},
{
"frame": 1,
Expand All @@ -238,7 +239,7 @@ def test_outside_bbox_interpolation(self):
"occluded": False,
"outside": False,
"attributes": [],
"keyframe": False
"keyframe": False,
},
{
"frame": 2,
Expand All @@ -248,7 +249,7 @@ def test_outside_bbox_interpolation(self):
"occluded": False,
"outside": True,
"attributes": [],
"keyframe": True
"keyframe": True,
},
{
"frame": 4,
Expand All @@ -258,11 +259,11 @@ def test_outside_bbox_interpolation(self):
"occluded": False,
"outside": True,
"attributes": [],
"keyframe": True
}
"keyframe": True,
},
]

interpolated_shapes = TrackManager.get_interpolated_shapes(track, 0, 5, '2d')
interpolated_shapes = TrackManager.get_interpolated_shapes(track, 0, 5, "2d")
self.assertEqual(expected_shapes, interpolated_shapes)

def test_outside_polygon_interpolation(self):
Expand All @@ -279,17 +280,17 @@ def test_outside_polygon_interpolation(self):
"type": "polygon",
"occluded": False,
"outside": False,
"attributes": []
"attributes": [],
},
{
"frame": 2,
"points": [3.0, 4.0, 5.0, 6.0, 7.0, 8.0],
"type": "polygon",
"occluded": False,
"outside": True,
"attributes": []
}
]
"attributes": [],
},
],
}

expected_shapes = [
Expand All @@ -300,7 +301,7 @@ def test_outside_polygon_interpolation(self):
"occluded": False,
"outside": False,
"attributes": [],
"keyframe": True
"keyframe": True,
},
{
"frame": 1,
Expand All @@ -309,7 +310,7 @@ def test_outside_polygon_interpolation(self):
"occluded": False,
"outside": False,
"attributes": [],
"keyframe": False
"keyframe": False,
},
{
"frame": 2,
Expand All @@ -318,9 +319,98 @@ def test_outside_polygon_interpolation(self):
"occluded": False,
"outside": True,
"attributes": [],
"keyframe": True
}
"keyframe": True,
},
]

interpolated_shapes = TrackManager.get_interpolated_shapes(track, 0, 3, "2d")
self.assertEqual(expected_shapes, interpolated_shapes)

def test_duplicated_shape_interpolation(self):
# there should not be any new tracks with duplicated shapes,
# but it is possible that the database still contains some
expected_shapes = [
{
"type": "rectangle",
"occluded": False,
"outside": False,
"points": [100, 100, 200, 200],
"frame": 0,
"attributes": [],
"rotation": 0,
},
{
"type": "rectangle",
"occluded": False,
"outside": True,
"points": [100, 100, 200, 200],
"frame": 1,
"attributes": [],
"rotation": 0,
},
]
track = {
"id": 666,
"frame": 0,
"group": None,
"source": "manual",
"attributes": [],
"elements": [],
"label": "cat",
"shapes": expected_shapes + [expected_shapes[-1]],
}

interpolated_shapes = TrackManager.get_interpolated_shapes(track, 0, 3, '2d')
interpolated_shapes = TrackManager.get_interpolated_shapes(track, 0, 2, "2d")
self.assertEqual(expected_shapes, interpolated_shapes)


class AnnotationIRTest(TestCase):
def test_slice_track_does_not_duplicate_outside_frame_on_the_end(self):
track_shapes = [
{
"type": "rectangle",
"occluded": False,
"outside": False,
"points": [100, 100, 200, 200],
"frame": 0,
"attributes": [],
"rotation": 0,
},
{
"type": "rectangle",
"occluded": False,
"outside": True,
"points": [100, 100, 200, 200],
"frame": 1,
"attributes": [],
"rotation": 0,
},
{
"type": "rectangle",
"occluded": False,
"outside": False,
"points": [111, 111, 222, 222],
"frame": 10,
"attributes": [],
"rotation": 0,
},
]
data = {
"tags": [],
"shapes": [],
"tracks": [
{
"id": 666,
"frame": 0,
"group": None,
"source": "manual",
"attributes": [],
"elements": [],
"label": "cat",
"shapes": track_shapes,
}
],
}
annotation = AnnotationIR(dimension=DimensionType.DIM_2D, data=data)
sliced_annotation = annotation.slice(0, 1)
self.assertEqual(sliced_annotation.data["tracks"][0]["shapes"], track_shapes[0:2])
1 change: 1 addition & 0 deletions dev/format_python_code.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ for paths in \
"cvat/apps/engine/default_settings.py" \
"cvat/apps/engine/field_validation.py" \
"cvat/apps/engine/model_utils.py" \
"cvat/apps/dataset_manager/tests/test_annotation.py" \
"cvat/apps/dataset_manager/tests/utils.py" \
; do
${BLACK} -- ${paths}
Expand Down
Loading