Don't export outside annotations (#1729)

* Add option to omit outside annotations

* update changelog

* Fix mot format and test

* Fix outside in mot

* fix repo problem

* t

* Update CHANGELOG.md

Co-authored-by: Nikita Manovich <40690625+nmanovic@users.noreply.github.com>
main
zhiltsov-max 6 years ago committed by GitHub
parent 18f6b2f95d
commit fc2fb6156a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Some objects aren't shown on canvas sometimes. For example after propagation on of objects is invisible (<https://github.com/opencv/cvat/pull/1834>) - Some objects aren't shown on canvas sometimes. For example after propagation on of objects is invisible (<https://github.com/opencv/cvat/pull/1834>)
- `outside` annotations should not be in exported images (<https://github.com/opencv/cvat/issues/1620>)
### Security ### Security
- -

@ -442,9 +442,10 @@ class TaskData:
return None return None
class CvatTaskDataExtractor(datumaro.SourceExtractor): class CvatTaskDataExtractor(datumaro.SourceExtractor):
def __init__(self, task_data, include_images=False): def __init__(self, task_data, include_images=False, include_outside=False):
super().__init__() super().__init__()
self._categories = self._load_categories(task_data) self._categories = self._load_categories(task_data)
self._include_outside = include_outside
dm_items = [] dm_items = []
@ -541,6 +542,9 @@ class CvatTaskDataExtractor(datumaro.SourceExtractor):
anno_attr['track_id'] = shape_obj.track_id anno_attr['track_id'] = shape_obj.track_id
anno_attr['keyframe'] = shape_obj.keyframe anno_attr['keyframe'] = shape_obj.keyframe
if not self._include_outside and shape_obj.outside:
continue
anno_points = shape_obj.points anno_points = shape_obj.points
if shape_obj.type == ShapeType.POINTS: if shape_obj.type == ShapeType.POINTS:
anno = datumaro.Points(anno_points, anno = datumaro.Points(anno_points,

@ -63,7 +63,7 @@ def _import(src_file, task_data):
points=ann.points, points=ann.points,
occluded=ann.attributes.get('occluded') == True, occluded=ann.attributes.get('occluded') == True,
outside=False, outside=False,
keyframe=False, keyframe=True,
z_order=ann.z_order, z_order=ann.z_order,
frame=frame_number, frame=frame_number,
attributes=[], attributes=[],
@ -78,6 +78,11 @@ def _import(src_file, task_data):
for track in tracks.values(): for track in tracks.values():
# MOT annotations do not require frames to be ordered # MOT annotations do not require frames to be ordered
track.shapes.sort(key=lambda t: t.frame) track.shapes.sort(key=lambda t: t.frame)
# Set outside=True for the last shape in a track to finish the track # Append a shape with outside=True to finish the track
track.shapes[-1] = track.shapes[-1]._replace(outside=True) last_shape = track.shapes[-1]
if last_shape.frame + task_data.frame_step <= \
int(task_data.meta['task']['stop_frame']):
track.shapes.append(last_shape._replace(outside=True,
frame=last_shape.frame + task_data.frame_step)
)
task_data.add_track(track) task_data.add_track(track)

@ -77,7 +77,8 @@ from cvat.apps.engine.models import Task
_setUpModule() _setUpModule()
from cvat.apps.dataset_manager.annotation import AnnotationIR from cvat.apps.dataset_manager.annotation import AnnotationIR
from cvat.apps.dataset_manager.bindings import TaskData from cvat.apps.dataset_manager.bindings import TaskData, CvatTaskDataExtractor
from cvat.apps.dataset_manager.task import TaskAnnotation
from cvat.apps.engine.models import Task from cvat.apps.engine.models import Task
@ -406,6 +407,22 @@ class TaskExportTest(_DbTestBase):
self.assertEqual(len(dataset), task["size"]) self.assertEqual(len(dataset), task["size"])
self._test_export(check, task, format_name, save_images=False) self._test_export(check, task, format_name, save_images=False)
def test_can_skip_outside(self):
images = self._generate_task_images(3)
task = self._generate_task(images)
self._generate_annotations(task)
task_ann = TaskAnnotation(task["id"])
task_ann.init_from_db()
task_data = TaskData(task_ann.ir_data, Task.objects.get(pk=task["id"]))
extractor = CvatTaskDataExtractor(task_data, include_outside=False)
dm_dataset = datumaro.components.project.Dataset.from_extractors(extractor)
self.assertEqual(4, len(dm_dataset.get("image_1").annotations))
extractor = CvatTaskDataExtractor(task_data, include_outside=True)
dm_dataset = datumaro.components.project.Dataset.from_extractors(extractor)
self.assertEqual(5, len(dm_dataset.get("image_1").annotations))
def test_cant_make_rel_frame_id_from_unknown(self): def test_cant_make_rel_frame_id_from_unknown(self):
images = self._generate_task_images(3) images = self._generate_task_images(3)
images['frame_filter'] = 'step=2' images['frame_filter'] = 'step=2'

@ -2965,6 +2965,19 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase):
"points": [2.0, 2.1, 77.2, 36.22], "points": [2.0, 2.1, 77.2, 36.22],
"type": "rectangle", "type": "rectangle",
"occluded": True, "occluded": True,
"outside": False,
"attributes": [
{
"spec_id": task["labels"][0]["attributes"][1]["id"],
"value": task["labels"][0]["attributes"][1]["default_value"]
}
]
},
{
"frame": 2,
"points": [2.0, 2.1, 77.2, 36.22],
"type": "rectangle",
"occluded": True,
"outside": True, "outside": True,
"attributes": [ "attributes": [
{ {
@ -2976,19 +2989,27 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase):
] ]
}] }]
rectangle_tracks_wo_attrs = [{ rectangle_tracks_wo_attrs = [{
"frame": 1, "frame": 0,
"label_id": task["labels"][1]["id"], "label_id": task["labels"][1]["id"],
"group": 0, "group": 0,
"attributes": [], "attributes": [],
"shapes": [ "shapes": [
{ {
"frame": 1, "frame": 0,
"attributes": [], "attributes": [],
"points": [1.0, 2.1, 50.2, 36.6], "points": [1.0, 2.1, 50.2, 36.6],
"type": "rectangle", "type": "rectangle",
"occluded": False, "occluded": False,
"outside": False "outside": False
}, },
{
"frame": 1,
"attributes": [],
"points": [1.0, 2.1, 51, 36.6],
"type": "rectangle",
"occluded": False,
"outside": False
},
{ {
"frame": 2, "frame": 2,
"attributes": [], "attributes": [],

@ -104,7 +104,7 @@ class GitWrapper:
def __init__(self, config=None): def __init__(self, config=None):
self.repo = None self.repo = None
if config is not None and osp.isdir(config.project_dir): if config is not None and config.project_dir:
self.init(config.project_dir) self.init(config.project_dir)
@staticmethod @staticmethod
@ -116,8 +116,12 @@ class GitWrapper:
spawn = not osp.isdir(cls._git_dir(path)) spawn = not osp.isdir(cls._git_dir(path))
repo = git.Repo.init(path=path) repo = git.Repo.init(path=path)
if spawn: if spawn:
author = git.Actor("Nobody", "nobody@example.com") repo.config_writer().set_value("user", "name", "User") \
repo.index.commit('Initial commit', author=author) .set_value("user", "email", "user@nowhere.com") \
.release()
# gitpython does not support init, use git directly
repo.git.init()
repo.git.commit('-m', 'Initial commit', '--allow-empty')
return repo return repo
def init(self, path): def init(self, path):
@ -377,9 +381,10 @@ class Dataset(Extractor):
def get(self, item_id, subset=None, path=None): def get(self, item_id, subset=None, path=None):
if path: if path:
raise KeyError("Requested dataset item path is not found") raise KeyError("Requested dataset item path is not found")
if subset is None: item_id = str(item_id)
subset = '' subset = subset or ''
return self._subsets[subset].items[item_id] subset = self._subsets[subset]
return subset.items[item_id]
def put(self, item, item_id=None, subset=None, path=None): def put(self, item, item_id=None, subset=None, path=None):
if path: if path:
@ -567,7 +572,7 @@ class ProjectDataset(Dataset):
rest_path = path[1:] rest_path = path[1:]
return self._sources[source].get( return self._sources[source].get(
item_id=item_id, subset=subset, path=rest_path) item_id=item_id, subset=subset, path=rest_path)
return self._subsets[subset].items[item_id] return super().get(item_id, subset)
def put(self, item, item_id=None, subset=None, path=None): def put(self, item, item_id=None, subset=None, path=None):
if path is None: if path is None:
@ -754,9 +759,10 @@ class Project:
@staticmethod @staticmethod
def generate(save_dir, config=None): def generate(save_dir, config=None):
config = Config(config)
config.project_dir = save_dir
project = Project(config) project = Project(config)
project.save(save_dir) project.save(save_dir)
project.config.project_dir = save_dir
return project return project
@staticmethod @staticmethod

Loading…
Cancel
Save