Skip to content

Commit

Permalink
Fixed uploading track annotations for multi-segment tasks (#1396)
Browse files Browse the repository at this point in the history
* 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 <40690625+nmanovic@users.noreply.github.com>

* drop start shapes with outside==True for splitted track

* code cleanup

* fixed typo

* fix

Co-authored-by: Nikita Manovich <40690625+nmanovic@users.noreply.github.com>
  • Loading branch information
azhavoro and nmanovic committed Apr 30, 2020
1 parent ee6deba commit 227ab05
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- AttributeError: 'tuple' object has no attribute 'read' in ReID algorithm (https://github.com/opencv/cvat/issues/1403)
- Wrong semi-automatic segmentation near edges of an image (https://github.com/opencv/cvat/issues/1403)
- Git repos paths (https://github.com/opencv/cvat/pull/1400)
- Uploading annotations for tasks with multiple jobs (https://github.com/opencv/cvat/pull/1396)

## [1.0.0-alpha] - 2020-03-31
### Added
Expand Down
68 changes: 64 additions & 4 deletions cvat/apps/annotation/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,73 @@ def serialize(self):
if serializer.is_valid(raise_exception=True):
return serializer.data

@staticmethod
def _is_shape_inside(shape, start, stop):
return start <= int(shape['frame']) <= stop

@staticmethod
def _is_track_inside(track, start, stop):
# a <= b
def has_overlap(a, b):
return 0 <= min(b, stop) - max(a, start)

prev_shape = None
for shape in track['shapes']:
if prev_shape and not prev_shape['outside'] and \
has_overlap(prev_shape['frame'], shape['frame']):
return True
prev_shape = shape

if not prev_shape['outside'] and prev_shape['frame'] <= stop:
return True

return False

@staticmethod
def _slice_track(track_, start, stop):
def filter_track_shapes(shapes):
shapes = [s for s in shapes if AnnotationIR._is_shape_inside(s, start, stop)]
drop_count = 0
for s in shapes:
if s['outside']:
drop_count += 1
else:
break
# Need to leave the last shape if all shapes are outside
if drop_count == len(shapes):
drop_count -= 1

return shapes[drop_count:]

track = copy.deepcopy(track_)
segment_shapes = filter_track_shapes(track['shapes'])

if len(segment_shapes) < len(track['shapes']):
interpolated_shapes = TrackManager.get_interpolated_shapes(track, start, stop)
scoped_shapes = filter_track_shapes(interpolated_shapes)

if scoped_shapes:
if not scoped_shapes[0]['keyframe']:
segment_shapes.insert(0, scoped_shapes[0])
if not scoped_shapes[-1]['keyframe']:
segment_shapes.append(scoped_shapes[-1])

# Should delete 'interpolation_shapes' and 'keyframe' keys because
# Track and TrackedShape models don't expect these fields
del track['interpolated_shapes']
for shape in segment_shapes:
del shape['keyframe']

track['shapes'] = segment_shapes
track['frame'] = track['shapes'][0]['frame']
return track

#makes a data copy from specified frame interval
def slice(self, start, stop):
is_frame_inside = lambda x: (start <= int(x['frame']) <= stop)
splitted_data = AnnotationIR()
splitted_data.tags = copy.deepcopy(list(filter(is_frame_inside, self.tags)))
splitted_data.shapes = copy.deepcopy(list(filter(is_frame_inside, self.shapes)))
splitted_data.tracks = copy.deepcopy(list(filter(lambda y: len(list(filter(is_frame_inside, y['shapes']))), self.tracks)))
splitted_data.tags = [copy.deepcopy(t) for t in self.tags if self._is_shape_inside(t, start, stop)]
splitted_data.shapes = [copy.deepcopy(s) for s in self.shapes if self._is_shape_inside(s, start, stop)]
splitted_data.tracks = [self._slice_track(t, start, stop) for t in self.tracks if self._is_track_inside(t, start, stop)]

return splitted_data

Expand Down
1 change: 0 additions & 1 deletion cvat/apps/engine/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from enum import Enum
from collections import OrderedDict
from django.utils import timezone
from PIL import Image

from django.conf import settings
from django.db import transaction
Expand Down
9 changes: 9 additions & 0 deletions cvat/apps/engine/data_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,15 @@ def _modify_unmached_object(obj, end_frame):
shape["frame"] = end_frame
shape["outside"] = True
obj["shapes"].append(shape)
# Need to update cached interpolated shapes
# because key shapes were changed
if obj.get("interpolated_shapes"):
last_interpolated_shape = obj["interpolated_shapes"][-1]
for frame in range(last_interpolated_shape["frame"] + 1, end_frame):
last_interpolated_shape = copy.deepcopy(last_interpolated_shape)
last_interpolated_shape["frame"] = frame
obj["interpolated_shapes"].append(last_interpolated_shape)
obj["interpolated_shapes"].append(shape)

@staticmethod
def normalize_shape(shape):
Expand Down

0 comments on commit 227ab05

Please sign in to comment.