Skip to content

Commit 227ab05

Browse files
azhavoronmanovic
andauthored
Fixed uploading track annotations for multi-segment tasks (#1396)
* fixed uploading annotation for overlapped segments * fixed dump of tracks for multisegment task * Update CHANGELOG.md * fixed comments * fixed comments * Update cvat/apps/engine/data_manager.py Co-Authored-By: Nikita Manovich <[email protected]> * drop start shapes with outside==True for splitted track * code cleanup * fixed typo * fix Co-authored-by: Nikita Manovich <[email protected]>
1 parent ee6deba commit 227ab05

File tree

4 files changed

+74
-5
lines changed

4 files changed

+74
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8080
- AttributeError: 'tuple' object has no attribute 'read' in ReID algorithm (https://github.com/opencv/cvat/issues/1403)
8181
- Wrong semi-automatic segmentation near edges of an image (https://github.com/opencv/cvat/issues/1403)
8282
- Git repos paths (https://github.com/opencv/cvat/pull/1400)
83+
- Uploading annotations for tasks with multiple jobs (https://github.com/opencv/cvat/pull/1396)
8384

8485
## [1.0.0-alpha] - 2020-03-31
8586
### Added

cvat/apps/annotation/annotation.py

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,73 @@ def serialize(self):
7777
if serializer.is_valid(raise_exception=True):
7878
return serializer.data
7979

80+
@staticmethod
81+
def _is_shape_inside(shape, start, stop):
82+
return start <= int(shape['frame']) <= stop
83+
84+
@staticmethod
85+
def _is_track_inside(track, start, stop):
86+
# a <= b
87+
def has_overlap(a, b):
88+
return 0 <= min(b, stop) - max(a, start)
89+
90+
prev_shape = None
91+
for shape in track['shapes']:
92+
if prev_shape and not prev_shape['outside'] and \
93+
has_overlap(prev_shape['frame'], shape['frame']):
94+
return True
95+
prev_shape = shape
96+
97+
if not prev_shape['outside'] and prev_shape['frame'] <= stop:
98+
return True
99+
100+
return False
101+
102+
@staticmethod
103+
def _slice_track(track_, start, stop):
104+
def filter_track_shapes(shapes):
105+
shapes = [s for s in shapes if AnnotationIR._is_shape_inside(s, start, stop)]
106+
drop_count = 0
107+
for s in shapes:
108+
if s['outside']:
109+
drop_count += 1
110+
else:
111+
break
112+
# Need to leave the last shape if all shapes are outside
113+
if drop_count == len(shapes):
114+
drop_count -= 1
115+
116+
return shapes[drop_count:]
117+
118+
track = copy.deepcopy(track_)
119+
segment_shapes = filter_track_shapes(track['shapes'])
120+
121+
if len(segment_shapes) < len(track['shapes']):
122+
interpolated_shapes = TrackManager.get_interpolated_shapes(track, start, stop)
123+
scoped_shapes = filter_track_shapes(interpolated_shapes)
124+
125+
if scoped_shapes:
126+
if not scoped_shapes[0]['keyframe']:
127+
segment_shapes.insert(0, scoped_shapes[0])
128+
if not scoped_shapes[-1]['keyframe']:
129+
segment_shapes.append(scoped_shapes[-1])
130+
131+
# Should delete 'interpolation_shapes' and 'keyframe' keys because
132+
# Track and TrackedShape models don't expect these fields
133+
del track['interpolated_shapes']
134+
for shape in segment_shapes:
135+
del shape['keyframe']
136+
137+
track['shapes'] = segment_shapes
138+
track['frame'] = track['shapes'][0]['frame']
139+
return track
140+
80141
#makes a data copy from specified frame interval
81142
def slice(self, start, stop):
82-
is_frame_inside = lambda x: (start <= int(x['frame']) <= stop)
83143
splitted_data = AnnotationIR()
84-
splitted_data.tags = copy.deepcopy(list(filter(is_frame_inside, self.tags)))
85-
splitted_data.shapes = copy.deepcopy(list(filter(is_frame_inside, self.shapes)))
86-
splitted_data.tracks = copy.deepcopy(list(filter(lambda y: len(list(filter(is_frame_inside, y['shapes']))), self.tracks)))
144+
splitted_data.tags = [copy.deepcopy(t) for t in self.tags if self._is_shape_inside(t, start, stop)]
145+
splitted_data.shapes = [copy.deepcopy(s) for s in self.shapes if self._is_shape_inside(s, start, stop)]
146+
splitted_data.tracks = [self._slice_track(t, start, stop) for t in self.tracks if self._is_track_inside(t, start, stop)]
87147

88148
return splitted_data
89149

cvat/apps/engine/annotation.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from enum import Enum
77
from collections import OrderedDict
88
from django.utils import timezone
9-
from PIL import Image
109

1110
from django.conf import settings
1211
from django.db import transaction

cvat/apps/engine/data_manager.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,15 @@ def _modify_unmached_object(obj, end_frame):
290290
shape["frame"] = end_frame
291291
shape["outside"] = True
292292
obj["shapes"].append(shape)
293+
# Need to update cached interpolated shapes
294+
# because key shapes were changed
295+
if obj.get("interpolated_shapes"):
296+
last_interpolated_shape = obj["interpolated_shapes"][-1]
297+
for frame in range(last_interpolated_shape["frame"] + 1, end_frame):
298+
last_interpolated_shape = copy.deepcopy(last_interpolated_shape)
299+
last_interpolated_shape["frame"] = frame
300+
obj["interpolated_shapes"].append(last_interpolated_shape)
301+
obj["interpolated_shapes"].append(shape)
293302

294303
@staticmethod
295304
def normalize_shape(shape):

0 commit comments

Comments
 (0)