diff --git a/CHANGELOG.md b/CHANGELOG.md index f768ba85..89c43bf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/cvat/apps/annotation/annotation.py b/cvat/apps/annotation/annotation.py index 49ababe9..86cdeb52 100644 --- a/cvat/apps/annotation/annotation.py +++ b/cvat/apps/annotation/annotation.py @@ -77,13 +77,73 @@ class AnnotationIR: 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 diff --git a/cvat/apps/engine/annotation.py b/cvat/apps/engine/annotation.py index bacfd0d7..ad005bd1 100644 --- a/cvat/apps/engine/annotation.py +++ b/cvat/apps/engine/annotation.py @@ -6,7 +6,6 @@ import os 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 diff --git a/cvat/apps/engine/data_manager.py b/cvat/apps/engine/data_manager.py index b39c6783..7da117c1 100644 --- a/cvat/apps/engine/data_manager.py +++ b/cvat/apps/engine/data_manager.py @@ -290,6 +290,15 @@ class TrackManager(ObjectManager): 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):