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

Fix 2827 #2890

Merged
merged 3 commits into from
Mar 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed of receiving function variable (<https://github.com/openvinotoolkit/cvat/pull/2860>)
- Shortcuts with CAPSLOCK enabled and with non-US languages activated (<https://github.com/openvinotoolkit/cvat/pull/2872>)
- Fixed label editor name field validator (<https://github.com/openvinotoolkit/cvat/pull/2879>)
- An error about track shapes outside of the task frames during export (<https://github.com/openvinotoolkit/cvat/pull/2890>)

### Security

Expand Down
6 changes: 5 additions & 1 deletion cvat/apps/dataset_manager/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def filter_track_shapes(shapes):
# Track and TrackedShape models don't expect these fields
del track['interpolated_shapes']
for shape in segment_shapes:
del shape['keyframe']
shape.pop('keyframe', None)

track['shapes'] = segment_shapes
track['frame'] = track['shapes'][0]['frame']
Expand Down Expand Up @@ -746,6 +746,10 @@ def interpolate(shape0, shape1):
curr_frame = shape["frame"]
prev_shape = shape

# keep at least 1 shape
if end_frame <= curr_frame:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we put the condition before iteration code (a bit changed):

if shape["frame"] >= end_frame:
    break

We probably do not need additional check in cvat/apps/dataset_manager/bindings.py. Right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if interpolation is the only source of this problem, so I'd better keep the check in bindings.py anyway. I wouldn't remove this check in the interpolation function above, because the code using this function definitely expects at least 1 frame in a track, so putting it before loop would break this convention.

break

if not prev_shape["outside"]:
shape = copy(prev_shape)
shape["frame"] = end_frame
Expand Down
5 changes: 5 additions & 0 deletions cvat/apps/dataset_manager/bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,11 @@ def get_frame(idx):
anno_manager = AnnotationManager(self._annotation_ir)
for shape in sorted(anno_manager.to_shapes(self._db_task.data.size),
key=lambda shape: shape.get("z_order", 0)):
if shape['frame'] not in self._frame_info:
# After interpolation there can be a finishing frame
# outside of the task boundaries. Filter it out to avoid errors.
# https://github.com/openvinotoolkit/cvat/issues/2827
continue
if 'track_id' in shape:
if shape['outside']:
continue
Expand Down
58 changes: 55 additions & 3 deletions cvat/apps/dataset_manager/tests/test_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ def _put_api_v1_task_id_annotations(self, tid, data):

return response

def _put_api_v1_job_id_annotations(self, jid, data):
with ForceLogin(self.user, self.client):
response = self.client.put("/api/v1/jobs/%s/annotations" % jid,
data=data, format="json")

return response

def _create_task(self, data, image_data):
with ForceLogin(self.user, self.client):
response = self.client.post('/api/v1/tasks', data=data, format="json")
Expand All @@ -87,6 +94,10 @@ def _create_task(self, data, image_data):
return task

class TaskExportTest(_DbTestBase):
def _generate_custom_annotations(self, annotations, task):
self._put_api_v1_task_id_annotations(task["id"], annotations)
return annotations

def _generate_annotations(self, task):
annotations = {
"version": 0,
Expand Down Expand Up @@ -204,8 +215,7 @@ def _generate_annotations(self, task):
},
]
}
self._put_api_v1_task_id_annotations(task["id"], annotations)
return annotations
return self._generate_custom_annotations(annotations, task)

def _generate_task_images(self, count): # pylint: disable=no-self-use
images = {
Expand All @@ -215,7 +225,7 @@ def _generate_task_images(self, count): # pylint: disable=no-self-use
images["image_quality"] = 75
return images

def _generate_task(self, images):
def _generate_task(self, images, **overrides):
task = {
"name": "my task #1",
"overlap": 0,
Expand All @@ -242,6 +252,7 @@ def _generate_task(self, images):
{"name": "person"},
]
}
task.update(overrides)
return self._create_task(task, images)

@staticmethod
Expand Down Expand Up @@ -422,6 +433,47 @@ def test_can_make_abs_frame_id_from_known(self):

self.assertEqual(5, task_data.abs_frame_id(2))

def test_frames_outside_are_not_generated(self):
# https://github.com/openvinotoolkit/cvat/issues/2827
images = self._generate_task_images(10)
images['start_frame'] = 0
task = self._generate_task(images, overlap=3, segment_size=6)
annotations = {
"version": 0,
"tags": [],
"shapes": [],
"tracks": [
{
"frame": 6,
"label_id": task["labels"][0]["id"],
"group": None,
"source": "manual",
"attributes": [],
"shapes": [
{
"frame": 6,
"points": [1.0, 2.1, 100, 300.222],
"type": "rectangle",
"occluded": False,
"outside": False,
"attributes": [],
},
]
},
]
}
self._put_api_v1_job_id_annotations(
task["segments"][2]["jobs"][0]["id"], annotations)

task_ann = TaskAnnotation(task["id"])
task_ann.init_from_db()
task_data = TaskData(task_ann.ir_data, Task.objects.get(pk=task['id']))

i = -1
for i, frame in enumerate(task_data.group_by_frame()):
self.assertTrue(frame.frame in range(6, 10))
self.assertEqual(i + 1, 4)

class FrameMatchingTest(_DbTestBase):
def _generate_task_images(self, paths): # pylint: disable=no-self-use
f = BytesIO()
Expand Down