From 23dacca26e758616f4eb0875cc2e1003c47e6397 Mon Sep 17 00:00:00 2001 From: Yunchu Lee Date: Wed, 19 Jun 2024 13:13:23 +0900 Subject: [PATCH] Revert "Mergeback 1.7.0 to develop (#1538)" This reverts commit d72d58965a72a89053f7634e56a1791a476f1651. --- .github/workflows/codeql.yml | 11 -- CHANGELOG.md | 31 +-- .../docs/command-reference/context/util.md | 1 - .../docs/data-formats/formats/datumaro.md | 1 - .../data-formats/formats/datumaro_binary.md | 1 - .../source/docs/data-formats/formats/video.md | 1 - docs/source/docs/release_notes.rst | 32 --- src/datumaro/components/annotation.py | 95 +-------- src/datumaro/components/dataset.py | 7 +- src/datumaro/components/dataset_base.py | 4 +- src/datumaro/components/exporter.py | 20 +- src/datumaro/components/media.py | 119 ++---------- .../plugins/data_formats/datumaro/base.py | 41 +--- .../plugins/data_formats/datumaro/exporter.py | 29 +-- .../data_formats/datumaro_binary/base.py | 9 +- .../datumaro_binary/mapper/media.py | 39 +--- .../plugins/data_formats/widerface.py | 2 +- src/datumaro/util/image.py | 27 +-- src/datumaro/version.py | 2 +- tests/integration/cli/test_utils.py | 6 +- tests/integration/cli/test_video.py | 79 ++------ tests/unit/components/test_exporter.py | 70 ------- tests/unit/data_formats/datumaro/conftest.py | 54 +----- .../datumaro/test_datumaro_binary_format.py | 24 +-- .../datumaro/test_datumaro_format.py | 23 +-- tests/unit/test_annotation.py | 78 ++------ tests/unit/test_dataset.py | 41 ++-- tests/unit/test_kinetics_format.py | 105 ++++------ tests/unit/test_video.py | 183 ++++++------------ tests/unit/test_widerface_format.py | 4 - tests/utils/test_utils.py | 37 ++-- 31 files changed, 248 insertions(+), 928 deletions(-) delete mode 100644 tests/unit/components/test_exporter.py diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c9f02ee4b6..cde868a256 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -76,14 +76,3 @@ jobs: uses: github/codeql-action/analyze@f079b8493333aace61c81488f8bd40919487bd9f # v3.25.7 with: category: "/language:${{matrix.language}}" - - name: Generate Security Report - uses: rsdmike/github-security-report-action@a149b24539044c92786ec39af8ba38c93496495d # v3.0.4 - with: - outputDir: ${{matrix.language}} - template: report - token: ${{ secrets.GITHUB_TOKEN }} - - name: GitHub Upload Release Artifacts - uses: actions/upload-artifact@1746f4ab65b179e0ea60a494b83293b640dd5bba # v4.3.2 - with: - name: codeql-report-${{matrix.language}} - path: "./${{matrix.language}}/report.pdf" diff --git a/CHANGELOG.md b/CHANGELOG.md index 02adc93303..eed0aed6a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,21 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## \[unreleased\] ### New features -- Add TabularValidator - () -- Add Clean Transform for tabular data type - () - -### Enhancements -- Set label name with parents to avoid duplicates for AstypeAnnotations - () -- Pass Keyword Argument to TabularDataBase - () - -## Q2 2024 Release 1.7.0 -### New features -- Support 'Video' media type in datumaro format - () - Add ann_types property for dataset (, ) - Add AnnotationType.rotated_bbox for oriented object detection @@ -30,8 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 () - Add AstypeAnnotations Transform () -- Enhance DatasetItem annotations for semantic segmentation model training use case - () +- Add TabularValidator + () +- Add Clean Transform for tabular data type + () ### Enhancements - Fix ambiguous COCO format detector @@ -40,12 +27,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 () - Add ExtractedMask and update importers who can use it to use it () -- Improve PIL and COLOR_BGR context image decode performance - () -- Improve get_area() of Polygon through Shoelace formula - () -- Improve _Shape point converter - () +- Set label name with parents to avoid duplicates for AstypeAnnotations + () +- Pass Keyword Argument to TabularDataBase + () ### Bug fixes - Split the video directory into subsets to avoid overwriting diff --git a/docs/source/docs/command-reference/context/util.md b/docs/source/docs/command-reference/context/util.md index 1c0188c839..2444db92d7 100644 --- a/docs/source/docs/command-reference/context/util.md +++ b/docs/source/docs/command-reference/context/util.md @@ -17,7 +17,6 @@ the dataset reproducible and stable. This command provides different options like setting the frame step (the `-s/--step` option), file name pattern (`-n/--name-pattern`), starting (`-b/--start-frame`) and finishing (`-e/--end-frame`) frame etc. -Note that starting and finishing frames denote a closed interval [`start-frame`, `end-frame`]. Note that this command is equivalent to the following commands: ```bash diff --git a/docs/source/docs/data-formats/formats/datumaro.md b/docs/source/docs/data-formats/formats/datumaro.md index b12f1af6a1..60869e55f2 100644 --- a/docs/source/docs/data-formats/formats/datumaro.md +++ b/docs/source/docs/data-formats/formats/datumaro.md @@ -11,7 +11,6 @@ Supported media types: - `Image` - `PointCloud` -- `Video` - `VideoFrame` Supported annotation types: diff --git a/docs/source/docs/data-formats/formats/datumaro_binary.md b/docs/source/docs/data-formats/formats/datumaro_binary.md index 7b724b3734..6454eff146 100644 --- a/docs/source/docs/data-formats/formats/datumaro_binary.md +++ b/docs/source/docs/data-formats/formats/datumaro_binary.md @@ -59,7 +59,6 @@ Supported media types: - `Image` - `PointCloud` -- `Video` - `VideoFrame` Supported annotation types: diff --git a/docs/source/docs/data-formats/formats/video.md b/docs/source/docs/data-formats/formats/video.md index 2ba1f6990c..b7f1e0f770 100644 --- a/docs/source/docs/data-formats/formats/video.md +++ b/docs/source/docs/data-formats/formats/video.md @@ -31,7 +31,6 @@ dataset = dm.Dataset.import_from('', format='video_frames') Datumaro has few import options for `video_frames` format, to apply them use the `--` after the main command argument. -Note that a video has a closed interval of [`start-frame`, `end-frame`]. `video_frames` import options: - `--subset` (string) - The name of the subset for the produced diff --git a/docs/source/docs/release_notes.rst b/docs/source/docs/release_notes.rst index 23a70c9e47..f1aab3fe7c 100644 --- a/docs/source/docs/release_notes.rst +++ b/docs/source/docs/release_notes.rst @@ -3,38 +3,6 @@ Release Notes .. toctree:: :maxdepth: 1 - -v1.7.0 (2024 Q2) ----------------- - -New features -^^^^^^^^^^^^ -- Add ann_types property for dataset -- Add AnnotationType.rotated_bbox for oriented object detection -- Add DOTA data format for oriented object detection task -- Add AstypeAnnotations Transform - -Enhancements -^^^^^^^^^^^^ -- Fix ambiguous COCO format detector -- Get target information for tabular dataset -- Add ExtractedMask and update importers who can use it to use it - -v1.6.1 (2024.05) ----------------- - -Enhancements -^^^^^^^^^^^^ -- Prevent AcLauncher for OpenVINO 2024.0 - -Bug fixes -^^^^^^^^^ -- Modify lxml dependency constraint -- Fix CLI error occurring when installed with default option only -- Relax Pillow dependency constraint -- Modify Numpy dependency constraint -- Relax old pandas version constraint - v1.6.0 (2024.04) ---------------- diff --git a/src/datumaro/components/annotation.py b/src/datumaro/components/annotation.py index 2dfa6bff41..060e913666 100644 --- a/src/datumaro/components/annotation.py +++ b/src/datumaro/components/annotation.py @@ -683,8 +683,11 @@ def lazy_extract(self, instance_id: int) -> Callable[[], IndexMaskImage]: @attrs(slots=True, order=False) class _Shape(Annotation): + # Flattened list of point coordinates points: List[float] = field( - converter=lambda x: np.array(x, dtype=np.float32).round(COORDINATE_ROUNDING_DIGITS).tolist() + converter=lambda x: np.around( + np.array(x, dtype=np.float32), COORDINATE_ROUNDING_DIGITS + ).tolist() ) label: Optional[int] = field( @@ -809,12 +812,11 @@ def __attrs_post_init__(self): ) def get_area(self): - # import pycocotools.mask as mask_utils + import pycocotools.mask as mask_utils - # x, y, w, h = self.get_bbox() - # rle = mask_utils.frPyObjects([self.points], y + h, x + w) - # area = mask_utils.area(rle)[0] - area = self._get_shoelace_area() + x, y, w, h = self.get_bbox() + rle = mask_utils.frPyObjects([self.points], y + h, x + w) + area = mask_utils.area(rle)[0] return area def as_polygon(self) -> List[float]: @@ -840,21 +842,6 @@ def __eq__(self, other): inter_area = self_polygon.intersection(other_polygon).area return abs(self_polygon.area - inter_area) < CHECK_POLYGON_EQ_EPSILONE - def _get_shoelace_area(self): - points = self.get_points() - n = len(points) - # Not a polygon - if n < 3: - return 0 - - area = 0.0 - for i in range(n): - x1, y1 = points[i] - x2, y2 = points[(i + 1) % n] # Next vertex, wrapping around using modulo - area += x1 * y2 - y1 * x2 - - return abs(area) / 2.0 - @attrs(slots=True, init=False, order=False) class Bbox(_Shape): @@ -1381,69 +1368,3 @@ class Tabular(Annotation): _type = AnnotationType.tabular values: Dict[str, TableDtype] = field(converter=dict) - - -class Annotations(List[Annotation]): - """List of `Annotation` equipped with additional utility functions.""" - - def get_semantic_seg_mask( - self, ignore_index: int = 0, dtype: np.dtype = np.uint8 - ) -> np.ndarray: - """Extract semantic segmentation mask from a collection of Datumaro `Mask`s. - - Args: - ignore_index: Scalar value to fill in the zeros in each binary mask - before merging into a semantic segmentation mask. This value is usually used - to represent a pixel denoting a not-interested region. Defaults to 0. - dtype: Data type for the resulting mask. Defaults to np.uint8. - - Returns: - Semantic segmentation mask generated by merging Datumaro `Mask`s. - - Raises: - ValueError: If there are no mask annotations or if there is an inconsistency in mask sizes. - """ - - masks = [ann for ann in self if isinstance(ann, Mask)] - # Mask with a lower z_order value will come first - masks.sort(key=lambda mask: mask.z_order) - - if not masks: - msg = "There is no mask annotations." - raise ValueError(msg) - - # Dispatching for better performance - # If all masks are `ExtractedMask`, share a same source `index_mask`, and - # there is no label remapping. - if ( - all(isinstance(mask, ExtractedMask) for mask in masks) - # and set(id(mask.index_mask) for mask in masks) == 1 - and all(mask.index_mask == next(iter(masks)).index_mask for mask in masks) - and all(mask.index == mask.label for mask in masks) - ): - index_mask = next(iter(masks)).index_mask - semantic_seg_mask: np.ndarray = index_mask() if callable(index_mask) else index_mask - if semantic_seg_mask.dtype != dtype: - semantic_seg_mask = semantic_seg_mask.astype(dtype) - - labels = np.unique(np.array([mask.label for mask in masks])) - ignore_index_mask = np.isin(semantic_seg_mask, labels, invert=True) - - return np.where(ignore_index_mask, ignore_index, semantic_seg_mask) - - class_masks = [mask.as_class_mask(ignore_index=ignore_index, dtype=dtype) for mask in masks] - - max_h = max([mask.shape[0] for mask in class_masks]) - max_w = max([mask.shape[1] for mask in class_masks]) - - semantic_seg_mask = np.full(shape=(max_h, max_w), fill_value=ignore_index, dtype=dtype) - - for class_mask in class_masks: - if class_mask.shape != semantic_seg_mask.shape: - msg = f"There is inconsistency in mask size: {class_mask.shape}!={semantic_seg_mask.shape}." - raise ValueError(msg, class_mask.shape, semantic_seg_mask.shape) - - ignore_index_mask = class_mask == ignore_index - semantic_seg_mask = np.where(ignore_index_mask, semantic_seg_mask, class_mask) - - return semantic_seg_mask diff --git a/src/datumaro/components/dataset.py b/src/datumaro/components/dataset.py index 8364b1051e..4023782228 100644 --- a/src/datumaro/components/dataset.py +++ b/src/datumaro/components/dataset.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2024 Intel Corporation +# Copyright (C) 2020-2023 Intel Corporation # # SPDX-License-Identifier: MIT @@ -41,7 +41,6 @@ from datumaro.components.environment import DEFAULT_ENVIRONMENT, Environment from datumaro.components.errors import ( DatasetImportError, - DatumaroError, MultipleFormatsMatchError, NoMatchingFormatsError, StreamedItemError, @@ -889,10 +888,6 @@ def import_from( cause = e.__cause__ if getattr(e, "__cause__", None) is not None else e cause.__traceback__ = e.__traceback__ raise DatasetImportError(f"Failed to import dataset '{format}' at '{path}'.") from cause - except DatumaroError as e: - cause = e.__cause__ if getattr(e, "__cause__", None) is not None else e - cause.__traceback__ = e.__traceback__ - raise DatasetImportError(f"Failed to import dataset '{format}' at '{path}'.") from cause except Exception as e: raise DatasetImportError(f"Failed to import dataset '{format}' at '{path}'.") from e diff --git a/src/datumaro/components/dataset_base.py b/src/datumaro/components/dataset_base.py index c7b72ea97a..62ddac2d08 100644 --- a/src/datumaro/components/dataset_base.py +++ b/src/datumaro/components/dataset_base.py @@ -9,7 +9,7 @@ import attr from attr import attrs, field -from datumaro.components.annotation import Annotation, Annotations, AnnotationType, Categories +from datumaro.components.annotation import Annotation, AnnotationType, Categories from datumaro.components.cli_plugin import CliPlugin from datumaro.components.contexts.importer import ImportContext, NullImportContext from datumaro.components.media import Image, MediaElement @@ -29,7 +29,7 @@ class DatasetItem: default=None, validator=attr.validators.optional(attr.validators.instance_of(MediaElement)) ) - annotations: Annotations = field(factory=Annotations, validator=default_if_none(Annotations)) + annotations: List[Annotation] = field(factory=list, validator=default_if_none(list)) attributes: Dict[str, Any] = field(factory=dict, validator=default_if_none(dict)) diff --git a/src/datumaro/components/exporter.py b/src/datumaro/components/exporter.py index 7639947425..be7492336e 100644 --- a/src/datumaro/components/exporter.py +++ b/src/datumaro/components/exporter.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2024 Intel Corporation +# Copyright (C) 2019-2023 Intel Corporation # # SPDX-License-Identifier: MIT @@ -22,7 +22,7 @@ DatumaroError, ItemExportError, ) -from datumaro.components.media import Image, PointCloud, Video, VideoFrame +from datumaro.components.media import Image, PointCloud, VideoFrame from datumaro.components.progress_reporting import NullProgressReporter, ProgressReporter from datumaro.util.meta_file_util import save_hashkey_file, save_meta_file from datumaro.util.os_util import rmtree @@ -339,15 +339,10 @@ def make_pcd_extra_image_filename(self, item, idx, image, *, name=None, subdir=N ) + self.find_image_ext(image) def make_video_filename(self, item, *, name=None): - STR_WRONG_MEDIA_TYPE = "Video item's media type should be Video or VideoFrame" - assert isinstance(item, DatasetItem), STR_WRONG_MEDIA_TYPE - - if isinstance(item.media, VideoFrame): + if isinstance(item, DatasetItem) and isinstance(item.media, VideoFrame): video_file_name = osp.basename(item.media.video.path) - elif isinstance(item.media, Video): - video_file_name = osp.basename(item.media.path) else: - assert False, STR_WRONG_MEDIA_TYPE + assert "Video item type should be VideoFrame" return video_file_name @@ -408,7 +403,7 @@ def save_video( subdir: Optional[str] = None, fname: Optional[str] = None, ): - if not item.media or not isinstance(item.media, (Video, VideoFrame)): + if not item.media or not isinstance(item.media, VideoFrame): log.warning("Item '%s' has no video", item.id) return basedir = self._video_dir if basedir is None else basedir @@ -420,10 +415,7 @@ def save_video( os.makedirs(osp.dirname(path), exist_ok=True) - if isinstance(item.media, VideoFrame): - item.media.video.save(path, crypter=NULL_CRYPTER) - else: # Video - item.media.save(path, crypter=NULL_CRYPTER) + item.media.video.save(path, crypter=NULL_CRYPTER) @property def images_dir(self) -> str: diff --git a/src/datumaro/components/media.py b/src/datumaro/components/media.py index 9453e71dec..fc9daf1c69 100644 --- a/src/datumaro/components/media.py +++ b/src/datumaro/components/media.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021-2024 Intel Corporation +# Copyright (C) 2021-2022 Intel Corporation # # SPDX-License-Identifier: MIT @@ -94,7 +94,7 @@ def media(self) -> Optional[Type[MediaElement]]: class MediaElement(Generic[AnyData]): _type = MediaType.MEDIA_ELEMENT - def __init__(self, crypter: Crypter = NULL_CRYPTER, *args, **kwargs) -> None: + def __init__(self, crypter: Crypter = NULL_CRYPTER) -> None: self._crypter = crypter def as_dict(self) -> Dict[str, Any]: @@ -488,26 +488,6 @@ def video(self) -> Video: def path(self) -> str: return self._video.path - def from_self(self, **kwargs): - attrs = deepcopy(self.as_dict()) - if "path" in kwargs: - attrs.update({"video": self.video.from_self(**kwargs)}) - kwargs.pop("path") - attrs.update(kwargs) - return self.__class__(**attrs) - - def __getstate__(self): - # Return only the picklable parts of the state. - state = self.__dict__.copy() - del state["_data"] - return state - - def __setstate__(self, state): - # Restore the objects' state. - self.__dict__.update(state) - # Reinitialize unpichlable attributes - self._data = lambda: self._video.get_frame_data(self._index) - class _VideoFrameIterator(Iterator[VideoFrame]): """ @@ -547,11 +527,6 @@ def _decode(self, cap) -> Iterator[VideoFrame]: if self._video._frame_count is None: self._video._frame_count = self._pos + 1 - if self._video._end_frame and self._video._end_frame >= self._video._frame_count: - raise ValueError( - f"The end_frame value({self._video._end_frame}) of the video " - f"must be less than the frame count({self._video._frame_count})." - ) def _make_frame(self, index) -> VideoFrame: return VideoFrame(self._video, index=index) @@ -600,22 +575,13 @@ class Video(MediaElement, Iterable[VideoFrame]): """ def __init__( - self, - path: str, - step: int = 1, - start_frame: int = 0, - end_frame: Optional[int] = None, - *args, - **kwargs, + self, path: str, *, step: int = 1, start_frame: int = 0, end_frame: Optional[int] = None ) -> None: - super().__init__(*args, **kwargs) + super().__init__() self._path = path - assert 0 <= start_frame if end_frame: - assert start_frame <= end_frame - # we can't know the video length here, - # so we cannot validate if the end_frame is valid. + assert start_frame < end_frame assert 0 < step self._step = step self._start_frame = start_frame @@ -664,7 +630,7 @@ def __iter__(self) -> Iterator[VideoFrame]: # Decoding is not necessary to get frame pointers # However, it can be inacurrate end_frame = self._get_end_frame() - for index in range(self._start_frame, end_frame + 1, self._step): + for index in range(self._start_frame, end_frame, self._step): yield VideoFrame(video=self, index=index) else: # Need to decode to iterate over frames @@ -673,8 +639,7 @@ def __iter__(self) -> Iterator[VideoFrame]: @property def length(self) -> Optional[int]: """ - Returns frame count of the closed interval [start_frame, end_frame], - if video provides such information. + Returns frame count, if video provides such information. Note that not all videos provide length / duration metainfo, so the result may be undefined. @@ -690,15 +655,12 @@ def length(self) -> Optional[int]: if self._length is None: end_frame = self._get_end_frame() + length = None if end_frame is not None: - length = (end_frame + 1 - self._start_frame) // self._step - if 0 >= length: - raise ValueError( - "There is no valid frame for the closed interval" - f"[start_frame({self._start_frame})," - f" end_frame({end_frame})] with step({self._step})." - ) - self._length = length + length = (end_frame - self._start_frame) // self._step + assert 0 < length + + self._length = length return self._length @@ -724,23 +686,18 @@ def _get_frame_size(self) -> Tuple[int, int]: return frame_size def _get_end_frame(self): - # Note that end_frame could less than the last frame of the video if self._end_frame is not None and self._frame_count is not None: end_frame = min(self._end_frame, self._frame_count) - elif self._end_frame is not None: - end_frame = self._end_frame - elif self._frame_count is not None: - end_frame = self._frame_count - 1 else: - end_frame = None + end_frame = self._end_frame or self._frame_count return end_frame def _includes_frame(self, i): + end_frame = self._get_end_frame() if self._start_frame <= i: if (i - self._start_frame) % self._step == 0: - end_frame = self._get_end_frame() - if end_frame is None or i <= end_frame: + if end_frame is None or i < end_frame: return True return False @@ -762,49 +719,15 @@ def _reset_reader(self): assert self._reader.isOpened() def __eq__(self, other: object) -> bool: - def _get_frame(obj: Video, idx: int): - try: - return obj[idx] - except IndexError: - return None - if not isinstance(other, __class__): return False - if self._start_frame != other._start_frame or self._step != other._step: - return False - # The video path can vary if a dataset is copied. - # So, we need to check if the video data is the same instead of checking paths. - if self._end_frame is not None and self._end_frame == other._end_frame: - for idx in range(self._start_frame, self._end_frame + 1, self._step): - if self[idx] != other[idx]: - return False - return True - - end_frame = self._end_frame or other._end_frame - if end_frame is None: - last_frame = None - for idx, frame in enumerate(self): - if frame != _get_frame(other, frame.index): - return False - last_frame = frame - # check if the actual last frames are same - try: - other[last_frame.index + self._step if last_frame else self._start_frame] - except IndexError: - return True - return False - - # _end_frame values, only one of the two is valid - for idx in range(self._start_frame, end_frame + 1, self._step): - frame = _get_frame(self, idx) - if frame is None: - return False - if frame != _get_frame(other, idx): - return False - # check if the actual last frames are same - idx_next = end_frame + self._step - return None is (_get_frame(self, idx_next) or _get_frame(other, idx_next)) + return ( + self.path == other.path + and self._start_frame == other._start_frame + and self._step == other._step + and self._end_frame == other._end_frame + ) def __hash__(self): # Required for caching diff --git a/src/datumaro/plugins/data_formats/datumaro/base.py b/src/datumaro/plugins/data_formats/datumaro/base.py index ee7a8cdc21..5095582e9e 100644 --- a/src/datumaro/plugins/data_formats/datumaro/base.py +++ b/src/datumaro/plugins/data_formats/datumaro/base.py @@ -1,4 +1,4 @@ -# Copyright (C) 2024 Intel Corporation +# Copyright (C) 2023 Intel Corporation # # SPDX-License-Identifier: MIT @@ -132,7 +132,6 @@ def _gen(): items = [] ann_types = set() - actual_media_types = set() for item_desc in pbar.iter( _gen(), desc=f"Importing '{self._subset}'", total=len(item_descs) ): @@ -141,28 +140,12 @@ def _gen(): items.append(item) for ann in item.annotations: ann_types.add(ann.type) - if item.media: - actual_media_types.add(item.media.type) self.ann_types = ann_types - if len(actual_media_types) == 1: - actual_media_type = actual_media_types.pop() - elif len(actual_media_types) > 1: - actual_media_type = MediaType.MEDIA_ELEMENT - else: - actual_media_type = None - - if actual_media_type and not issubclass(actual_media_type.media, self.media_type): - raise MediaTypeError( - f"Unexpected media type of a dataset '{self.media_type}'. " - f"Expected media type is '{actual_media_type.media}." - ) - return items def _parse_item(self, item_desc: Dict) -> Optional[DatasetItem]: - STR_MULTIPLE_MEDIA = "DatasetItem cannot contain multiple media types" try: item_id = item_desc["id"] @@ -178,10 +161,12 @@ def _parse_item(self, item_desc: Dict) -> Optional[DatasetItem]: image_path = old_image_path media = Image.from_file(path=image_path, size=image_info.get("size")) + if self.media_type == MediaElement: + self.media_type = Image pcd_info = item_desc.get("point_cloud") if media and pcd_info: - raise MediaTypeError(STR_MULTIPLE_MEDIA) + raise MediaTypeError("Dataset cannot contain multiple media types") if pcd_info: pcd_path = pcd_info.get("path") point_cloud = osp.join(self._pcd_dir, self._subset, pcd_path) @@ -198,10 +183,12 @@ def _parse_item(self, item_desc: Dict) -> Optional[DatasetItem]: ] media = PointCloud.from_file(path=point_cloud, extra_images=related_images) + if self.media_type == MediaElement: + self.media_type = PointCloud video_frame_info = item_desc.get("video_frame") if media and video_frame_info: - raise MediaTypeError(STR_MULTIPLE_MEDIA) + raise MediaTypeError("Dataset cannot contain multiple media types") if video_frame_info: video_path = osp.join( self._video_dir, self._subset, video_frame_info.get("video_path") @@ -214,20 +201,6 @@ def _parse_item(self, item_desc: Dict) -> Optional[DatasetItem]: media = VideoFrame(video, frame_index) - video_info = item_desc.get("video") - if media and video_info: - raise MediaTypeError(STR_MULTIPLE_MEDIA) - if video_info: - video_path = osp.join(self._video_dir, self._subset, video_info.get("path")) - if video_path not in self._videos: - self._videos[video_path] = Video(video_path) - step = video_info.get("step", 1) - start_frame = video_info.get("start_frame", 0) - end_frame = video_info.get("end_frame", None) - media = Video( - path=video_path, step=step, start_frame=start_frame, end_frame=end_frame - ) - media_desc = item_desc.get("media") if not media and media_desc and media_desc.get("path"): media = MediaElement(path=media_desc.get("path")) diff --git a/src/datumaro/plugins/data_formats/datumaro/exporter.py b/src/datumaro/plugins/data_formats/datumaro/exporter.py index 582a42dc46..a6e5be71f2 100644 --- a/src/datumaro/plugins/data_formats/datumaro/exporter.py +++ b/src/datumaro/plugins/data_formats/datumaro/exporter.py @@ -1,4 +1,4 @@ -# Copyright (C) 2024 Intel Corporation +# Copyright (C) 2023 Intel Corporation # # SPDX-License-Identifier: MIT @@ -179,24 +179,6 @@ def context_save_media( """ if item.media is None: yield - elif isinstance(item.media, Video): - video = item.media_as(Video) - - if context.save_media: - fname = context.make_video_filename(item) - # To prevent the video from being overwritten - # (A video can have same path but different start/end frames) - if not osp.exists(fname): - context.save_video(item, fname=fname, subdir=item.subset) - item.media = Video( - path=video.path, - step=video._step, - start_frame=video._start_frame, - end_frame=video._end_frame, - ) - - yield - item.media = video elif isinstance(item.media, VideoFrame): video_frame = item.media_as(VideoFrame) @@ -263,15 +245,6 @@ def _gen_item_desc(self, item: DatasetItem, *args, **kwargs) -> Dict: "video_path": getattr(video_frame.video, "path", None), "frame_index": getattr(video_frame, "index", -1), } - elif isinstance(item.media, Video): - video = item.media_as(Video) - item_desc["video"] = { - "path": getattr(video, "path", None), - "step": video._step, - "start_frame": video._start_frame, - } - if video._end_frame is not None: - item_desc["video"]["end_frame"] = video._end_frame elif isinstance(item.media, Image): image = item.media_as(Image) item_desc["image"] = {"path": getattr(image, "path", None)} diff --git a/src/datumaro/plugins/data_formats/datumaro_binary/base.py b/src/datumaro/plugins/data_formats/datumaro_binary/base.py index a1ef0d74bd..b054a1fe91 100644 --- a/src/datumaro/plugins/data_formats/datumaro_binary/base.py +++ b/src/datumaro/plugins/data_formats/datumaro_binary/base.py @@ -1,4 +1,4 @@ -# Copyright (C) 2024 Intel Corporation +# Copyright (C) 2023 Intel Corporation # # SPDX-License-Identifier: MIT @@ -12,7 +12,7 @@ from datumaro.components.dataset_base import DatasetItem from datumaro.components.errors import DatasetImportError from datumaro.components.importer import ImportContext -from datumaro.components.media import Image, MediaElement, MediaType, PointCloud, Video, VideoFrame +from datumaro.components.media import Image, MediaElement, MediaType, PointCloud, VideoFrame from datumaro.plugins.data_formats.datumaro_binary.format import DatumaroBinaryPath from datumaro.plugins.data_formats.datumaro_binary.mapper import DictMapper from datumaro.plugins.data_formats.datumaro_binary.mapper.common import IntListMapper @@ -108,8 +108,6 @@ def _read_media_type(self): self._media_type = Image elif media_type == MediaType.POINT_CLOUD: self._media_type = PointCloud - elif media_type == MediaType.VIDEO: - self._media_type = Video elif media_type == MediaType.VIDEO_FRAME: self._media_type = VideoFrame elif media_type == MediaType.MEDIA_ELEMENT: @@ -125,8 +123,7 @@ def _read_items(self) -> None: media_path_prefix = { MediaType.IMAGE: osp.join(self._images_dir, self._subset), MediaType.POINT_CLOUD: osp.join(self._pcd_dir, self._subset), - MediaType.VIDEO: osp.join(self._video_dir, self._subset), - MediaType.VIDEO_FRAME: osp.join(self._video_dir, self._subset), + MediaType.VIDEO_FRAME: self._video_dir, } if self._num_workers > 0: diff --git a/src/datumaro/plugins/data_formats/datumaro_binary/mapper/media.py b/src/datumaro/plugins/data_formats/datumaro_binary/mapper/media.py index b832525e9c..3570cc883c 100644 --- a/src/datumaro/plugins/data_formats/datumaro_binary/mapper/media.py +++ b/src/datumaro/plugins/data_formats/datumaro_binary/mapper/media.py @@ -1,4 +1,4 @@ -# Copyright (C) 2024 Intel Corporation +# Copyright (C) 2023 Intel Corporation # # SPDX-License-Identifier: MIT @@ -21,8 +21,6 @@ def forward(cls, obj: Optional[MediaElement]) -> bytes: return ImageMapper.forward(obj) elif obj._type == MediaType.POINT_CLOUD: return PointCloudMapper.forward(obj) - elif obj._type == MediaType.VIDEO: - return VideoMapper.forward(obj) elif obj._type == MediaType.VIDEO_FRAME: return VideoFrameMapper.forward(obj) elif obj._type == MediaType.MEDIA_ELEMENT: @@ -45,8 +43,6 @@ def backward( return ImageMapper.backward(_bytes, offset, media_path_prefix) elif media_type == MediaType.POINT_CLOUD: return PointCloudMapper.backward(_bytes, offset, media_path_prefix) - elif media_type == MediaType.VIDEO: - return VideoMapper.backward(_bytes, offset, media_path_prefix) elif media_type == MediaType.VIDEO_FRAME: return VideoFrameMapper.backward(_bytes, offset, media_path_prefix) elif media_type == MediaType.MEDIA_ELEMENT: @@ -132,39 +128,6 @@ def backward( ) -class VideoMapper(MediaElementMapper): - MAGIC_END_FRAME_FOR_NONE = 4294967295 # max value of unsigned int32 - MEDIA_TYPE = MediaType.VIDEO - - @classmethod - def forward(cls, obj: Video) -> bytes: - end_frame = obj._end_frame if obj._end_frame else cls.MAGIC_END_FRAME_FOR_NONE - - bytes_arr = bytearray() - bytes_arr.extend(super().forward(obj)) - bytes_arr.extend(struct.pack(" Tuple[Video, int]: - media_dict, offset = cls.backward_dict(_bytes, offset, media_path_prefix) - step, start_frame, end_frame = struct.unpack_from(" np.ndarray: raise ValueError - def decode_by_pil(self, image_bytes: bytes) -> np.ndarray: + def decode_by_pil(self, image_bytes: bytes) -> PILImage: """Convert image color channel for PIL Image.""" from PIL import Image img = Image.open(BytesIO(image_bytes)) if self == ImageColorChannel.UNCHANGED: - return np.asarray(img) + return img if self == ImageColorChannel.COLOR_BGR: - img = np.asarray(img.convert("RGB")) - return cv2.cvtColor(img, cv2.COLOR_RGB2BGR) + return Image.fromarray(np.flip(np.asarray(img.convert("RGB")), -1)) if self == ImageColorChannel.COLOR_RGB: - return np.asarray(img.convert("RGB")) + return img.convert("RGB") raise ValueError @@ -288,13 +289,13 @@ def decode_image(image_bytes: bytes, dtype: DTypeLike = np.uint8) -> np.ndarray: if IMAGE_BACKEND.get() == ImageBackend.cv2: image = ctx_color_scale.decode_by_cv2(image_bytes) + image = image.astype(dtype) elif IMAGE_BACKEND.get() == ImageBackend.PIL: image = ctx_color_scale.decode_by_pil(image_bytes) + image = np.asarray(image, dtype=dtype) else: raise NotImplementedError() - image = image.astype(dtype) - assert len(image.shape) in {2, 3} if len(image.shape) == 3: assert image.shape[2] in {3, 4} diff --git a/src/datumaro/version.py b/src/datumaro/version.py index 14d9d2f583..6eff2c814c 100644 --- a/src/datumaro/version.py +++ b/src/datumaro/version.py @@ -1 +1 @@ -__version__ = "1.7.0" +__version__ = "1.7.0rc0" diff --git a/tests/integration/cli/test_utils.py b/tests/integration/cli/test_utils.py index f0e03ef951..2a5c9949be 100644 --- a/tests/integration/cli/test_utils.py +++ b/tests/integration/cli/test_utils.py @@ -1,7 +1,3 @@ -# Copyright (C) 2024 Intel Corporation -# -# SPDX-License-Identifier: MIT - import os import os.path as osp from unittest.case import TestCase @@ -50,4 +46,4 @@ def test_can_split_video(self, mock_video_frame_data): "2", ) - assert set(os.listdir(output_dir)) == {"%06d.jpg" % n for n in range(2, 9, 2)} + assert set(os.listdir(output_dir)) == {"%06d.jpg" % n for n in range(2, 8, 2)} diff --git a/tests/integration/cli/test_video.py b/tests/integration/cli/test_video.py index 0146bd2624..37742c8d16 100644 --- a/tests/integration/cli/test_video.py +++ b/tests/integration/cli/test_video.py @@ -1,17 +1,12 @@ -# Copyright (C) 2024 Intel Corporation -# -# SPDX-License-Identifier: MIT - import os import os.path as osp from unittest import TestCase import pytest -from datumaro.components.annotation import Bbox, Label +from datumaro.components.annotation import Bbox from datumaro.components.dataset import Dataset, DatasetItem -from datumaro.components.errors import DatasetImportError -from datumaro.components.media import MediaElement, Video, VideoFrame +from datumaro.components.media import Video, VideoFrame from ...requirements import Requirements, mark_requirement @@ -98,7 +93,7 @@ def test_can_extract_frames_from_video(self): "--start-frame", "0", "--end-frame", - "3", + "4", "--step", "2", ) @@ -145,61 +140,27 @@ def test_can_extract_keyframes_from_video(self): compare_datasets(self, expected, parsed_dataset) - @staticmethod - def _make_sample_video_dataset(test_dir: str, media_type=None) -> Dataset: - video_path = osp.join(test_dir, "video.avi") - make_sample_video(video_path, frame_size=(4, 6), frames=4) - video = Video(video_path) - - kwargs = { - "iterable": [ - DatasetItem( - "0", - media=VideoFrame(video, 0), - annotations=[ - Bbox(1, 1, 1, 1, label=0, object_id=0), - Bbox(2, 2, 2, 2, label=1, object_id=1), - ], - ), - DatasetItem( - "1", - media=Video(video_path, step=1, start_frame=0, end_frame=1), - annotations=[ - Label(0), - ], - ), - DatasetItem( - "2", - media=Video(video_path, step=1, start_frame=2, end_frame=2), - annotations=[ - Label(1), - ], - ), - ], - "categories": ["a", "b"], - } - if media_type: - kwargs["media_type"] = media_type - dataset = Dataset.from_iterable(**kwargs) - - return dataset - - @mark_requirement(Requirements.DATUM_GENERAL_REQ) - def test_cannot_create_video_and_videoframe_dataset_with_wrong_media_type(self): - with TestDir() as test_dir: - dataset = self._make_sample_video_dataset(test_dir) - project_dir = osp.join(test_dir, "proj") - run(self, "project", "create", "-o", project_dir) - with self.assertRaises(DatasetImportError): - dataset_dir = osp.join(test_dir, "test_video") - dataset.save(dataset_dir, save_media=True) - run(self, "project", "import", "-p", project_dir, "-f", "datumaro", dataset_dir) - @pytest.mark.new @mark_requirement(Requirements.DATUM_GENERAL_REQ) def test_can_export_video_dataset(self): with TestDir() as test_dir: - expected = self._make_sample_video_dataset(test_dir, media_type=MediaElement) + video_path = osp.join(test_dir, "video.avi") + make_sample_video(video_path, frame_size=(4, 6), frames=4) + video = Video(video_path) + + expected = Dataset.from_iterable( + [ + DatasetItem( + 0, + media=VideoFrame(video, 0), + annotations=[ + Bbox(1, 1, 1, 1, label=0, object_id=0), + Bbox(2, 2, 2, 2, label=1, object_id=1), + ], + ) + ], + categories=["a", "b"], + ) project_dir = osp.join(test_dir, "proj") run(self, "project", "create", "-o", project_dir) diff --git a/tests/unit/components/test_exporter.py b/tests/unit/components/test_exporter.py deleted file mode 100644 index 4820b32eda..0000000000 --- a/tests/unit/components/test_exporter.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import os -import os.path as osp - -import pytest - -from datumaro.components.dataset_base import DatasetItem -from datumaro.components.exporter import ExportContextComponent -from datumaro.components.media import MediaElement, Video, VideoFrame - -from tests.utils.test_utils import TestDir -from tests.utils.video import make_sample_video - - -@pytest.fixture() -def fxt_sample_video(test_dir): - video_path = osp.join(test_dir, "video.avi") - make_sample_video(video_path, frame_size=(4, 6), frames=4) - yield video_path - - -@pytest.fixture -def fxt_export_context_component(test_dir): - return ExportContextComponent( - save_dir=test_dir, - save_media=True, - images_dir="images", - pcd_dir="point_clouds", - video_dir="videos", - ) - - -class ExportContextComponentTest: - def test_make_video_filename(self, fxt_export_context_component, fxt_sample_video): - video = Video(fxt_sample_video) - frame = VideoFrame(video, index=1) - - ecc: ExportContextComponent = fxt_export_context_component - - for media in [video, frame]: - assert "video.avi" == ecc.make_video_filename(DatasetItem(0, media=media)) - - # error cases - for item in [None, DatasetItem(0, media=MediaElement())]: - with pytest.raises(AssertionError): - ecc.make_video_filename(item) - - def test_save_video(self, fxt_export_context_component, fxt_sample_video): - video = Video(fxt_sample_video) - frame = VideoFrame(video, index=1) - ecc: ExportContextComponent = fxt_export_context_component - - with TestDir() as test_dir: - ecc.save_video(DatasetItem(0, media=video), basedir=test_dir) - expected_path = osp.join(test_dir, "video.avi") - assert osp.exists(expected_path) - - with TestDir() as test_dir: - ecc.save_video(DatasetItem(0, media=frame), basedir=test_dir) - expected_path = osp.join(test_dir, "video.avi") - assert osp.exists(expected_path) - - # cannot save items with no media - with TestDir() as test_dir: - ecc.save_video(DatasetItem(0), basedir=test_dir) - files = os.listdir(test_dir) - assert not files diff --git a/tests/unit/data_formats/datumaro/conftest.py b/tests/unit/data_formats/datumaro/conftest.py index e600ae957c..6707c2c637 100644 --- a/tests/unit/data_formats/datumaro/conftest.py +++ b/tests/unit/data_formats/datumaro/conftest.py @@ -1,4 +1,4 @@ -# Copyright (C) 2024 Intel Corporation +# Copyright (C) 2023 Intel Corporation # # SPDX-License-Identifier: MIT @@ -28,13 +28,11 @@ RleMask, ) from datumaro.components.dataset_base import DatasetItem -from datumaro.components.media import Image, MediaElement, PointCloud, Video, VideoFrame +from datumaro.components.media import Image, PointCloud from datumaro.components.project import Dataset from datumaro.plugins.data_formats.datumaro.format import DatumaroPath from datumaro.util.mask_tools import generate_colormap -from tests.utils.video import make_sample_video - @pytest.fixture def fxt_test_datumaro_format_dataset(): @@ -201,54 +199,6 @@ def fxt_test_datumaro_format_dataset(): ) -@pytest.fixture -def fxt_test_datumaro_format_video_dataset(test_dir) -> Dataset: - video_path = osp.join(test_dir, "video.avi") - make_sample_video(video_path, frame_size=(4, 6), frames=4) - video = Video(video_path) - - return Dataset.from_iterable( - iterable=[ - DatasetItem( - "f0", - subset="train", - media=VideoFrame(video, 0), - annotations=[ - Bbox(1, 1, 1, 1, label=0, object_id=0), - Bbox(2, 2, 2, 2, label=1, object_id=1), - ], - ), - DatasetItem( - "f1", - subset="test", - media=VideoFrame(video, 0), - annotations=[ - Bbox(0, 0, 2, 2, label=1, object_id=1), - Bbox(3, 3, 1, 1, label=0, object_id=0), - ], - ), - DatasetItem( - "v0", - subset="train", - media=Video(video_path, step=1, start_frame=0, end_frame=1), - annotations=[ - Label(0), - ], - ), - DatasetItem( - "v1", - subset="test", - media=Video(video_path, step=1, start_frame=2, end_frame=2), - annotations=[ - Bbox(1, 1, 3, 3, label=1, object_id=1), - ], - ), - ], - media_type=MediaElement, - categories=["a", "b"], - ) - - @pytest.fixture def fxt_wrong_version_dir(fxt_test_datumaro_format_dataset, test_dir): dest_dir = osp.join(test_dir, "wrong_version") diff --git a/tests/unit/data_formats/datumaro/test_datumaro_binary_format.py b/tests/unit/data_formats/datumaro/test_datumaro_binary_format.py index 73cacaded0..00700bb1e7 100644 --- a/tests/unit/data_formats/datumaro/test_datumaro_binary_format.py +++ b/tests/unit/data_formats/datumaro/test_datumaro_binary_format.py @@ -1,5 +1,5 @@ # pylint: disable=arguments-differ -# Copyright (C) 2024 Intel Corporation +# Copyright (C) 2023 Intel Corporation # # SPDX-License-Identifier: MIT @@ -39,13 +39,10 @@ class DatumaroBinaryFormatTest(TestBase): ann_ext = DatumaroBinaryPath.ANNOTATION_EXT @pytest.mark.parametrize( - "fxt_dataset", - ("fxt_test_datumaro_format_dataset", "fxt_test_datumaro_format_video_dataset"), - ) - @pytest.mark.parametrize( - ["compare", "require_media", "fxt_import_kwargs", "fxt_export_kwargs"], + ["fxt_dataset", "compare", "require_media", "fxt_import_kwargs", "fxt_export_kwargs"], [ pytest.param( + "fxt_test_datumaro_format_dataset", compare_datasets_strict, True, {}, @@ -53,6 +50,7 @@ class DatumaroBinaryFormatTest(TestBase): id="test_no_encryption", ), pytest.param( + "fxt_test_datumaro_format_dataset", compare_datasets_strict, True, {"encryption_key": ENCRYPTION_KEY}, @@ -60,6 +58,7 @@ class DatumaroBinaryFormatTest(TestBase): id="test_with_encryption", ), pytest.param( + "fxt_test_datumaro_format_dataset", compare_datasets_strict, True, {"encryption_key": ENCRYPTION_KEY}, @@ -67,6 +66,7 @@ class DatumaroBinaryFormatTest(TestBase): id="test_no_media_encryption", ), pytest.param( + "fxt_test_datumaro_format_dataset", compare_datasets_strict, True, {"encryption_key": ENCRYPTION_KEY}, @@ -74,6 +74,7 @@ class DatumaroBinaryFormatTest(TestBase): id="test_multi_blobs", ), pytest.param( + "fxt_test_datumaro_format_dataset", compare_datasets_strict, True, {"encryption_key": ENCRYPTION_KEY, "num_workers": 2}, @@ -166,15 +167,10 @@ def _get_ann_mapper(ann: Annotation) -> AnnotationMapper: def test_common_mapper(self, mapper: Mapper, expected: Any): self._test(mapper, expected) - @pytest.mark.parametrize( - "fxt_dataset", - ("fxt_test_datumaro_format_dataset", "fxt_test_datumaro_format_video_dataset"), - ) - def test_annotations_mapper(self, fxt_dataset, request): - """Test all annotations in fxt_dataset""" + def test_annotations_mapper(self, fxt_test_datumaro_format_dataset): + """Test all annotations in fxt_test_datumaro_format_dataset""" mapper = DatasetItemMapper - fxt_dataset = request.getfixturevalue(fxt_dataset) - for item in fxt_dataset: + for item in fxt_test_datumaro_format_dataset: for ann in item.annotations: mapper = self._get_ann_mapper(ann) self._test(mapper, ann) diff --git a/tests/unit/data_formats/datumaro/test_datumaro_format.py b/tests/unit/data_formats/datumaro/test_datumaro_format.py index 1f9caadfbd..9a3168df83 100644 --- a/tests/unit/data_formats/datumaro/test_datumaro_format.py +++ b/tests/unit/data_formats/datumaro/test_datumaro_format.py @@ -1,4 +1,4 @@ -# Copyright (C) 2024 Intel Corporation +# Copyright (C) 2023 Intel Corporation # # SPDX-License-Identifier: MIT @@ -76,18 +76,6 @@ def _test_save_and_load( False, id="test_can_save_and_load_with_no_save_media", ), - pytest.param( - "fxt_test_datumaro_format_video_dataset", - compare_datasets, - True, - id="test_can_save_and_load_video_dataset", - ), - pytest.param( - "fxt_test_datumaro_format_video_dataset", - None, - False, - id="test_can_save_and_load_video_dataset_with_no_save_media", - ), pytest.param( "fxt_relative_paths", compare_datasets, @@ -188,13 +176,8 @@ def test_source_target_pair( ) @mark_requirement(Requirements.DATUM_GENERAL_REQ) - @pytest.mark.parametrize( - "fxt_dataset", - ("fxt_test_datumaro_format_dataset", "fxt_test_datumaro_format_video_dataset"), - ) - def test_can_detect(self, fxt_dataset, test_dir, request): - fxt_dataset = request.getfixturevalue(fxt_dataset) - self.exporter.convert(fxt_dataset, save_dir=test_dir) + def test_can_detect(self, fxt_test_datumaro_format_dataset, test_dir): + self.exporter.convert(fxt_test_datumaro_format_dataset, save_dir=test_dir) detected_formats = Environment().detect_dataset(test_dir) assert [self.importer.NAME] == detected_formats diff --git a/tests/unit/test_annotation.py b/tests/unit/test_annotation.py index ddcd718bf7..a742526ea7 100644 --- a/tests/unit/test_annotation.py +++ b/tests/unit/test_annotation.py @@ -1,24 +1,16 @@ -# Copyright (C) 2020-2024 Intel Corporation +# Copyright (C) 2020-2021 Intel Corporation # # SPDX-License-Identifier: MIT from pathlib import Path from typing import List -from unittest.mock import patch import cv2 import numpy as np import pytest import shapely.geometry as sg -from datumaro.components.annotation import ( - Annotations, - Ellipse, - ExtractedMask, - HashKey, - Mask, - RotatedBbox, -) +from datumaro.components.annotation import Ellipse, ExtractedMask, HashKey, RotatedBbox from datumaro.util.image import lazy_image @@ -78,67 +70,19 @@ def test_create_polygon(self, fxt_rot_bbox): assert fxt_rot_bbox == expected -@pytest.fixture -def fxt_index_mask(): - return np.random.randint(0, 10, size=(10, 10)) - - -@pytest.fixture -def fxt_index_mask_file(fxt_index_mask, tmpdir): - fpath = Path(tmpdir, "mask.png") - cv2.imwrite(str(fpath), fxt_index_mask) - yield fpath +class ExtractedMaskTest: + @pytest.fixture + def fxt_index_mask(self): + return np.random.randint(0, 10, size=(10, 10)) + @pytest.fixture + def fxt_index_mask_file(self, fxt_index_mask, tmpdir): + fpath = Path(tmpdir, "mask.png") + cv2.imwrite(str(fpath), fxt_index_mask) + yield fpath -class ExtractedMaskTest: def test_extracted_mask(self, fxt_index_mask, fxt_index_mask_file): index_mask = lazy_image(path=str(fxt_index_mask_file), dtype=np.uint8) for index in range(10): mask = ExtractedMask(index_mask=index_mask, index=index) assert np.allclose(mask.image, (fxt_index_mask == index)) - - -class AnnotationsTest: - @pytest.mark.parametrize("dtype", [np.uint8, np.int32]) - def test_get_semantic_seg_mask_extracted_mask(self, fxt_index_mask_file, fxt_index_mask, dtype): - index_mask = lazy_image(path=str(fxt_index_mask_file), dtype=np.uint8) - annotations = Annotations( - ExtractedMask(index_mask=index_mask, index=index, label=index) for index in range(10) - ) - with patch("datumaro.components.annotation.Mask.as_class_mask") as mock_as_class_mask: - semantic_seg_mask = annotations.get_semantic_seg_mask(ignore_index=255, dtype=dtype) - - assert np.allclose(semantic_seg_mask, fxt_index_mask) - # It should directly look up index_mask and there is no calling as_class_mask() - mock_as_class_mask.assert_not_called() - - @pytest.mark.parametrize("dtype", [np.uint8, np.int32]) - def test_get_semantic_seg_mask_extracted_mask_remapping_label( - self, fxt_index_mask_file, fxt_index_mask, dtype - ): - index_mask = lazy_image(path=str(fxt_index_mask_file), dtype=np.uint8) - annotations = Annotations( - ExtractedMask( - index_mask=index_mask, - index=index, - label=index % 5, # Remapping label - ) - for index in range(10) - ) - semantic_seg_mask = annotations.get_semantic_seg_mask(ignore_index=255, dtype=dtype) - - # fxt_index_mask % 5 is label-remapped ground truth - assert np.allclose(semantic_seg_mask, fxt_index_mask % 5) - - @pytest.mark.parametrize("dtype", [np.uint8, np.int32]) - def test_get_semantic_seg_mask_binary_mask(self, fxt_index_mask, dtype): - annotations = Annotations( - Mask( - image=fxt_index_mask == index, - label=index, - ) - for index in range(10) - ) - semantic_seg_mask = annotations.get_semantic_seg_mask(ignore_index=255, dtype=dtype) - - assert np.allclose(semantic_seg_mask, fxt_index_mask) diff --git a/tests/unit/test_dataset.py b/tests/unit/test_dataset.py index 44e9fdd6dd..a698de1aed 100644 --- a/tests/unit/test_dataset.py +++ b/tests/unit/test_dataset.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2024 Intel Corporation +# Copyright (C) 2019-2023 Intel Corporation # # SPDX-License-Identifier: MIT @@ -14,7 +14,6 @@ from datumaro.components.annotation import ( Annotation, - Annotations, AnnotationType, Bbox, Caption, @@ -2079,41 +2078,25 @@ def test_index_access_tile(self): self.assertRaises(IndexError, lambda: dataset[length]) -class DatasetItemTest: +class DatasetItemTest(TestCase): @mark_requirement(Requirements.DATUM_GENERAL_REQ) def test_ctor_requires_id(self): - with pytest.raises(Exception): + with self.assertRaises(Exception): # pylint: disable=no-value-for-parameter DatasetItem() # pylint: enable=no-value-for-parameter + @staticmethod @mark_requirement(Requirements.DATUM_GENERAL_REQ) - @pytest.mark.parametrize( - "kwargs", - [ + def test_ctors_with_image(): + for args in [ {"id": 0, "media": None}, - {"id": 0, "media": Image.from_file(path="path.jpg"), "annotations": []}, - { - "id": 0, - "media": Image.from_numpy(data=np.array([1, 2, 3])), - "annotations": Annotations(), - }, - { - "id": 0, - "media": Image.from_numpy(data=lambda f: np.array([1, 2, 3])), - "annotations": [Label(label=0), Label(label=1)], - }, - { - "id": 0, - "media": Image.from_numpy(data=np.array([1, 2, 3])), - "annotations": [Label(label=0), Label(label=1)], - }, - ], - ) - def test_ctors_with_image(self, kwargs): - item = DatasetItem(**kwargs) - assert isinstance(item.annotations, Annotations) - assert item.annotations == kwargs.get("annotations", []) + {"id": 0, "media": Image.from_file(path="path.jpg")}, + {"id": 0, "media": Image.from_numpy(data=np.array([1, 2, 3]))}, + {"id": 0, "media": Image.from_numpy(data=lambda f: np.array([1, 2, 3]))}, + {"id": 0, "media": Image.from_numpy(data=np.array([1, 2, 3]))}, + ]: + DatasetItem(**args) class DatasetFilterTest(TestCase): diff --git a/tests/unit/test_kinetics_format.py b/tests/unit/test_kinetics_format.py index 67567e14c5..586ddbe900 100644 --- a/tests/unit/test_kinetics_format.py +++ b/tests/unit/test_kinetics_format.py @@ -1,11 +1,4 @@ -# Copyright (C) 2024 Intel Corporation -# -# SPDX-License-Identifier: MIT - -import os -import os.path as osp - -import pytest +from unittest import TestCase from datumaro.components.annotation import Label from datumaro.components.dataset import Dataset, DatasetItem @@ -18,68 +11,46 @@ from tests.utils.assets import get_test_asset_path from tests.utils.test_utils import compare_datasets -KINETICS_DATASET_DIR = get_test_asset_path("kinetics_dataset") - - -@pytest.fixture -def fxt_kinetics_dataset(test_dir): - def make_video(fname, frame_size=(4, 6), frames=4): - src_path = osp.join(KINETICS_DATASET_DIR, fname) - dst_path = osp.join(test_dir, fname) - if not osp.exists(osp.dirname(dst_path)): - os.makedirs(osp.dirname(dst_path)) - os.symlink(src_path, dst_path) - return Video(dst_path) - - return Dataset.from_iterable( - [ - DatasetItem( - id="1", - subset="test", - annotations=[Label(0, attributes={"time_start": 0, "time_end": 2})], - media=make_video("video_1.avi"), - ), - DatasetItem( - id="2", - subset="test", - annotations=[Label(0, attributes={"time_start": 5, "time_end": 7})], - ), - DatasetItem( - id="4", - subset="test", - annotations=[Label(1, attributes={"time_start": 10, "time_end": 15})], - ), - DatasetItem( - id="3", - subset="train", - annotations=[Label(2, attributes={"time_start": 0, "time_end": 2})], - media=make_video("train/3.avi"), - ), - ], - categories=["label_0", "label_1", "label_2"], - media_type=Video, - ) +DUMMY_DATASET_DIR = get_test_asset_path("kinetics_dataset") -class KineticsImporterTest: +class KineticsImporterTest(TestCase): @mark_requirement(Requirements.DATUM_GENERAL_REQ) def test_can_detect(self): - detected_formats = Environment().detect_dataset(KINETICS_DATASET_DIR) - assert [KineticsImporter.NAME] == detected_formats + detected_formats = Environment().detect_dataset(DUMMY_DATASET_DIR) + self.assertEqual([KineticsImporter.NAME], detected_formats) @mark_requirement(Requirements.DATUM_GENERAL_REQ) - def test_can_import_with_video(self, helper_tc, fxt_kinetics_dataset): - expected_dataset = fxt_kinetics_dataset - imported_dataset = Dataset.import_from(KINETICS_DATASET_DIR, "kinetics") - - compare_datasets(helper_tc, expected_dataset, imported_dataset, require_media=True) - - @mark_requirement(Requirements.DATUM_GENERAL_REQ) - def test_can_convert_to_datumaro_and_export_it(self, helper_tc, test_dir): - imported_dataset = Dataset.import_from(KINETICS_DATASET_DIR, "kinetics") - export_dir = osp.join(test_dir, "dst") - imported_dataset.export(export_dir, "datumaro", save_media=True) - - exported_dataset = Dataset.import_from(export_dir, "datumaro") - - compare_datasets(helper_tc, imported_dataset, exported_dataset, require_media=True) + def test_can_import_with_video(self): + expected_dataset = Dataset.from_iterable( + [ + DatasetItem( + id="1", + subset="test", + annotations=[Label(0, attributes={"time_start": 0, "time_end": 2})], + media=Video("./video_1.avi"), + ), + DatasetItem( + id="2", + subset="test", + annotations=[Label(0, attributes={"time_start": 5, "time_end": 7})], + ), + DatasetItem( + id="4", + subset="test", + annotations=[Label(1, attributes={"time_start": 10, "time_end": 15})], + ), + DatasetItem( + id="3", + subset="train", + annotations=[Label(2, attributes={"time_start": 0, "time_end": 2})], + media=Video("./train/3.avi"), + ), + ], + categories=["label_0", "label_1", "label_2"], + media_type=Video, + ) + + imported_dataset = Dataset.import_from(DUMMY_DATASET_DIR, "kinetics") + + compare_datasets(self, expected_dataset, imported_dataset, require_media=True) diff --git a/tests/unit/test_video.py b/tests/unit/test_video.py index 2d44a61ef5..dbbe9d4787 100644 --- a/tests/unit/test_video.py +++ b/tests/unit/test_video.py @@ -1,11 +1,10 @@ -# Copyright (C) 2024 Intel Corporation +# Copyright (C) 2023 Intel Corporation # # SPDX-License-Identifier: MIT import filecmp import os.path as osp from unittest import TestCase -from unittest.mock import MagicMock import numpy as np import pytest @@ -23,18 +22,20 @@ from tests.utils.test_utils import TestDir, compare_datasets -def _make_sample_video(video_dir, fname="video.avi", frame_size=(4, 6), frames=4): - video_path = osp.join(video_dir, fname) - make_sample_video(video_path, frame_size=frame_size, frames=frames) - return video_path +@pytest.fixture() +def fxt_sample_video(): + with TestDir() as test_dir: + video_path = osp.join(test_dir, "video.avi") + make_sample_video(video_path, frame_size=(4, 6), frames=4) + + yield video_path class VideoTest: @mark_requirement(Requirements.DATUM_GENERAL_REQ) @scoped - def test_can_read_video(self, test_dir): - video_path = _make_sample_video(test_dir) - video = Video(video_path) + def test_can_read_video(self, fxt_sample_video): + video = Video(fxt_sample_video) on_exit_do(video.close) assert None is video.length @@ -42,33 +43,20 @@ def test_can_read_video(self, test_dir): @mark_requirement(Requirements.DATUM_GENERAL_REQ) @scoped - def test_can_read_frames_sequentially(self, test_dir): - video_path = _make_sample_video(test_dir) - video = Video(video_path) + def test_can_read_frames_sequentially(self, fxt_sample_video): + video = Video(fxt_sample_video) on_exit_do(video.close) - assert None == video._frame_count - for idx, frame in enumerate(video): - assert frame.size == video.frame_size - assert frame.index == idx - assert frame.video is video - assert np.array_equal(frame.data, np.ones((*video.frame_size, 3)) * idx) - - assert 4 == video._frame_count for idx, frame in enumerate(video): assert frame.size == video.frame_size assert frame.index == idx assert frame.video is video assert np.array_equal(frame.data, np.ones((*video.frame_size, 3)) * idx) - with pytest.raises(IndexError): - video.get_frame_data(idx + 1) - @mark_requirement(Requirements.DATUM_GENERAL_REQ) @scoped - def test_can_read_frames_randomly(self, test_dir): - video_path = _make_sample_video(test_dir) - video = Video(video_path) + def test_can_read_frames_randomly(self, fxt_sample_video): + video = Video(fxt_sample_video) on_exit_do(video.close) for idx in {1, 3, 2, 0, 3}: @@ -76,14 +64,10 @@ def test_can_read_frames_randomly(self, test_dir): assert frame.index == idx assert np.array_equal(frame.data, np.ones((*video.frame_size, 3)) * idx) - with pytest.raises(IndexError): - frame = video[4] - @mark_requirement(Requirements.DATUM_GENERAL_REQ) @scoped - def test_can_skip_frames_between(self, test_dir): - video_path = _make_sample_video(test_dir) - video = Video(video_path, step=2) + def test_can_skip_frames_between(self, fxt_sample_video): + video = Video(fxt_sample_video, step=2) on_exit_do(video.close) for idx, frame in enumerate(video): @@ -91,53 +75,29 @@ def test_can_skip_frames_between(self, test_dir): @mark_requirement(Requirements.DATUM_GENERAL_REQ) @scoped - def test_can_skip_from_start(self, test_dir): - video_path = _make_sample_video(test_dir) - video = Video(video_path, start_frame=1) + def test_can_skip_from_start(self, fxt_sample_video): + video = Video(fxt_sample_video, start_frame=1) on_exit_do(video.close) assert 1 == next(iter(video)).index @mark_requirement(Requirements.DATUM_GENERAL_REQ) @scoped - def test_can_skip_from_end(self, test_dir): - video_path = _make_sample_video(test_dir) - video = Video(video_path, end_frame=2) + def test_can_skip_from_end(self, fxt_sample_video): + video = Video(fxt_sample_video, end_frame=2) on_exit_do(video.close) last_frame = None for last_frame in video: pass - assert 3 == video.length - assert 2 == last_frame.index + assert 2 == video.length + assert 1 == last_frame.index @mark_requirement(Requirements.DATUM_GENERAL_REQ) @scoped - def test_check_invalid_length(self, test_dir): - video_path = _make_sample_video(test_dir) - video = Video(video_path, step=2, start_frame=1, end_frame=1) - on_exit_do(video.close) - - with pytest.raises(ValueError): - video.length - - @mark_requirement(Requirements.DATUM_GENERAL_REQ) - @scoped - def test_check_invalid_end_frame(self, test_dir): - video_path = _make_sample_video(test_dir) - video = Video(video_path, end_frame=4) - on_exit_do(video.close) - - with pytest.raises(ValueError): - for _ in video: - pass - - @mark_requirement(Requirements.DATUM_GENERAL_REQ) - @scoped - def test_can_init_frame_count_lazily(self, test_dir): - video_path = _make_sample_video(test_dir) - video = Video(video_path) + def test_can_init_frame_count_lazily(self, fxt_sample_video): + video = Video(fxt_sample_video) on_exit_do(video.close) assert None is video.length @@ -149,35 +109,18 @@ def test_can_init_frame_count_lazily(self, test_dir): @mark_requirement(Requirements.DATUM_GENERAL_REQ) @scoped - def test_can_open_lazily(self, test_dir): - video = Video(osp.join(test_dir, "path.mp4")) - - assert osp.join(test_dir, "path.mp4") == video.path - assert ".mp4" == video.ext - - @mark_requirement(Requirements.DATUM_GENERAL_REQ) - @scoped - def test_can_compare(self, test_dir): - video_path1 = _make_sample_video(test_dir) - video1 = Video(video_path1) - assert video1 != Video(video_path1, step=2) - assert video1 != Video(video_path1, start_frame=1) - assert video1 != Video(video_path1, end_frame=2) - assert video1 == Video(video_path1, end_frame=3) - - video_path2 = _make_sample_video(test_dir, fname="video2.avi") - assert video1 == Video(video_path2) + def test_can_open_lazily(self): + with TestDir() as test_dir: + video = Video(osp.join(test_dir, "path.mp4")) - video_path3 = _make_sample_video(test_dir, fname="video3.avi", frames=6) - assert video1 != Video(video_path3) - assert Video(video_path3, end_frame=3) == video1 + assert osp.join(test_dir, "path.mp4") == video.path + assert ".mp4" == video.ext class VideoExtractorTest: @mark_requirement(Requirements.DATUM_GENERAL_REQ) @scoped - def test_can_read_frames(self, test_dir): - video_path = _make_sample_video(test_dir) + def test_can_read_frames(self, fxt_sample_video): expected = Dataset.from_iterable( [ DatasetItem( @@ -190,16 +133,15 @@ def test_can_read_frames(self, test_dir): ) actual = Dataset.import_from( - video_path, "video_frames", subset="train", name_pattern="frame_%03d" + fxt_sample_video, "video_frames", subset="train", name_pattern="frame_%03d" ) compare_datasets(TestCase(), expected, actual) @mark_requirement(Requirements.DATUM_GENERAL_REQ) @scoped - def test_can_split_and_load(self, test_dir): - video_path = _make_sample_video(test_dir) - dataset_dir = Scope().add(TestDir()) + def test_can_split_and_load(self, fxt_sample_video): + test_dir = scope_add(TestDir()) expected = Dataset.from_iterable( [ @@ -209,65 +151,62 @@ def test_can_split_and_load(self, test_dir): ) dataset = Dataset.import_from( - video_path, "video_frames", start_frame=0, end_frame=3, name_pattern="frame_%06d" + fxt_sample_video, "video_frames", start_frame=0, end_frame=4, name_pattern="frame_%06d" ) - dataset.export(format="image_dir", save_dir=dataset_dir, image_ext=".jpg") + dataset.export(format="image_dir", save_dir=test_dir, image_ext=".jpg") - actual = Dataset.import_from(dataset_dir, "image_dir") + actual = Dataset.import_from(test_dir, "image_dir") compare_datasets(TestCase(), expected, actual) class ProjectTest: @mark_requirement(Requirements.DATUM_GENERAL_REQ) - def test_can_release_resources_on_exit(self, test_dir): - video_path = _make_sample_video(test_dir) + def test_can_release_resources_on_exit(self, fxt_sample_video): with Scope() as scope: - project_dir = scope.add(TestDir()) + test_dir = scope.add(TestDir()) - project = scope.add(Project.init(project_dir)) + project = scope.add(Project.init(test_dir)) project.import_source( "src", - osp.dirname(video_path), + osp.dirname(fxt_sample_video), "video_frames", - rpath=osp.basename(video_path), + rpath=osp.basename(fxt_sample_video), ) assert len(project.working_tree.make_dataset()) == 4 - assert not osp.exists(project_dir) + assert not osp.exists(test_dir) @mark_requirement(Requirements.DATUM_GENERAL_REQ) @scoped - def test_can_release_resources_on_remove(self, test_dir): - video_path = _make_sample_video(test_dir) - project_dir = scope_add(TestDir()) + def test_can_release_resources_on_remove(self, fxt_sample_video): + test_dir = scope_add(TestDir()) - project = scope_add(Project.init(project_dir)) + project = scope_add(Project.init(test_dir)) project.import_source( "src", - osp.dirname(video_path), + osp.dirname(fxt_sample_video), "video_frames", - rpath=osp.basename(video_path), + rpath=osp.basename(fxt_sample_video), ) project.commit("commit 1") assert len(project.working_tree.make_dataset()) == 4 - assert osp.isdir(osp.join(project_dir, "src")) + assert osp.isdir(osp.join(test_dir, "src")) project.remove_source("src", keep_data=False) - assert not osp.exists(osp.join(project_dir, "src")) + assert not osp.exists(osp.join(test_dir, "src")) @mark_requirement(Requirements.DATUM_GENERAL_REQ) @scoped - def test_can_release_resources_on_checkout(self, test_dir): - video_path = _make_sample_video(test_dir) - project_dir = scope_add(TestDir()) + def test_can_release_resources_on_checkout(self, fxt_sample_video): + test_dir = scope_add(TestDir()) - project = scope_add(Project.init(project_dir)) + project = scope_add(Project.init(test_dir)) - src_url = osp.join(project_dir, "src") + src_url = osp.join(test_dir, "src") src = Dataset.from_iterable( [ DatasetItem(1), @@ -282,14 +221,14 @@ def test_can_release_resources_on_checkout(self, test_dir): project.import_source( "src", - osp.dirname(video_path), + osp.dirname(fxt_sample_video), "video_frames", - rpath=osp.basename(video_path), + rpath=osp.basename(fxt_sample_video), ) project.commit("commit 2") assert len(project.working_tree.make_dataset()) == 4 - assert osp.isdir(osp.join(project_dir, "src")) + assert osp.isdir(osp.join(test_dir, "src")) project.checkout("HEAD~1") @@ -301,9 +240,8 @@ class VideoAnnotationTest: @mark_requirement(Requirements.DATUM_GENERAL_REQ) @pytest.mark.parametrize("dataset_format", ["datumaro", "datumaro_binary"]) @scoped - def test_can_video_annotation_export(self, dataset_format, test_dir): - video_path = _make_sample_video(test_dir) - video = Video(video_path) + def test_can_video_annotation_export(self, dataset_format, fxt_sample_video): + video = Video(fxt_sample_video) on_exit_do(video.close) expected = Dataset.from_iterable( @@ -327,9 +265,8 @@ def test_can_video_annotation_export(self, dataset_format, test_dir): @mark_requirement(Requirements.DATUM_GENERAL_REQ) @scoped - def test_can_save_video(self, test_dir): - video_path = _make_sample_video(test_dir) - video = Video(video_path) + def test_can_save_video(self, fxt_sample_video): + video = Video(fxt_sample_video) on_exit_do(video.close) with TestDir() as test_dir: diff --git a/tests/unit/test_widerface_format.py b/tests/unit/test_widerface_format.py index 4e0edda613..caf947a5ea 100644 --- a/tests/unit/test_widerface_format.py +++ b/tests/unit/test_widerface_format.py @@ -1,7 +1,3 @@ -# Copyright (C) 2019-2024 Intel Corporation -# -# SPDX-License-Identifier: MIT - import os import os.path as osp from unittest import TestCase diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py index 64b600d1f8..9f0cd9e836 100644 --- a/tests/utils/test_utils.py +++ b/tests/utils/test_utils.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2024 Intel Corporation +# Copyright (C) 2019-2023 Intel Corporation # # SPDX-License-Identifier: MIT @@ -21,7 +21,7 @@ from datumaro.components.annotation import AnnotationType from datumaro.components.dataset import Dataset, StreamDataset from datumaro.components.dataset_base import IDataset -from datumaro.components.media import Image, MultiframeImage, PointCloud, Video, VideoFrame +from datumaro.components.media import Image, MultiframeImage, PointCloud, VideoFrame from datumaro.util import filter_dict, find from datumaro.util.os_util import rmfile, rmtree @@ -204,8 +204,6 @@ def compare_datasets( elif isinstance(item_a.media, PointCloud): test.assertEqual(item_a.media.data, item_b.media.data, item_a.id) test.assertEqual(item_a.media.extra_images, item_b.media.extra_images, item_a.id) - elif isinstance(item_a.media, Video): - test.assertEqual(item_a.media, item_b.media, item_a.id) elif isinstance(item_a.media, VideoFrame): test.assertEqual(item_a.media, item_b.media, item_a.id) test.assertEqual(item_a.index, item_b.index, item_a.id) @@ -325,28 +323,27 @@ def check_save_and_load( def _change_path_in_items(dataset, source_path, target_path): for item in dataset: - if item.media: - if hasattr(item.media, "path") and item.media.path: - path = item.media.path.replace(source_path, target_path) - item.media = item.media.from_self(path=path) - if isinstance(item.media, PointCloud): - new_images = [] - for image in item.media.extra_images: - if hasattr(image, "path"): - path = image._path - new_images.append( - image.from_self(path=path.replace(source_path, target_path)) - ) - else: - new_images.append(image) - item.media._extra_images = new_images + if item.media and hasattr(item.media, "path"): + path = item.media._path + item.media = item.media.from_self(path=path.replace(source_path, target_path)) + if item.media and isinstance(item.media, PointCloud): + new_images = [] + for image in item.media.extra_images: + if hasattr(image, "path"): + path = image._path + new_images.append( + image.from_self(path=path.replace(source_path, target_path)) + ) + else: + new_images.append(image) + item.media._extra_images = new_images with TestDir() as tmp_dir: converter(source_dataset, test_dir, stream=stream) if move_save_dir: save_dir = tmp_dir for file in os.listdir(test_dir): - os.symlink(osp.join(test_dir, file), osp.join(save_dir, file)) + shutil.move(osp.join(test_dir, file), save_dir) else: save_dir = test_dir