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

update datumaro #8954

Merged
merged 11 commits into from
Jan 30, 2025
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
22 changes: 11 additions & 11 deletions cvat/apps/dataset_manager/bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2123,8 +2123,8 @@ def match_dm_item(
is_video = instance_data.meta[instance_data.META_FIELD]['mode'] == 'interpolation'

frame_number = None
if frame_number is None and item.has_image:
frame_number = instance_data.match_frame(item.id + item.image.ext, root_hint)
if frame_number is None and isinstance(item.media, dm.Image):
frame_number = instance_data.match_frame(item.id + item.media.ext, root_hint)
if frame_number is None:
frame_number = instance_data.match_frame(item.id, root_hint, path_has_ext=False)
if frame_number is None:
Expand Down Expand Up @@ -2465,20 +2465,20 @@ def load_dataset_data(project_annotation, dataset: dm.Dataset, project_data):

root_paths = set()
for dataset_item in subset_dataset:
if dataset_item.image and dataset_item.image.has_data:
dataset_files['media'].append(dataset_item.image.path)
data_root = dataset_item.image.path.rsplit(dataset_item.id, 1)
if isinstance(dataset_item.media, dm.Image) and dataset_item.media.has_data:
dataset_files['media'].append(dataset_item.media.path)
data_root = dataset_item.media.path.rsplit(dataset_item.id, 1)
if len(data_root) == 2:
root_paths.add(data_root[0])
elif dataset_item.point_cloud:
dataset_files['media'].append(dataset_item.point_cloud)
data_root = dataset_item.point_cloud.rsplit(dataset_item.id, 1)
elif isinstance(dataset_item.media, dm.PointCloud):
dataset_files['media'].append(dataset_item.media)
data_root = dataset_item.media.path.rsplit(dataset_item.id, 1)
if len(data_root) == 2:
root_paths.add(data_root[0])

if isinstance(dataset_item.related_images, list):
dataset_files['media'] += \
list(map(lambda ri: ri.path, dataset_item.related_images))
if isinstance(dataset_item.media.extra_images, list):
dataset_files['media'] += \
list(map(lambda ri: ri.path, dataset_item.media.extra_images))

if len(root_paths):
dataset_files['data_root'] = osp.commonpath(root_paths) + osp.sep
Expand Down
9 changes: 5 additions & 4 deletions cvat/apps/dataset_manager/formats/cvat.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
from datumaro.components.dataset import Dataset, DatasetItem
from datumaro.components.dataset_base import DEFAULT_SUBSET_NAME, DatasetBase
from datumaro.components.importer import Importer
from datumaro.components.media import Image
from datumaro.plugins.data_formats.cvat.base import CvatImporter as _CvatImporter
from datumaro.util.image import Image
from defusedxml import ElementTree

from cvat.apps.dataset_manager.bindings import (
Expand Down Expand Up @@ -112,7 +112,7 @@ def parse_image_dir(image_dir, subset):
name, ext = osp.splitext(osp.basename(file))
if ext.lower() in CvatPath.MEDIA_EXTS:
items[(subset, name)] = DatasetItem(id=name, annotations=[],
image=Image(path=file), subset=subset or DEFAULT_SUBSET_NAME,
media=Image(path=file), subset=subset or DEFAULT_SUBSET_NAME,
)

if subsets == [DEFAULT_SUBSET_NAME] and not osp.isdir(osp.join(image_dir, DEFAULT_SUBSET_NAME)):
Expand Down Expand Up @@ -1172,9 +1172,10 @@ def load_anno(file_object, annotations):
elif el.tag == 'image':
image_is_opened = True
frame_id = annotations.abs_frame_id(match_dm_item(
DatasetItem(id=osp.splitext(el.attrib['name'])[0],
DatasetItem(
id=osp.splitext(el.attrib['name'])[0],
attributes={'frame': el.attrib['id']},
image=el.attrib['name']
media=Image(path=el.attrib['name'])
),
instance_data=annotations
))
Expand Down
13 changes: 8 additions & 5 deletions cvat/apps/dataset_manager/tests/test_rest_api_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
import av
import numpy as np
from attr import define, field
from datumaro.components.comparator import EqualityComparator
from datumaro.components.dataset import Dataset
from datumaro.components.operations import ExactComparator
from django.contrib.auth.models import Group, User
from PIL import Image
from rest_framework import status
Expand Down Expand Up @@ -119,10 +119,13 @@ def generate_video_file(filename, width=1280, height=720, duration=1, fps=25, co

def compare_datasets(expected: Dataset, actual: Dataset):
# we need this function to allow for a bit of variation in the rotation attribute
comparator = ExactComparator(ignored_attrs=["rotation"])
_, unmatched, expected_extra, actual_extra, errors = comparator.compare_datasets(
expected, actual
)
comparator = EqualityComparator(ignored_attrs=["rotation"])

output = comparator.compare_datasets(expected, actual)
unmatched = output["mismatches"]
expected_extra = output["a_extra_items"]
actual_extra = output["b_extra_items"]
errors = output["errors"]
assert not unmatched, f"Datasets have unmatched items: {unmatched}"
assert not actual_extra, f"Actual has following extra items: {actual_extra}"
assert not expected_extra, f"Expected has following extra items: {expected_extra}"
Expand Down
55 changes: 29 additions & 26 deletions cvat/apps/quality_control/quality_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
from typing import Any, Callable, Optional, Union, cast

import datumaro as dm
import datumaro.components.annotations.matcher
import datumaro.components.comparator
import datumaro.util.annotation_util
import datumaro.util.mask_tools
import django_rq
import numpy as np
Expand Down Expand Up @@ -661,7 +664,7 @@ def _convert_shape(self, shape, *, index):
def _match_segments(
a_segms,
b_segms,
distance=dm.ops.segment_iou,
distance=dm.util.annotation_util.segment_iou,
dist_thresh=1.0,
label_matcher=lambda a, b: a.label == b.label,
):
Expand Down Expand Up @@ -740,7 +743,7 @@ def _OKS(a, b, sigma=0.1, bbox=None, scale=None, visibility_a=None, visibility_b

if not scale:
if bbox is None:
bbox = dm.ops.mean_bbox([a, b])
bbox = dm.util.annotation_util.mean_bbox([a, b])
scale = bbox[2] * bbox[3]

dists = np.linalg.norm(p1 - p2, axis=1)
Expand All @@ -750,14 +753,14 @@ def _OKS(a, b, sigma=0.1, bbox=None, scale=None, visibility_a=None, visibility_b


@define(kw_only=True)
class _KeypointsMatcher(dm.ops.PointsMatcher):
class _KeypointsMatcher(datumaro.components.annotations.matcher.PointsMatcher):
def distance(self, a: dm.Points, b: dm.Points) -> float:
a_bbox = self.instance_map[id(a)][1]
b_bbox = self.instance_map[id(b)][1]
if dm.ops.bbox_iou(a_bbox, b_bbox) <= 0:
if dm.util.annotation_util.bbox_iou(a_bbox, b_bbox) <= 0:
return 0

bbox = dm.ops.mean_bbox([a_bbox, b_bbox])
bbox = dm.util.annotation_util.mean_bbox([a_bbox, b_bbox])
return _OKS(
a,
b,
Expand Down Expand Up @@ -806,7 +809,7 @@ def _segment_iou(a: dm.Annotation, b: dm.Annotation, *, img_h: int, img_w: int)


@define(kw_only=True)
class _LineMatcher(dm.ops.LineMatcher):
class _LineMatcher(datumaro.components.annotations.matcher.LineMatcher):
EPSILON = 1e-7

torso_r: float = 0.25
Expand Down Expand Up @@ -953,7 +956,7 @@ def approximate_points(cls, a: np.ndarray, b: np.ndarray) -> tuple[np.ndarray, n
return a_new_points, b_new_points


class _DistanceComparator(dm.ops.DistanceComparator):
class _DistanceComparator(dm.components.comparator.DistanceComparator):
def __init__(
self,
categories: dm.CategoriesInfo,
Expand Down Expand Up @@ -994,7 +997,7 @@ def __init__(
def _instance_bbox(
self, instance_anns: Sequence[dm.Annotation]
) -> tuple[float, float, float, float]:
return dm.ops.max_bbox(
return dm.util.annotation_util.max_bbox(
a.get_bbox() if isinstance(a, dm.Skeleton) else a
for a in instance_anns
if hasattr(a, "get_bbox") and not a.attributes.get("outside", False)
Expand Down Expand Up @@ -1048,7 +1051,7 @@ def _match_segments(
item_a,
item_b,
*,
distance: Callable = dm.ops.segment_iou,
distance: Callable = dm.util.annotation_util.segment_iou,
label_matcher: Callable = None,
a_objs: Optional[Sequence[dm.Annotation]] = None,
b_objs: Optional[Sequence[dm.Annotation]] = None,
Expand Down Expand Up @@ -1083,7 +1086,7 @@ def _match_segments(

return returned_values

def match_boxes(self, item_a, item_b):
def match_boxes(self, item_a: dm.DatasetItem, item_b: dm.DatasetItem):
def _to_polygon(bbox_ann: dm.Bbox):
points = bbox_ann.as_polygon()
angle = bbox_ann.attributes.get("rotation", 0) / 180 * math.pi
Expand All @@ -1102,33 +1105,33 @@ def _to_polygon(bbox_ann: dm.Bbox):

def _bbox_iou(a: dm.Bbox, b: dm.Bbox, *, img_w: int, img_h: int) -> float:
if a.attributes.get("rotation", 0) == b.attributes.get("rotation", 0):
return dm.ops.bbox_iou(a, b)
return dm.util.annotation_util.bbox_iou(a, b)
else:
return _segment_iou(_to_polygon(a), _to_polygon(b), img_h=img_h, img_w=img_w)

img_h, img_w = item_a.image.size
img_h, img_w = item_a.media_as(dm.Image).size
return self._match_segments(
dm.AnnotationType.bbox,
item_a,
item_b,
distance=partial(_bbox_iou, img_h=img_h, img_w=img_w),
)

def match_segmentations(self, item_a, item_b):
def match_segmentations(self, item_a: dm.DatasetItem, item_b: dm.DatasetItem):
def _get_segmentations(item):
return self._get_ann_type(dm.AnnotationType.polygon, item) + self._get_ann_type(
dm.AnnotationType.mask, item
)

img_h, img_w = item_a.image.size
img_h, img_w = item_a.media_as(dm.Image).size

def _find_instances(annotations):
# Group instance annotations by label.
# Annotations with the same label and group will be merged,
# and considered a single object in comparison
instances = []
instance_map = {} # ann id -> instance id
for ann_group in dm.ops.find_instances(annotations):
for ann_group in dm.util.annotation_util.find_instances(annotations):
ann_group = sorted(ann_group, key=lambda a: a.label)
for _, label_group in itertools.groupby(ann_group, key=lambda a: a.label):
label_group = list(label_group)
Expand Down Expand Up @@ -1266,31 +1269,31 @@ def _label_matcher(a_inst_id: int, b_inst_id: int) -> bool:

return returned_values

def match_lines(self, item_a, item_b):
def match_lines(self, item_a: dm.DatasetItem, item_b: dm.DatasetItem):
matcher = _LineMatcher(
oriented=self.compare_line_orientation,
torso_r=self.line_torso_radius,
scale=np.prod(item_a.image.size),
scale=np.prod(item_a.media_as(dm.Image).size),
)
return self._match_segments(
dm.AnnotationType.polyline, item_a, item_b, distance=matcher.distance
)

def match_points(self, item_a, item_b):
def match_points(self, item_a: dm.DatasetItem, item_b: dm.DatasetItem):
a_points = self._get_ann_type(dm.AnnotationType.points, item_a)
b_points = self._get_ann_type(dm.AnnotationType.points, item_b)

instance_map = {} # points id -> (instance group, instance bbox)
for source_anns in [item_a.annotations, item_b.annotations]:
source_instances = dm.ops.find_instances(source_anns)
source_instances = dm.util.annotation_util.find_instances(source_anns)
for instance_group in source_instances:
instance_bbox = self._instance_bbox(instance_group)

for ann in instance_group:
if ann.type == dm.AnnotationType.points:
instance_map[id(ann)] = [instance_group, instance_bbox]

img_h, img_w = item_a.image.size
img_h, img_w = item_a.media_as(dm.Image).size

def _distance(a: dm.Points, b: dm.Points) -> float:
a_bbox = instance_map[id(a)][1]
Expand All @@ -1312,11 +1315,11 @@ def _distance(a: dm.Points, b: dm.Points) -> float:
elif self.point_size_base == models.PointSizeBase.GROUP_BBOX_SIZE:
# match points in their bbox space

if dm.ops.bbox_iou(a_bbox, b_bbox) <= 0:
if dm.util.annotation_util.bbox_iou(a_bbox, b_bbox) <= 0:
# this early exit may not work for points forming an axis-aligned line
return 0

bbox = dm.ops.mean_bbox([a_bbox, b_bbox])
bbox = dm.util.annotation_util.mean_bbox([a_bbox, b_bbox])
scale = bbox[2] * bbox[3]
else:
assert False, f"Unknown point size base {self.point_size_base}"
Expand Down Expand Up @@ -1427,7 +1430,7 @@ def match_skeletons(self, item_a, item_b):

instance_map = {}
for source in [item_a.annotations, item_b.annotations]:
for instance_group in dm.ops.find_instances(source):
for instance_group in dm.util.annotation_util.find_instances(source):
instance_bbox = self._instance_bbox(instance_group)

instance_group = [
Expand Down Expand Up @@ -1583,7 +1586,7 @@ def match_attrs(self, ann_a: dm.Annotation, ann_b: dm.Annotation):
def find_groups(
self, item: dm.DatasetItem
) -> tuple[dict[int, list[dm.Annotation]], dict[int, int]]:
ann_groups = dm.ops.find_instances(
ann_groups = dm.util.annotation_util.find_instances(
[
ann
for ann in item.annotations
Expand Down Expand Up @@ -1653,7 +1656,7 @@ def find_covered(self, item: dm.DatasetItem) -> list[dm.Annotation]:
else:
assert False

img_h, img_w = item.image.size
img_h, img_w = item.media_as(dm.Image).size
covered_ids = _find_covered_segments(
segms, img_w=img_w, img_h=img_h, visibility_threshold=self.coverage_threshold
)
Expand Down Expand Up @@ -1863,7 +1866,7 @@ def _find_closest_unmatched_shape(shape: dm.Annotation):
line_matcher = _LineMatcher(
torso_r=self.settings.line_thickness,
oriented=True,
scale=np.prod(gt_item.image.size),
scale=np.prod(gt_item.media_as(dm.Image).size),
)

for gt_ann, ds_ann in itertools.chain(matches, mismatches):
Expand Down
2 changes: 1 addition & 1 deletion cvat/requirements/base.in
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ azure-storage-blob==12.13.0
boto3==1.17.61
clickhouse-connect==0.6.8
coreapi==2.3.3
datumaro @ git+https://github.com/cvat-ai/datumaro.git@ebcc2d4254e5b5c19b51ad6062dc882366871703
datumaro @ git+https://github.com/cvat-ai/datumaro.git@e61ebe4c5cfee8a741fbf89f966535996be6dcff
dj-pagination==2.5.0
# Despite direct indication allauth in requirements we should keep 'with_social' for dj-rest-auth
# to avoid possible further versions conflicts (we use registration functionality)
Expand Down
Loading
Loading