diff --git a/CHANGELOG.md b/CHANGELOG.md index 2305f17cfe50..ccb7951f8cce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - `api/docs`, `api/swagger`, `api/schema` endpoints now allow unauthorized access () +- Datumaro version () ### Deprecated - TDB @@ -20,9 +21,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - TDB ### Fixed -- Removed a possibly duplicated encodeURI() calls in `server-proxy.ts` to prevent doubly encoding +- Removed a possibly duplicated encodeURI() calls in `server-proxy.ts` to prevent doubly encoding non-ascii paths while adding files from "Connected file share" (issue #4428) -- Removed unnecessary volumes defined in docker-compose.serverless.yml +- Removed unnecessary volumes defined in docker-compose.serverless.yml () ### Security diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py index b416bcae85c8..602fd4d603c9 100644 --- a/cvat/apps/dataset_manager/bindings.py +++ b/cvat/apps/dataset_manager/bindings.py @@ -1,5 +1,6 @@ # Copyright (C) 2019-2022 Intel Corporation +# Copyright (C) 2022 CVAT.ai Corporation # # SPDX-License-Identifier: MIT @@ -12,13 +13,10 @@ from typing import (Any, Callable, DefaultDict, Dict, List, Literal, Mapping, NamedTuple, OrderedDict, Set, Tuple, Union) -import datumaro.components.annotation as datum_annotation -import datumaro.components.extractor as datum_extractor +import datumaro as dm import rq from attr import attrib, attrs -from datumaro.components.dataset import Dataset -from datumaro.util import cast -from datumaro.util.image import ByteImage, Image +from datumaro.components.media import PointCloud from django.utils import timezone from cvat.apps.dataset_manager.formats.utils import get_label_color @@ -241,7 +239,7 @@ def meta_for_task(db_task, host, label_mapping=None): ("bugtracker", db_task.bug_tracker), ("created", str(timezone.localtime(db_task.created_date))), ("updated", str(timezone.localtime(db_task.updated_date))), - ("subset", db_task.subset or datum_extractor.DEFAULT_SUBSET_NAME), + ("subset", db_task.subset or dm.DEFAULT_SUBSET_NAME), ("start_frame", str(db_task.data.start_frame)), ("stop_frame", str(db_task.data.stop_frame)), ("frame_filter", db_task.data.frame_filter), @@ -781,7 +779,7 @@ def _init_meta(self): ) for db_task in self._db_tasks.values() ]), - ("subsets", '\n'.join([s if s else datum_extractor.DEFAULT_SUBSET_NAME for s in self._subsets])), + ("subsets", '\n'.join([s if s else dm.DEFAULT_SUBSET_NAME for s in self._subsets])), ("owner", OrderedDict([ ("username", self._db_project.owner.username), @@ -1024,7 +1022,7 @@ def task_data(self): def _get_filename(path): return osp.splitext(path)[0] - def match_frame(self, path: str, subset: str=datum_extractor.DEFAULT_SUBSET_NAME, root_hint: str=None, path_has_ext: bool=True): + def match_frame(self, path: str, subset: str=dm.DEFAULT_SUBSET_NAME, root_hint: str=None, path_has_ext: bool=True): if path_has_ext: path = self._get_filename(path) match_task, match_frame = self._frame_mapping.get((subset, path), (None, None)) @@ -1040,11 +1038,11 @@ def match_frame_fuzzy(self, path): return frame_number return None - def split_dataset(self, dataset: Dataset): + def split_dataset(self, dataset: dm.Dataset): for task_data in self.task_data: if task_data._db_task.id not in self.new_tasks: continue - subset_dataset: Dataset = dataset.subsets()[task_data.db_task.subset].as_dataset() + subset_dataset: dm.Dataset = dataset.subsets()[task_data.db_task.subset].as_dataset() yield subset_dataset, task_data def add_labels(self, labels: List[dict]): @@ -1060,7 +1058,7 @@ def add_task(self, task, files): self._project_annotation.add_task(task, files, self) class CVATDataExtractorMixin: - def __init__(self): + def __init__(self, media_type=dm.Image): super().__init__() def categories(self) -> dict: @@ -1068,28 +1066,27 @@ def categories(self) -> dict: @staticmethod def _load_categories(labels: list): - categories: Dict[datum_annotation.AnnotationType, - datum_annotation.Categories] = {} + categories: Dict[dm.AnnotationType, + dm.Categories] = {} - label_categories = datum_annotation.LabelCategories(attributes=['occluded']) - point_categories = datum_annotation.PointsCategories() + label_categories = dm.LabelCategories(attributes=['occluded']) + point_categories = dm.PointsCategories() for _, label in labels: - if label.get('parent') is None: - label_id = label_categories.add(label['name']) - for _, attr in label['attributes']: - label_categories.attributes.add(attr['name']) + label_id = label_categories.add(label['name'], label.get('parent')) + for _, attr in label['attributes']: + label_categories.attributes.add(attr['name']) - if label['type'] == str(LabelType.SKELETON): - labels_from = list(map(int, re.findall(r'data-node-from="(\d+)"', label['svg']))) - labels_to = list(map(int, re.findall(r'data-node-to="(\d+)"', label['svg']))) - sublabels = re.findall(r'data-label-name="(\w+)"', label['svg']) - joints = zip(labels_from, labels_to) + if label['type'] == str(LabelType.SKELETON): + labels_from = list(map(int, re.findall(r'data-node-from="(\d+)"', label['svg']))) + labels_to = list(map(int, re.findall(r'data-node-to="(\d+)"', label['svg']))) + sublabels = re.findall(r'data-label-name="(\w+)"', label['svg']) + joints = zip(labels_from, labels_to) - point_categories.add(label_id, sublabels, joints) + point_categories.add(label_id, sublabels, joints) - categories[datum_annotation.AnnotationType.label] = label_categories - categories[datum_annotation.AnnotationType.points] = point_categories + categories[dm.AnnotationType.label] = label_categories + categories[dm.AnnotationType.points] = point_categories return categories @@ -1103,7 +1100,7 @@ def _load_user_info(meta: dict): def _read_cvat_anno(self, cvat_frame_anno: Union[ProjectData.Frame, TaskData.Frame], labels: list): categories = self.categories() - label_cat = categories[datum_annotation.AnnotationType.label] + label_cat = categories[dm.AnnotationType.label] def map_label(name): return label_cat.find(name)[0] label_attrs = { label['name']: label['attributes'] @@ -1113,9 +1110,9 @@ def map_label(name): return label_cat.find(name)[0] return convert_cvat_anno_to_dm(cvat_frame_anno, label_attrs, map_label) -class CvatTaskDataExtractor(datum_extractor.SourceExtractor, CVATDataExtractorMixin): +class CvatTaskDataExtractor(dm.SourceExtractor, CVATDataExtractorMixin): def __init__(self, task_data, include_images=False, format_type=None, dimension=DimensionType.DIM_2D): - super().__init__() + super().__init__(media_type=dm.Image if dimension == DimensionType.DIM_2D else PointCloud) self._categories = self._load_categories(task_data.meta['task']['labels']) self._user = self._load_user_info(task_data.meta['task']) if dimension == DimensionType.DIM_3D else {} self._dimension = dimension @@ -1148,14 +1145,14 @@ def _make_image(i, **kwargs): loader = lambda _: frame_provider.get_frame(i, quality=frame_provider.Quality.ORIGINAL, out_type=frame_provider.Type.NUMPY_ARRAY)[0] - return Image(loader=loader, **kwargs) + return dm.Image(data=loader, **kwargs) else: # for images use encoded data to avoid recoding def _make_image(i, **kwargs): loader = lambda _: frame_provider.get_frame(i, quality=frame_provider.Quality.ORIGINAL, out_type=frame_provider.Type.BUFFER)[0].getvalue() - return ByteImage(data=loader, **kwargs) + return dm.ByteImage(data=loader, **kwargs) for frame_data in task_data.group_by_frame(include_empty=True): image_args = { @@ -1168,13 +1165,13 @@ def _make_image(i, **kwargs): elif include_images: dm_image = _make_image(frame_data.idx, **image_args) else: - dm_image = Image(**image_args) + dm_image = dm.Image(**image_args) dm_anno = self._read_cvat_anno(frame_data, task_data.meta['task']['labels']) if dimension == DimensionType.DIM_2D: - dm_item = datum_extractor.DatasetItem( + dm_item = dm.DatasetItem( id=osp.splitext(frame_data.name)[0], - annotations=dm_anno, image=dm_image, + annotations=dm_anno, media=dm_image, attributes={'frame': frame_data.frame }) elif dimension == DimensionType.DIM_3D: @@ -1188,9 +1185,9 @@ def _make_image(i, **kwargs): attributes["labels"].append({"label_id": idx, "name": label["name"], "color": label["color"], "type": label["type"]}) attributes["track_id"] = -1 - dm_item = datum_extractor.DatasetItem( + dm_item = dm.DatasetItem( id=osp.splitext(osp.split(frame_data.name)[-1])[0], - annotations=dm_anno, point_cloud=dm_image[0], related_images=dm_image[1], + annotations=dm_anno, media=PointCloud(dm_image[0]), related_images=dm_image[1], attributes=attributes ) @@ -1200,24 +1197,24 @@ def _make_image(i, **kwargs): def _read_cvat_anno(self, cvat_frame_anno: TaskData.Frame, labels: list): categories = self.categories() - label_cat = categories[datum_annotation.AnnotationType.label] - def map_label(name): return label_cat.find(name)[0] + label_cat = categories[dm.AnnotationType.label] + def map_label(name, parent=""): return label_cat.find(name, parent)[0] label_attrs = { - label['name']: label['attributes'] + label.get("parent", "") + label['name']: label['attributes'] for _, label in labels } return convert_cvat_anno_to_dm(cvat_frame_anno, label_attrs, map_label, self._format_type, self._dimension) -class CVATProjectDataExtractor(datum_extractor.Extractor, CVATDataExtractorMixin): +class CVATProjectDataExtractor(dm.Extractor, CVATDataExtractorMixin): def __init__(self, project_data: ProjectData, include_images: bool = False, format_type: str = None, dimension: DimensionType = DimensionType.DIM_2D): - super().__init__() + super().__init__(media_type=dm.Image if dimension == DimensionType.DIM_2D else PointCloud) self._categories = self._load_categories(project_data.meta['project']['labels']) self._user = self._load_user_info(project_data.meta['project']) if dimension == DimensionType.DIM_3D else {} self._dimension = dimension self._format_type = format_type - dm_items: List[datum_extractor.DatasetItem] = [] + dm_items: List[dm.DatasetItem] = [] ext_per_task: Dict[int, str] = {} image_maker_per_task: Dict[int, Callable] = {} @@ -1251,7 +1248,7 @@ def _make_image(i, **kwargs): loader = lambda _: frame_provider.get_frame(i, quality=frame_provider.Quality.ORIGINAL, out_type=frame_provider.Type.NUMPY_ARRAY)[0] - return Image(loader=loader, **kwargs) + return dm.Image(data=loader, **kwargs) return _make_image else: # for images use encoded data to avoid recoding @@ -1261,7 +1258,7 @@ def _make_image(i, **kwargs): loader = lambda _: frame_provider.get_frame(i, quality=frame_provider.Quality.ORIGINAL, out_type=frame_provider.Type.BUFFER)[0].getvalue() - return ByteImage(data=loader, **kwargs) + return dm.ByteImage(data=loader, **kwargs) return _make_image image_maker_per_task[task.id] = image_maker_factory(task) @@ -1275,12 +1272,12 @@ def _make_image(i, **kwargs): elif include_images: dm_image = image_maker_per_task[frame_data.task_id](frame_data.idx, **image_args) else: - dm_image = Image(**image_args) + dm_image = dm.Image(**image_args) dm_anno = self._read_cvat_anno(frame_data, project_data.meta['project']['labels']) if self._dimension == DimensionType.DIM_2D: - dm_item = datum_extractor.DatasetItem( + dm_item = dm.DatasetItem( id=osp.splitext(frame_data.name)[0], - annotations=dm_anno, image=dm_image, + annotations=dm_anno, media=dm_image, subset=frame_data.subset, attributes={'frame': frame_data.frame} ) @@ -1295,9 +1292,9 @@ def _make_image(i, **kwargs): attributes["labels"].append({"label_id": idx, "name": label["name"], "color": label["color"], "type": label["type"]}) attributes["track_id"] = -1 - dm_item = datum_extractor.DatasetItem( + dm_item = dm.DatasetItem( id=osp.splitext(osp.split(frame_data.name)[-1])[0], - annotations=dm_anno, point_cloud=dm_image[0], related_images=dm_image[1], + annotations=dm_anno, media=PointCloud(dm_image[0]), related_images=dm_image[1], attributes=attributes, subset=frame_data.subset ) dm_items.append(dm_item) @@ -1348,13 +1345,13 @@ def get_defaulted_subset(subset: str, subsets: List[str]) -> str: if subset: return subset else: - if datum_extractor.DEFAULT_SUBSET_NAME not in subsets: - return datum_extractor.DEFAULT_SUBSET_NAME + if dm.DEFAULT_SUBSET_NAME not in subsets: + return dm.DEFAULT_SUBSET_NAME else: i = 1 while i < sys.maxsize: - if f'{datum_extractor.DEFAULT_SUBSET_NAME}_{i}' not in subsets: - return f'{datum_extractor.DEFAULT_SUBSET_NAME}_{i}' + if f'{dm.DEFAULT_SUBSET_NAME}_{i}' not in subsets: + return f'{dm.DEFAULT_SUBSET_NAME}_{i}' i += 1 raise Exception('Cannot find default name for subset') @@ -1385,7 +1382,7 @@ def convert_attrs(label, cvat_attrs): anno_label = map_label(tag_obj.label) anno_attr = convert_attrs(tag_obj.label, tag_obj.attributes) - anno = datum_annotation.Label(label=anno_label, + anno = dm.Label(label=anno_label, attributes=anno_attr, group=anno_group) item_anno.append(anno) @@ -1408,7 +1405,7 @@ def convert_attrs(label, cvat_attrs): anno_points = shape_obj.points if shape_obj.type == ShapeType.POINTS: - anno = datum_annotation.Points(anno_points, + anno = dm.Points(anno_points, label=anno_label, attributes=anno_attr, group=anno_group, z_order=shape_obj.z_order) elif shape_obj.type == ShapeType.ELLIPSE: @@ -1424,16 +1421,16 @@ def convert_attrs(label, cvat_attrs): "attributes": anno_attr, }), cvat_frame_anno.height, cvat_frame_anno.width) elif shape_obj.type == ShapeType.POLYLINE: - anno = datum_annotation.PolyLine(anno_points, + anno = dm.PolyLine(anno_points, label=anno_label, attributes=anno_attr, group=anno_group, z_order=shape_obj.z_order) elif shape_obj.type == ShapeType.POLYGON: - anno = datum_annotation.Polygon(anno_points, + anno = dm.Polygon(anno_points, label=anno_label, attributes=anno_attr, group=anno_group, z_order=shape_obj.z_order) elif shape_obj.type == ShapeType.RECTANGLE: x0, y0, x1, y1 = anno_points - anno = datum_annotation.Bbox(x0, y0, x1 - x0, y1 - y0, + anno = dm.Bbox(x0, y0, x1 - x0, y1 - y0, label=anno_label, attributes=anno_attr, group=anno_group, z_order=shape_obj.z_order) elif shape_obj.type == ShapeType.CUBOID: @@ -1443,27 +1440,30 @@ def convert_attrs(label, cvat_attrs): else: anno_id = index position, rotation, scale = anno_points[0:3], anno_points[3:6], anno_points[6:9] - anno = datum_annotation.Cuboid3d( + anno = dm.Cuboid3d( id=anno_id, position=position, rotation=rotation, scale=scale, label=anno_label, attributes=anno_attr, group=anno_group ) else: continue elif shape_obj.type == ShapeType.SKELETON: - points = [] - vis = [] + elements = [] for element in shape_obj.elements: - points.extend(element.points) - element_vis = datum_annotation.Points.Visibility.visible + element_attr = convert_attrs(shape_obj.label + element.label, element.attributes) + + if hasattr(element, 'track_id'): + element_attr['track_id'] = element.track_id + element_attr['keyframe'] = element.keyframe + element_vis = dm.Points.Visibility.visible if element.outside: - element_vis = datum_annotation.Points.Visibility.absent + element_vis = dm.Points.Visibility.absent elif element.occluded: - element_vis = datum_annotation.Points.Visibility.hidden - vis.append(element_vis) + element_vis = dm.Points.Visibility.hidden + elements.append(dm.Points(element.points, [element_vis], + label=map_label(element.label, shape_obj.label), attributes=element_attr)) - anno = datum_annotation.Points(points, vis, - label=anno_label, attributes=anno_attr, group=anno_group, - z_order=shape_obj.z_order) + anno = dm.Skeleton(elements, label=anno_label, + attributes=anno_attr, group=anno_group, z_order=shape_obj.z_order) else: raise Exception("Unknown shape type '%s'" % shape_obj.type) @@ -1480,9 +1480,9 @@ def match_dm_item(item, task_data, root_hint=None): if frame_number is None: frame_number = task_data.match_frame(item.id, root_hint, path_has_ext=False) if frame_number is None: - frame_number = cast(item.attributes.get('frame', item.id), int) + frame_number = dm.util.cast(item.attributes.get('frame', item.id), int) if frame_number is None and is_video: - frame_number = cast(osp.basename(item.id)[len('frame_'):], int) + frame_number = dm.util.cast(osp.basename(item.id)[len('frame_'):], int) if not frame_number in task_data.frame_info: raise CvatImportError("Could not match item id: " @@ -1505,7 +1505,7 @@ def find_dataset_root(dm_dataset, instance_data: Union[TaskData, ProjectData]): prefix = prefix[:-1] return prefix -def import_dm_annotations(dm_dataset: Dataset, instance_data: Union[TaskData, ProjectData]): +def import_dm_annotations(dm_dataset: dm.Dataset, instance_data: Union[TaskData, ProjectData]): if len(dm_dataset) == 0: return @@ -1518,15 +1518,15 @@ def import_dm_annotations(dm_dataset: Dataset, instance_data: Union[TaskData, Pr return shapes = { - datum_annotation.AnnotationType.bbox: ShapeType.RECTANGLE, - datum_annotation.AnnotationType.polygon: ShapeType.POLYGON, - datum_annotation.AnnotationType.polyline: ShapeType.POLYLINE, - datum_annotation.AnnotationType.points: ShapeType.POINTS, - datum_annotation.AnnotationType.cuboid_3d: ShapeType.CUBOID + dm.AnnotationType.bbox: ShapeType.RECTANGLE, + dm.AnnotationType.polygon: ShapeType.POLYGON, + dm.AnnotationType.polyline: ShapeType.POLYLINE, + dm.AnnotationType.points: ShapeType.POINTS, + dm.AnnotationType.cuboid_3d: ShapeType.CUBOID, + dm.AnnotationType.skeleton: ShapeType.SKELETON } - label_cat = dm_dataset.categories()[datum_annotation.AnnotationType.label] - point_cat = dm_dataset.categories().get(datum_annotation.AnnotationType.points) + label_cat = dm_dataset.categories()[dm.AnnotationType.label] root_hint = find_dataset_root(dm_dataset, instance_data) @@ -1563,54 +1563,57 @@ def import_dm_annotations(dm_dataset: Dataset, instance_data: Union[TaskData, Pr ] if ann.type in shapes: - if ann.type == datum_annotation.AnnotationType.cuboid_3d: - try: - ann.points = [*ann.position,*ann.rotation,*ann.scale,0,0,0,0,0,0,0] - except Exception: - ann.points = ann.points - ann.z_order = 0 + points = [] + if ann.type == dm.AnnotationType.cuboid_3d: + points = [*ann.position, *ann.rotation, *ann.scale, 0, 0, 0, 0, 0, 0, 0] + elif ann.type != dm.AnnotationType.skeleton: + points = ann.points # Use safe casting to bool instead of plain reading # because in some formats return type can be different # from bool / None # https://github.com/openvinotoolkit/datumaro/issues/719 - occluded = cast(ann.attributes.pop('occluded', None), bool) is True - keyframe = cast(ann.attributes.get('keyframe', None), bool) is True - outside = cast(ann.attributes.pop('outside', None), bool) is True + occluded = dm.util.cast(ann.attributes.pop('occluded', None), bool) is True + keyframe = dm.util.cast(ann.attributes.get('keyframe', None), bool) is True + outside = dm.util.cast(ann.attributes.pop('outside', None), bool) is True track_id = ann.attributes.pop('track_id', None) source = ann.attributes.pop('source').lower() \ if ann.attributes.get('source', '').lower() in {'auto', 'manual'} else 'manual' shape_type = shapes[ann.type] - elements = [] - if point_cat and shape_type == ShapeType.POINTS: - labels = point_cat.items[ann.label].labels - shape_type = ShapeType.SKELETON - for i in range(len(ann.points) // 2): - label = None - if i < len(labels): - label = labels[i] - elements.append(instance_data.LabeledShape( - type=ShapeType.POINTS, - frame=frame_number, - points=ann.points[2 * i : 2 * i + 2], - label=label, - occluded=ann.visibility[i] == datum_annotation.Points.Visibility.hidden, - source=source, - attributes=[], - outside=ann.visibility[i] == datum_annotation.Points.Visibility.absent, - )) - - if track_id is None or dm_dataset.format != 'cvat' : + elements = [] + if ann.type == dm.AnnotationType.skeleton: + for element in ann.elements: + element_attributes = [ + instance_data.Attribute(name=n, value=str(v)) + for n, v in element.attributes.items() + ] + element_occluded = element.visibility[0] == dm.Points.Visibility.hidden + element_outside = element.visibility[0] == dm.Points.Visibility.absent + element_source = element.attributes.pop('source').lower() \ + if element.attributes.get('source', '').lower() in {'auto', 'manual'} else 'manual' + elements.append(instance_data.LabeledShape( + type=shapes[element.type], + frame=frame_number, + points=element.points, + label=label_cat.items[element.label].name, + occluded=element_occluded, + z_order=ann.z_order, + group=group_map.get(ann.group, 0), + source=element_source, + attributes=element_attributes, + elements=[], + outside=element_outside, + )) instance_data.add_shape(instance_data.LabeledShape( type=shape_type, frame=frame_number, - points=ann.points, + points=points, label=label_cat.items[ann.label].name, occluded=occluded, - z_order=ann.z_order, + z_order=ann.z_order if ann.type != dm.AnnotationType.cuboid_3d else 0, group=group_map.get(ann.group, 0), source=source, attributes=attributes, @@ -1619,29 +1622,59 @@ def import_dm_annotations(dm_dataset: Dataset, instance_data: Union[TaskData, Pr continue if keyframe or outside: + if track_id not in tracks: + tracks[track_id] = { + 'label': label_cat.items[ann.label].name, + 'group': group_map.get(ann.group, 0), + 'source': source, + 'shapes': [], + 'elements':{}, + } + track = instance_data.TrackedShape( type=shapes[ann.type], frame=frame_number, occluded=occluded, outside=outside, keyframe=keyframe, - points=ann.points, - z_order=ann.z_order, + points=points, + z_order=ann.z_order if ann.type != dm.AnnotationType.cuboid_3d else 0, source=source, attributes=attributes, ) - if track_id not in tracks: - tracks[track_id] = instance_data.Track( - label=label_cat.items[ann.label].name, - group=group_map.get(ann.group, 0), - source=source, - shapes=[], - ) - - tracks[track_id].shapes.append(track) - - elif ann.type == datum_annotation.AnnotationType.label: + tracks[track_id]['shapes'].append(track) + + if ann.type == dm.AnnotationType.skeleton: + for element in ann.elements: + if element.label not in tracks[track_id]['elements']: + tracks[track_id]['elements'][element.label] = instance_data.Track( + label=label_cat.items[element.label].name, + group=0, + source=source, + shapes=[], + ) + element_attributes = [ + instance_data.Attribute(name=n, value=str(v)) + for n, v in element.attributes.items() + ] + element_occluded = dm.util.cast(element.attributes.pop('occluded', None), bool) is True + element_outside = dm.util.cast(element.attributes.pop('outside', None), bool) is True + element_source = element.attributes.pop('source').lower() \ + if element.attributes.get('source', '').lower() in {'auto', 'manual'} else 'manual' + tracks[track_id]['elements'][element.label].shapes.append(instance_data.TrackedShape( + type=shapes[element.type], + frame=frame_number, + occluded=element_occluded, + outside=element_outside, + keyframe=keyframe, + points=element.points, + z_order=element.z_order, + source=element_source, + attributes=element_attributes, + )) + + elif ann.type == dm.AnnotationType.label: instance_data.add_tag(instance_data.Tag( frame=frame_number, label=label_cat.items[ann.label].name, @@ -1654,13 +1687,14 @@ def import_dm_annotations(dm_dataset: Dataset, instance_data: Union[TaskData, Pr "#{} ({}): {}".format(item.id, idx, ann.type.name, e)) from e for track in tracks.values(): - instance_data.add_track(track) + track['elements'] = list(track['elements'].values()) + instance_data.add_track(instance_data.Track(**track)) -def import_labels_to_project(project_annotation, dataset: Dataset): +def import_labels_to_project(project_annotation, dataset: dm.Dataset): labels = [] label_colors = [] - for label in dataset.categories()[datum_annotation.AnnotationType.label].items: + for label in dataset.categories()[dm.AnnotationType.label].items: db_label = Label( name=label.name, color=get_label_color(label.name, label_colors), @@ -1670,11 +1704,11 @@ def import_labels_to_project(project_annotation, dataset: Dataset): label_colors.append(db_label.color) project_annotation.add_labels(labels) -def load_dataset_data(project_annotation, dataset: Dataset, project_data): +def load_dataset_data(project_annotation, dataset: dm.Dataset, project_data): if not project_annotation.db_project.label_set.count(): import_labels_to_project(project_annotation, dataset) else: - for label in dataset.categories()[datum_annotation.AnnotationType.label].items: + for label in dataset.categories()[dm.AnnotationType.label].items: if not project_annotation.db_project.label_set.filter(name=label.name).exists(): raise CvatImportError(f'Target project does not have label with name "{label.name}"') for subset_id, subset in enumerate(dataset.subsets().values()): diff --git a/cvat/apps/dataset_manager/formats/cvat.py b/cvat/apps/dataset_manager/formats/cvat.py index 97bd98a31325..50d066b0989d 100644 --- a/cvat/apps/dataset_manager/formats/cvat.py +++ b/cvat/apps/dataset_manager/formats/cvat.py @@ -1,4 +1,5 @@ # Copyright (C) 2018-2022 Intel Corporation +# Copyright (C) 2022 CVAT.ai Corporation # # SPDX-License-Identifier: MIT @@ -409,7 +410,7 @@ def _load_items(self, parsed, image_items): di.subset = subset or DEFAULT_SUBSET_NAME di.annotations = item_desc.get('annotations') di.attributes = {'frame': int(frame_id)} - di.image = image if isinstance(image, Image) else di.image + di.media = image if isinstance(image, Image) else di.media image_items[(subset, osp.splitext(name)[0])] = di return image_items diff --git a/cvat/apps/dataset_manager/formats/icdar.py b/cvat/apps/dataset_manager/formats/icdar.py index a46173f4c871..013ccf0a3ad2 100644 --- a/cvat/apps/dataset_manager/formats/icdar.py +++ b/cvat/apps/dataset_manager/formats/icdar.py @@ -1,4 +1,5 @@ # Copyright (C) 2021-2022 Intel Corporation +# Copyright (C) 2022 CVAT.ai Corporation # # SPDX-License-Identifier: MIT @@ -90,7 +91,7 @@ def _import(src_file, instance_data, load_data_callback=None): with TemporaryDirectory() as tmp_dir: zipfile.ZipFile(src_file).extractall(tmp_dir) dataset = Dataset.import_from(tmp_dir, 'icdar_word_recognition', env=dm_env) - dataset.transform(CaptionToLabel, 'icdar') + dataset.transform(CaptionToLabel, label='icdar') if load_data_callback is not None: load_data_callback(dataset, instance_data) import_dm_annotations(dataset, instance_data) @@ -110,7 +111,7 @@ def _import(src_file, instance_data, load_data_callback=None): zipfile.ZipFile(src_file).extractall(tmp_dir) dataset = Dataset.import_from(tmp_dir, 'icdar_text_localization', env=dm_env) - dataset.transform(AddLabelToAnns, 'icdar') + dataset.transform(AddLabelToAnns, label='icdar') if load_data_callback is not None: load_data_callback(dataset, instance_data) import_dm_annotations(dataset, instance_data) @@ -133,7 +134,7 @@ def _import(src_file, instance_data, load_data_callback=None): with TemporaryDirectory() as tmp_dir: zipfile.ZipFile(src_file).extractall(tmp_dir) dataset = Dataset.import_from(tmp_dir, 'icdar_text_segmentation', env=dm_env) - dataset.transform(AddLabelToAnns, 'icdar') + dataset.transform(AddLabelToAnns, label='icdar') dataset.transform('masks_to_polygons') if load_data_callback is not None: load_data_callback(dataset, instance_data) diff --git a/cvat/apps/dataset_manager/formats/market1501.py b/cvat/apps/dataset_manager/formats/market1501.py index f4c305d31811..b5902002d46c 100644 --- a/cvat/apps/dataset_manager/formats/market1501.py +++ b/cvat/apps/dataset_manager/formats/market1501.py @@ -1,4 +1,5 @@ # Copyright (C) 2021-2022 Intel Corporation +# Copyright (C) 2022 CVAT.ai Corporation # # SPDX-License-Identifier: MIT @@ -65,7 +66,7 @@ def _export(dst_file, instance_data, save_images=False): dataset = Dataset.from_extractors(GetCVATDataExtractor( instance_data, include_images=save_images), env=dm_env) with TemporaryDirectory() as temp_dir: - dataset.transform(LabelAttrToAttr, 'market-1501') + dataset.transform(LabelAttrToAttr, label='market-1501') dataset.export(temp_dir, 'market1501', save_images=save_images) make_zip_archive(temp_dir, dst_file) @@ -75,7 +76,7 @@ def _import(src_file, instance_data, load_data_callback=None): zipfile.ZipFile(src_file).extractall(tmp_dir) dataset = Dataset.import_from(tmp_dir, 'market1501', env=dm_env) - dataset.transform(AttrToLabelAttr, 'market-1501') + dataset.transform(AttrToLabelAttr, label='market-1501') if load_data_callback is not None: load_data_callback(dataset, instance_data) import_dm_annotations(dataset, instance_data) diff --git a/cvat/apps/dataset_manager/formats/transformations.py b/cvat/apps/dataset_manager/formats/transformations.py index 49de6caff6bc..ecce6474a65b 100644 --- a/cvat/apps/dataset_manager/formats/transformations.py +++ b/cvat/apps/dataset_manager/formats/transformations.py @@ -9,7 +9,7 @@ from pycocotools import mask as mask_utils from datumaro.components.extractor import ItemTransform -import datumaro.components.annotation as datum_annotation +import datumaro.components.annotation as dm class RotatedBoxesToPolygons(ItemTransform): def _rotate_point(self, p, angle, cx, cy): @@ -20,7 +20,7 @@ def _rotate_point(self, p, angle, cx, cy): def transform_item(self, item): annotations = item.annotations[:] - anns = [p for p in annotations if p.type == datum_annotation.AnnotationType.bbox and p.attributes['rotation']] + anns = [p for p in annotations if p.type == dm.AnnotationType.bbox and p.attributes['rotation']] for ann in anns: rotation = math.radians(ann.attributes['rotation']) x0, y0, x1, y1 = ann.points @@ -30,7 +30,7 @@ def transform_item(self, item): )) annotations.remove(ann) - annotations.append(datum_annotation.Polygon(anno_points, + annotations.append(dm.Polygon(anno_points, label=ann.label, attributes=ann.attributes, group=ann.group, z_order=ann.z_order)) @@ -48,5 +48,5 @@ def convert_ellipse(ellipse, img_h, img_w): mat = np.zeros((img_h, img_w), dtype=np.uint8) cv2.ellipse(mat, center, axis, angle, 0, 360, 255, thickness=-1) rle = mask_utils.encode(np.asfortranarray(mat)) - return datum_annotation.RleMask(rle=rle, label=ellipse.label, z_order=ellipse.z_order, + return dm.RleMask(rle=rle, label=ellipse.label, z_order=ellipse.z_order, attributes=ellipse.attributes, group=ellipse.group) diff --git a/cvat/apps/dataset_manager/formats/vggface2.py b/cvat/apps/dataset_manager/formats/vggface2.py index fc699050caf9..e5f3eca1adfc 100644 --- a/cvat/apps/dataset_manager/formats/vggface2.py +++ b/cvat/apps/dataset_manager/formats/vggface2.py @@ -1,4 +1,5 @@ # Copyright (C) 2021-2022 Intel Corporation +# Copyright (C) 2022 CVAT.ai Corporation # # SPDX-License-Identifier: MIT @@ -29,7 +30,7 @@ def _import(src_file, instance_data, load_data_callback=None): zipfile.ZipFile(src_file).extractall(tmp_dir) dataset = Dataset.import_from(tmp_dir, 'vgg_face2', env=dm_env) - dataset.transform('rename', r"|([^/]+/)?(.+)|\2|") + dataset.transform('rename', regex=r"|([^/]+/)?(.+)|\2|") if load_data_callback is not None: load_data_callback(dataset, instance_data) import_dm_annotations(dataset, instance_data) diff --git a/cvat/apps/dataset_manager/tests/assets/annotations.json b/cvat/apps/dataset_manager/tests/assets/annotations.json index 4ec55a94cd7d..cdd9f9836bef 100644 --- a/cvat/apps/dataset_manager/tests/assets/annotations.json +++ b/cvat/apps/dataset_manager/tests/assets/annotations.json @@ -533,15 +533,7 @@ }, "Market-1501 1.0": { "version": 0, - "tags": [ - { - "frame": 1, - "label_id": null, - "group": 0, - "source": "manual", - "attributes": [] - } - ], + "tags": [], "shapes": [], "tracks": [] }, diff --git a/cvat/apps/dataset_manager/tests/assets/tasks.json b/cvat/apps/dataset_manager/tests/assets/tasks.json index e301c54a2237..e5c4e140ecb7 100644 --- a/cvat/apps/dataset_manager/tests/assets/tasks.json +++ b/cvat/apps/dataset_manager/tests/assets/tasks.json @@ -336,7 +336,14 @@ "name": "skeleton", "color": "#2080c0", "type": "skeleton", - "attributes": [], + "attributes": [ + { + "name": "attr", + "mutable": false, + "input_type": "select", + "values": ["0", "1", "2"] + } + ], "sublabels": [ { "name": "1", @@ -353,14 +360,7 @@ { "name": "3", "color": "#479ffe", - "attributes": [ - { - "name": "attr", - "mutable": false, - "input_type": "select", - "values": ["0", "1", "2"] - } - ], + "attributes": [], "type": "points" } ], diff --git a/cvat/apps/dataset_manager/tests/test_formats.py b/cvat/apps/dataset_manager/tests/test_formats.py index 326a104b04e0..38ffe236d83c 100644 --- a/cvat/apps/dataset_manager/tests/test_formats.py +++ b/cvat/apps/dataset_manager/tests/test_formats.py @@ -1,5 +1,6 @@ # Copyright (C) 2020-2022 Intel Corporation +# Copyright (C) 2022 CVAT.ai Corporation # # SPDX-License-Identifier: MIT @@ -617,6 +618,14 @@ def _generate_task_images(self, count, name="image", **image_params): images["image_quality"] = 75 return images + def _generate_task_images_by_names(self, names, **image_params): + images = { + f"client_files[{i}]": generate_image_file(f"{name}.jpg", **image_params) + for i, name in enumerate(names) + } + images["image_quality"] = 75 + return images + def _generate_task(self, images, annotation_format, **overrides): labels = [] if annotation_format in ["ICDAR Recognition 1.0", @@ -911,7 +920,10 @@ def test_can_import_annotations_for_image_with_dots_in_filename(self): for f in dm.views.get_import_formats(): format_name = f.DISPLAY_NAME - images = self._generate_task_images(3, "img0.0.0") + if format_name == "Market-1501 1.0": + images = self._generate_task_images_by_names(["img0.0.0_0", "1.0_c3s1_000000_00", "img0.0.0_1"]) + else: + images = self._generate_task_images(3, "img0.0.0") task = self._generate_task(images, format_name) self._generate_annotations(task, format_name) diff --git a/cvat/requirements/base.txt b/cvat/requirements/base.txt index 54178cc96e07..8cdabe0234e1 100644 --- a/cvat/requirements/base.txt +++ b/cvat/requirements/base.txt @@ -45,9 +45,7 @@ diskcache==5.0.2 boto3==1.17.61 azure-storage-blob==12.13.0 google-cloud-storage==1.42.0 -# --no-binary=datumaro: workaround for pip to install -# opencv-headless instead of regular opencv, to actually run setup script -datumaro==0.2.0 --no-binary=datumaro +git+https://github.com/cvat-ai/datumaro.git@bac20235bd6c792b4d068a54fd3ad14be50bb26f urllib3>=1.26.5 # not directly required, pinned by Snyk to avoid a vulnerability natsort==8.0.0 mistune>=2.0.1 # not directly required, pinned by Snyk to avoid a vulnerability