diff --git a/CHANGELOG.md b/CHANGELOG.md index 09f651e5b89f..988a2c31ef87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Some objects aren't shown on canvas sometimes. For example after propagation on of objects is invisible () +- `outside` annotations should not be in exported images () ### Security - diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py index d7c92ed4f724..787c7f8e5cb3 100644 --- a/cvat/apps/dataset_manager/bindings.py +++ b/cvat/apps/dataset_manager/bindings.py @@ -442,9 +442,10 @@ def match_frame_fuzzy(self, path): return None 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__() self._categories = self._load_categories(task_data) + self._include_outside = include_outside dm_items = [] @@ -541,6 +542,9 @@ def convert_attrs(label, cvat_attrs): anno_attr['track_id'] = shape_obj.track_id anno_attr['keyframe'] = shape_obj.keyframe + if not self._include_outside and shape_obj.outside: + continue + anno_points = shape_obj.points if shape_obj.type == ShapeType.POINTS: anno = datumaro.Points(anno_points, diff --git a/cvat/apps/dataset_manager/formats/mot.py b/cvat/apps/dataset_manager/formats/mot.py index 1efb9b578c6c..37fa7a7bc793 100644 --- a/cvat/apps/dataset_manager/formats/mot.py +++ b/cvat/apps/dataset_manager/formats/mot.py @@ -63,7 +63,7 @@ def _import(src_file, task_data): points=ann.points, occluded=ann.attributes.get('occluded') == True, outside=False, - keyframe=False, + keyframe=True, z_order=ann.z_order, frame=frame_number, attributes=[], @@ -78,6 +78,11 @@ def _import(src_file, task_data): for track in tracks.values(): # MOT annotations do not require frames to be ordered track.shapes.sort(key=lambda t: t.frame) - # Set outside=True for the last shape in a track to finish the track - track.shapes[-1] = track.shapes[-1]._replace(outside=True) + # Append a shape with outside=True to finish the track + 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) diff --git a/cvat/apps/dataset_manager/tests/_test_formats.py b/cvat/apps/dataset_manager/tests/_test_formats.py index c6c8ab7feb73..5dbee71c44c6 100644 --- a/cvat/apps/dataset_manager/tests/_test_formats.py +++ b/cvat/apps/dataset_manager/tests/_test_formats.py @@ -77,7 +77,8 @@ def _setUpModule(): _setUpModule() 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 @@ -406,6 +407,22 @@ def load_dataset(src): self.assertEqual(len(dataset), task["size"]) 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): images = self._generate_task_images(3) images['frame_filter'] = 'step=2' diff --git a/cvat/apps/engine/tests/_test_rest_api.py b/cvat/apps/engine/tests/_test_rest_api.py index 8046efbc4b6e..44d8f32a9246 100644 --- a/cvat/apps/engine/tests/_test_rest_api.py +++ b/cvat/apps/engine/tests/_test_rest_api.py @@ -2965,6 +2965,19 @@ def _get_initial_annotation(annotation_format): "points": [2.0, 2.1, 77.2, 36.22], "type": "rectangle", "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, "attributes": [ { @@ -2976,19 +2989,27 @@ def _get_initial_annotation(annotation_format): ] }] rectangle_tracks_wo_attrs = [{ - "frame": 1, + "frame": 0, "label_id": task["labels"][1]["id"], "group": 0, "attributes": [], "shapes": [ { - "frame": 1, + "frame": 0, "attributes": [], "points": [1.0, 2.1, 50.2, 36.6], "type": "rectangle", "occluded": False, "outside": False }, + { + "frame": 1, + "attributes": [], + "points": [1.0, 2.1, 51, 36.6], + "type": "rectangle", + "occluded": False, + "outside": False + }, { "frame": 2, "attributes": [], diff --git a/datumaro/datumaro/components/project.py b/datumaro/datumaro/components/project.py index 4fe08b13f4fd..c99b514f6e38 100644 --- a/datumaro/datumaro/components/project.py +++ b/datumaro/datumaro/components/project.py @@ -104,7 +104,7 @@ class GitWrapper: def __init__(self, config=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) @staticmethod @@ -116,8 +116,12 @@ def spawn(cls, path): spawn = not osp.isdir(cls._git_dir(path)) repo = git.Repo.init(path=path) if spawn: - author = git.Actor("Nobody", "nobody@example.com") - repo.index.commit('Initial commit', author=author) + repo.config_writer().set_value("user", "name", "User") \ + .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 def init(self, path): @@ -377,9 +381,10 @@ def categories(self): def get(self, item_id, subset=None, path=None): if path: raise KeyError("Requested dataset item path is not found") - if subset is None: - subset = '' - return self._subsets[subset].items[item_id] + item_id = str(item_id) + subset = subset or '' + subset = self._subsets[subset] + return subset.items[item_id] def put(self, item, item_id=None, subset=None, path=None): if path: @@ -567,7 +572,7 @@ def get(self, item_id, subset=None, path=None): rest_path = path[1:] return self._sources[source].get( 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): if path is None: @@ -754,9 +759,10 @@ def save(self, save_dir=None): @staticmethod def generate(save_dir, config=None): + config = Config(config) + config.project_dir = save_dir project = Project(config) project.save(save_dir) - project.config.project_dir = save_dir return project @staticmethod