Repository: openvinotoolkit/datumaro (python)

Issue:
datum create might delete your git workspace
I inadvertently just deleted my (dirty :scream:) git workspace - with datumaro v1.4.1:

1. Installed datumaro (omitting (erroneously?) `[default]`):
```sh
jsa@localhost FiftyOneImageAnnotations.jl % conda activate .CondaPkg/env 
(/Users/jsa/work/julia/FiftyOneImageAnnotations.jl/.CondaPkg/env) jsa@localhost FiftyOneImageAnnotations.jl % pip install datumaro
Collecting datumaro
...
Successfully installed ... datumaro-1.4.1 ...
(/Users/jsa/work/julia/FiftyOneImageAnnotations.jl/.CondaPkg/env) jsa@localhost FiftyOneImageAnnotations.jl % which datum
/Users/jsa/work/julia/FiftyOneImageAnnotations.jl/.CondaPkg/env/bin/datum
```

Git workspace intact (messy, but it's there):
```sh
(/Users/jsa/work/julia/FiftyOneImageAnnotations.jl/.CondaPkg/env) jsa@localhost FiftyOneImageAnnotations.jl % git status
On branch feature/importer
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   src/image_annotations_importer.jl

no changes added to commit (use "git add" and/or "git commit -a")
```

2. Create datumaro project (fails due to buggy install):
```sh
(/Users/jsa/work/julia/FiftyOneImageAnnotations.jl/.CondaPkg/env) jsa@localhost FiftyOneImageAnnotations.jl % datum project create
2023-08-29 14:35:10,934 INFO: Creating project at '/Users/jsa/work/julia/FiftyOneImageAnnotations.jl'
2023-08-29 14:35:11,113 ERROR: Can't import the 'git' package. Make sure GitPython is installed, or install it with 'pip install datumaro[default]'.
Traceback (most recent call last):
  File "/Users/jsa/work/julia/FiftyOneImageAnnotations.jl/.CondaPkg/env/lib/python3.11/site-packages/datumaro/components/project.py", line 998, in module
    import git
ModuleNotFoundError: No module named 'git'
...
ModuleNotFoundError: Can't import the 'git' package. Make sure GitPython is installed, or install it with 'pip install datumaro[default]'.
```

3. Git workspace gone:
```sh
(/Users/jsa/work/julia/FiftyOneImageAnnotations.jl/.CondaPkg/env) jsa@localhost FiftyOneImageAnnotations.jl % git status
fatal: not a git repository (or any of the parent directories): .git
```


Pull request:
Raise VcsAlreadyExists error if vcs directory exists
### Summary
 - Resolve #1136
 - Currently, the `datum project create` command can remove the vcs directory such as Git which is already existed. This patch will prevent it from removing it.

### How to test
Updated unit tests for it.

### Checklist
<!-- Put an 'x' in all the boxes that apply -->
- [x] I have added unit tests to cover my changes.​
- [ ] I have added integration tests to cover my changes.​
- [x] I have added the description of my changes into [CHANGELOG](https://github.com/openvinotoolkit/datumaro/blob/develop/CHANGELOG.md).​
- [ ] I have updated the [documentation](https://github.com/openvinotoolkit/datumaro/tree/develop/docs) accordingly

### License

- [x] I submit _my code changes_ under the same [MIT License](https://github.com/openvinotoolkit/datumaro/blob/develop/LICENSE) that covers the project.
  Feel free to contact the maintainers if that's a concern.
- [x] I have updated the license header for each file (see an example below).

```python
# Copyright (C) 2023 Intel Corporation
#
# SPDX-License-Identifier: MIT
```


Patch:
diff --git a/.github/workflows/issue_assignment.yml b/.github/workflows/issue_assignment.yml
index 81c78f597..8115b4f7b 100644
--- a/.github/workflows/issue_assignment.yml
+++ b/.github/workflows/issue_assignment.yml
@@ -17,6 +17,6 @@ jobs:
         uses: pozil/auto-assign-issue@v1.11.0
         with:
           repo-token: ${{ secrets.GITHUB_TOKEN }}
-          assignees: vinnamkim,jihyeonyi,sooahleex,bonhunko
+          assignees: vinnamkim,jihyeonyi,sooahleex
           numOfAssignee: 1
           allowSelfAssign: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9136dc584..673cf2d1b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,12 +27,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   (<https://github.com/openvinotoolkit/datumaro/pull/1116>)
 - Add Rust API to optimize COCOPageMapper performance
   (<https://github.com/openvinotoolkit/datumaro/pull/1120>)
+- Remove deprecates announced to be removed in 1.5.0
+  (<https://github.com/openvinotoolkit/datumaro/pull/1140>)
 
 ### Bug fixes
 - Fix bugs for Tile transform
   (<https://github.com/openvinotoolkit/datumaro/pull/1123>)
 - Disable Roboflow Tfrecord format when Tensorflow is not installed
   (<https://github.com/openvinotoolkit/datumaro/pull/1130>)
+- Raise VcsAlreadyExists error if vcs directory exists
+  (<https://github.com/openvinotoolkit/datumaro/pull/1138>)
 
 ## 27/07/2023 - Release 1.4.1
 ### Bug fixes
diff --git a/src/datumaro/__init__.py b/src/datumaro/__init__.py
index 7113a9bd7..c8257b28b 100644
--- a/src/datumaro/__init__.py
+++ b/src/datumaro/__init__.py
@@ -56,7 +56,7 @@ from .components.exporter import Exporter, ExportErrorPolicy, FailingExportError
 from .components.hl_ops import HLOps
 from .components.importer import FailingImportErrorPolicy, Importer, ImportErrorPolicy
 from .components.launcher import Launcher
-from .components.media import ByteImage, Image, MediaElement, Video, VideoFrame
+from .components.media import Image, MediaElement, Video, VideoFrame
 from .components.media_manager import MediaManager
 from .components.progress_reporting import (
     NullProgressReporter,
diff --git a/src/datumaro/cli/commands/convert.py b/src/datumaro/cli/commands/convert.py
index d42d2447b..828e77144 100644
--- a/src/datumaro/cli/commands/convert.py
+++ b/src/datumaro/cli/commands/convert.py
@@ -39,7 +39,7 @@ def build_parser(parser_ctor=argparse.ArgumentParser):
         |n
         Examples:|n
         - Export a dataset as a PASCAL VOC dataset, include images:|n
-        |s|s%(prog)s -i src/path -f voc -- --save-images|n
+        |s|s%(prog)s -i src/path -f voc -- --save-media|n
         |n
         - Export a dataset as a COCO dataset to a specific directory:|n
         |s|s%(prog)s -i src/path -f coco -o path/I/like/
diff --git a/src/datumaro/cli/commands/patch.py b/src/datumaro/cli/commands/patch.py
index bde88aa3d..6e122a08c 100644
--- a/src/datumaro/cli/commands/patch.py
+++ b/src/datumaro/cli/commands/patch.py
@@ -25,7 +25,7 @@ def build_parser(parser_ctor=argparse.ArgumentParser):
         By default, datasets are updated in-place. The '-o/--output-dir'
         option can be used to specify another output directory. When
         updating in-place, use the '--overwrite' parameter along with the
-        '--save-images' export option (in-place updates fail by default
+        '--save-media' export option (in-place updates fail by default
         to prevent data loss).|n
         |n
         Unlike the regular project data source joining, the datasets are not
@@ -39,7 +39,7 @@ def build_parser(parser_ctor=argparse.ArgumentParser):
         The command supports passing extra exporting options for the output
         dataset. The extra options should be passed after the main arguments
         and after the '--' separator. Particularly, this is useful to include
-        images in the output dataset with '--save-images'.|n
+        images in the output dataset with '--save-media'.|n
         |n
         This command can be applied to the current project targets or
         arbitrary datasets outside a project. Note that if the target dataset
@@ -64,7 +64,7 @@ def build_parser(parser_ctor=argparse.ArgumentParser):
         |n
         Examples:|n
         - Update a VOC-like dataset with COCO-like annotations:|n
-        |s|s%(prog)s --overwrite dataset1/:voc dataset2/:coco -- --save-images|n
+        |s|s%(prog)s --overwrite dataset1/:voc dataset2/:coco -- --save-media|n
         |n
         - Generate a patched dataset, based on a project:|n
         |s|s%(prog)s -o patched_proj1/ proj1/ proj2/|n
diff --git a/src/datumaro/cli/commands/require_project/modification/export.py b/src/datumaro/cli/commands/require_project/modification/export.py
index 4a11be841..eefbb8485 100644
--- a/src/datumaro/cli/commands/require_project/modification/export.py
+++ b/src/datumaro/cli/commands/require_project/modification/export.py
@@ -35,7 +35,7 @@ def build_parser(parser_ctor=argparse.ArgumentParser):
         options, which are passed after the '--' separator (see examples),
         pass '-- -h' for more info. If not stated otherwise, by default
         only annotations are exported, to include images pass
-        '--save-images' parameter.|n
+        '--save-media' parameter.|n
         |n
         A filter can be passed, check the 'filter' command description for
         more info.|n
@@ -58,7 +58,7 @@ def build_parser(parser_ctor=argparse.ArgumentParser):
         |n
         Examples:|n
         - Export project as a VOC-like dataset, include images:|n
-        |s|s%(prog)s -f voc -- --save-images|n
+        |s|s%(prog)s -f voc -- --save-media|n
         |n
         - Export project as a COCO-like dataset in other directory:|n
         |s|s%(prog)s -f coco -o path/I/like/
diff --git a/src/datumaro/components/dataset_base.py b/src/datumaro/components/dataset_base.py
index dec766321..32f210584 100644
--- a/src/datumaro/components/dataset_base.py
+++ b/src/datumaro/components/dataset_base.py
@@ -4,17 +4,15 @@
 
 from __future__ import annotations
 
-import warnings
 from typing import Any, Dict, Iterator, List, Optional, Sequence, Type, TypeVar, Union, cast
 
 import attr
-import numpy as np
 from attr import attrs, field
 
 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, PointCloud
+from datumaro.components.media import Image, MediaElement
 from datumaro.util.attrs_util import default_if_none, not_empty
 from datumaro.util.definitions import DEFAULT_SUBSET_NAME
 
@@ -50,118 +48,11 @@ class DatasetItem:
         media: Union[str, MediaElement, None] = None,
         annotations: Optional[List[Annotation]] = None,
         attributes: Dict[str, Any] = None,
-        image=None,
-        point_cloud=None,
-        related_images=None,
     ):
-        if image is not None:
-            warnings.warn(
-                "'image' is deprecated and will be removed in future. Use 'media' instead. "
-                "It will be deprecated in datumaro==1.5.0.",
-                DeprecationWarning,
-                stacklevel=2,
-            )
-            if isinstance(image, str):
-                image = Image.from_file(path=image)
-            elif isinstance(image, np.ndarray):
-                image = Image.from_numpy(data=image)
-            elif isinstance(image, bytes) or callable(image):
-                image = Image.from_bytes(data=image)
-            assert isinstance(image, Image)
-            media = image
-        elif point_cloud is not None:
-            warnings.warn(
-                "'point_cloud' is deprecated and will be "
-                "removed in future. Use 'media' instead. "
-                "It will be deprecated in datumaro==1.5.0.",
-                DeprecationWarning,
-                stacklevel=2,
-            )
-            if related_images is not None:
-                warnings.warn(
-                    "'related_images' is deprecated and will be "
-                    "removed in future. Use 'media' instead. "
-                    "It will be deprecated in datumaro==1.5.0.",
-                    DeprecationWarning,
-                    stacklevel=2,
-                )
-            if isinstance(point_cloud, str):
-                point_cloud = PointCloud.from_file(path=point_cloud, extra_images=related_images)
-            elif isinstance(point_cloud, bytes):
-                point_cloud = PointCloud.from_bytes(data=point_cloud, extra_images=related_images)
-            assert isinstance(point_cloud, PointCloud)
-            media = point_cloud
-
         self.__attrs_init__(
             id=id, subset=subset, media=media, annotations=annotations, attributes=attributes
         )
 
-    # Deprecated. Provided for backward compatibility.
-    @property
-    def image(self) -> Optional[Image]:
-        warnings.warn(
-            "'DatasetItem.image' is deprecated and will be "
-            "removed in future. Use '.media' and '.media_as()' instead. "
-            "It will be deprecated in datumaro==1.5.0.",
-            DeprecationWarning,
-            stacklevel=2,
-        )
-        if not isinstance(self.media, Image):
-            return None
-        return self.media_as(Image)
-
-    # Deprecated. Provided for backward compatibility.
-    @property
-    def point_cloud(self) -> Optional[str]:
-        warnings.warn(
-            "'DatasetItem.point_cloud' is deprecated and will be "
-            "removed in future. Use '.media' and '.media_as()' instead. "
-            "It will be deprecated in datumaro==1.5.0.",
-            DeprecationWarning,
-            stacklevel=2,
-        )
-        if not isinstance(self.media, PointCloud):
-            return None
-        return getattr(self.media_as(PointCloud), "path", None)
-
-    # Deprecated. Provided for backward compatibility.
-    @property
-    def related_images(self) -> List[Image]:
-        warnings.warn(
-            "'DatasetItem.related_images' is deprecated and will be "
-            "removed in future. Use '.media' and '.media_as()' instead. "
-            "It will be deprecated in datumaro==1.5.0.",
-            DeprecationWarning,
-            stacklevel=2,
-        )
-        if not isinstance(self.media, PointCloud):
-            return []
-        return self.media_as(PointCloud).extra_images
-
-    # Deprecated. Provided for backward compatibility.
-    @property
-    def has_image(self):
-        warnings.warn(
-            "'DatasetItem.has_image' is deprecated and will be "
-            "removed in future. Use '.media' and '.media_as()' instead. "
-            "It will be deprecated in datumaro==1.5.0.",
-            DeprecationWarning,
-            stacklevel=2,
-        )
-        return isinstance(self.media, Image)
-
-    # Deprecated. Provided for backward compatibility.
-    @property
-    def has_point_cloud(self):
-        warnings.warn(
-            "'DatasetItem.has_point_cloud' is deprecated and will be "
-            "removed in future. Use '.media' and '.media_as()' instead. "
-            "It will be deprecated in datumaro==1.5.0.",
-            DeprecationWarning,
-            stacklevel=2,
-        )
-        return isinstance(self.media, PointCloud)
-
 
 DatasetInfo = Dict[str, Any]
 CategoriesInfo = Dict[AnnotationType, Categories]
diff --git a/src/datumaro/components/errors.py b/src/datumaro/components/errors.py
index 902dc5c6d..331e7c627 100644
--- a/src/datumaro/components/errors.py
+++ b/src/datumaro/components/errors.py
@@ -142,6 +142,14 @@ class ProjectAlreadyExists(DatumaroError):
         return f"Can't create project: a project already exists " f"at '{self.path}'"
 
 
+@define(auto_exc=False)
+class VcsAlreadyExists(DatumaroError):
+    path = field()
+
+    def __str__(self):
+        return f"Can't create project: a version control system already exists " f"at '{self.path}'"
+
+
 @define(auto_exc=False)
 class UnknownSourceError(DatumaroError):
     name = field()
diff --git a/src/datumaro/components/exporter.py b/src/datumaro/components/exporter.py
index 62fa659f8..47f91af35 100644
--- a/src/datumaro/components/exporter.py
+++ b/src/datumaro/components/exporter.py
@@ -6,7 +6,6 @@ import logging as log
 import os
 import os.path as osp
 import shutil
-import warnings
 from tempfile import mkdtemp
 from typing import NoReturn, Optional, Tuple, TypeVar, Union
 
@@ -100,18 +99,9 @@ class Exporter(CliPlugin):
     def build_cmdline_parser(cls, **kwargs):
         parser = super().build_cmdline_parser(**kwargs)
 
-        # Deprecated
-        parser.add_argument(
-            "--save-images",
-            action="store_true",
-            default=None,
-            help="(Deprecated. Use --save-media instead) " "Save images (default: False)",
-        )
-
         parser.add_argument(
             "--save-media",
             action="store_true",
-            default=None,  # TODO: remove default once save-images is removed
             help="Save media (default: False)",
         )
         parser.add_argument(
@@ -176,7 +166,6 @@ class Exporter(CliPlugin):
         extractor: IDataset,
         save_dir: str,
         *,
-        save_images=None,  # Deprecated
         save_media: Optional[bool] = None,
         image_ext: Optional[str] = None,
         default_image_ext: Optional[str] = None,
@@ -189,23 +178,7 @@ class Exporter(CliPlugin):
         assert default_image_ext
         self._default_image_ext = default_image_ext
 
-        if save_images is not None and save_media is not None:
-            raise DatasetExportError("Can't use both 'save-media' and " "'save-images'")
-
-        if save_media is not None:
-            self._save_media = save_media
-        elif save_images is not None:
-            self._save_media = save_images
-            warnings.warn(
-                "'save-images' is deprecated and will be "
-                "removed in future. Use 'save-media' instead. "
-                "It will be deprecated in datumaro==1.5.0.",
-                DeprecationWarning,
-                stacklevel=2,
-            )
-        else:
-            self._save_media = False
-
+        self._save_media = save_media
         self._image_ext = image_ext
 
         self._extractor = extractor
diff --git a/src/datumaro/components/media.py b/src/datumaro/components/media.py
index 94d0a0b0e..dbfca986b 100644
--- a/src/datumaro/components/media.py
+++ b/src/datumaro/components/media.py
@@ -9,7 +9,6 @@ import io
 import os
 import os.path as osp
 import shutil
-import warnings
 import weakref
 from copy import deepcopy
 from enum import IntEnum
@@ -67,8 +66,6 @@ class MediaType(IntEnum):
             return MediaElement
         if self == self.IMAGE:
             return Image
-        if self == self.BYTE_IMAGE:
-            return ByteImage
         if self == self.VIDEO_FRAME:
             return VideoFrame
         if self == self.VIDEO:
@@ -438,97 +435,6 @@ class ImageFromBytes(ImageFromData):
         return data
 
 
-class ByteImage(ImageFromBytes):
-    _type = MediaType.BYTE_IMAGE
-
-    _FORMAT_MAGICS = (
-        (b"\x89PNG\r\n\x1a\n", ".png"),
-        (b"\xff\xd8\xff", ".jpg"),
-        (b"BM", ".bmp"),
-    )
-
-    def __init__(
-        self,
-        data: Union[bytes, Callable[[str], bytes], None] = None,
-        *,
-        path: Optional[str] = None,
-        ext: Optional[str] = None,
-        size: Optional[Tuple[int, int]] = None,
-        crypter: Crypter = NULL_CRYPTER,
-    ):
-        warnings.warn(
-            f"Using {self.__class__.__name__} is deprecated. "
-            "Please use 'Image.from_bytes()' instead. "
-            "It will be deprecated in datumaro==1.5.0.",
-            DeprecationWarning,
-            stacklevel=2,
-        )
-
-        if not isinstance(data, bytes):
-            assert path or callable(data), "Image can not be empty"
-            assert data is None or callable(data)
-            if path and osp.isfile(path) or data:
-                data = lazy_image(path, loader=data)
-
-        self._bytes_data = data
-
-        if ext is None:
-            if path:
-                ext = osp.splitext(osp.basename(path))[1]
-            elif isinstance(data, bytes):
-                ext = self._guess_ext(data)
-
-        super().__init__(ext=ext, size=size, data=lambda: decode_image(self.get_bytes()))
-        if data is None:
-            # We don't expect decoder to produce images from nothing,
-            # otherwise using this class makes no sense. We undefine
-            # data to avoid using default image loader for loading binaries
-            # from the path, when no data is provided.
-            self._data = None
-
-        # TODO: do we need this replace?
-        self._path = path.replace("\\", "/") if path else ""
-        self._crypter = crypter
-
-    @property
-    def path(self) -> str:
-        return self._path
-
-    @classmethod
-    def _guess_ext(cls, data: bytes) -> Optional[str]:
-        return next(
-            (ext for magic, ext in cls._FORMAT_MAGICS if data.startswith(magic)),
-            None,
-        )
-
-    def get_bytes(self):
-        if callable(self._bytes_data):
-            return self._bytes_data()
-        return self._bytes_data
-
-    def save(self, path, crypter: Crypter = NULL_CRYPTER):
-        if not crypter.is_null_crypter:
-            raise NotImplementedError(
-                f"{self.__class__.__name__} does not implement save() with non NullCrypter."
-            )
-
-        cur_path = osp.abspath(self.path)
-        path = osp.abspath(path)
-
-        cur_ext = self.ext.lower()
-        new_ext = osp.splitext(osp.basename(path))[1].lower()
-
-        os.makedirs(osp.dirname(path), exist_ok=True)
-        if cur_ext == new_ext and osp.isfile(cur_path):
-            if cur_path != path:
-                shutil.copyfile(cur_path, path)
-        elif cur_ext == new_ext:
-            with open(path, "wb") as f:
-                f.write(self.get_bytes())
-        else:
-            save_image(path, self.data)
-
-
 class VideoFrame(ImageFromNumpy):
     _type = MediaType.VIDEO_FRAME
 
diff --git a/src/datumaro/components/merge/base.py b/src/datumaro/components/merge/base.py
index b05aee248..416a0157c 100644
--- a/src/datumaro/components/merge/base.py
+++ b/src/datumaro/components/merge/base.py
@@ -67,7 +67,7 @@ class Merger(IMergerContext, CliPlugin):
                     media_type, s.media_type()
                 ):
                     # Symmetric comparision is needed in the case of subclasses:
-                    # eg. Image and ByteImage
+                    # eg. Image and RoIImage
                     raise MediaTypeError("Datasets have different media types")
             return media_type
 
diff --git a/src/datumaro/components/project.py b/src/datumaro/components/project.py
index e39ae4027..4fde2bc9e 100644
--- a/src/datumaro/components/project.py
+++ b/src/datumaro/components/project.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2019-2022 Intel Corporation
+# Copyright (C) 2019-2023 Intel Corporation
 #
 # SPDX-License-Identifier: MIT
 
@@ -70,6 +70,7 @@ from datumaro.components.errors import (
     UnknownStageError,
     UnknownTargetError,
     UnsavedChangesError,
+    VcsAlreadyExists,
     VcsError,
 )
 from datumaro.components.launcher import Launcher
@@ -1787,8 +1788,16 @@ class Project:
         os.makedirs(osp.join(path, ProjectLayout.cache_dir))
         os.makedirs(osp.join(path, ProjectLayout.tmp_dir))
 
-        on_error_do(rmtree, osp.join(project_dir, ".git"), ignore_errors=True)
-        on_error_do(rmtree, osp.join(project_dir, ".dvc"), ignore_errors=True)
+        git_dir, dvc_dir = osp.join(project_dir, ".git"), osp.join(project_dir, ".dvc")
+
+        if osp.exists(git_dir):
+            raise VcsAlreadyExists(git_dir)
+        if osp.exists(dvc_dir):
+            raise VcsAlreadyExists(dvc_dir)
+
+        on_error_do(rmtree, git_dir, ignore_errors=True)
+        on_error_do(rmtree, dvc_dir, ignore_errors=True)
+
         project = Project(path)
         project._init_vcs()
 
diff --git a/src/datumaro/plugins/data_formats/arrow/exporter.py b/src/datumaro/plugins/data_formats/arrow/exporter.py
index ce9a47b8e..d0fb1e09a 100644
--- a/src/datumaro/plugins/data_formats/arrow/exporter.py
+++ b/src/datumaro/plugins/data_formats/arrow/exporter.py
@@ -365,7 +365,6 @@ class ArrowExporter(Exporter):
         extractor: IDataset,
         save_dir: str,
         *,
-        save_images=None,  # Deprecated
         save_media: Optional[bool] = None,
         image_ext: Optional[Union[str, Callable[[str], bytes]]] = None,
         default_image_ext: Optional[str] = None,
@@ -380,7 +379,6 @@ class ArrowExporter(Exporter):
         super().__init__(
             extractor=extractor,
             save_dir=save_dir,
-            save_images=save_images,
             save_media=save_media,
             image_ext=image_ext,
             default_image_ext=default_image_ext,
diff --git a/src/datumaro/plugins/data_formats/datumaro_binary/exporter.py b/src/datumaro/plugins/data_formats/datumaro_binary/exporter.py
index 52cf41067..a1c86d506 100644
--- a/src/datumaro/plugins/data_formats/datumaro_binary/exporter.py
+++ b/src/datumaro/plugins/data_formats/datumaro_binary/exporter.py
@@ -238,7 +238,6 @@ class DatumaroBinaryExporter(DatumaroExporter):
         extractor: IDataset,
         save_dir: str,
         *,
-        save_images=None,
         save_media: Optional[bool] = None,
         image_ext: Optional[str] = None,
         default_image_ext: Optional[str] = None,
@@ -289,7 +288,6 @@ class DatumaroBinaryExporter(DatumaroExporter):
         super().__init__(
             extractor,
             save_dir,
-            save_images=save_images,
             save_media=save_media,
             image_ext=image_ext,
             default_image_ext=default_image_ext,
diff --git a/src/datumaro/plugins/data_formats/tf_detection_api/exporter.py b/src/datumaro/plugins/data_formats/tf_detection_api/exporter.py
index fa48f49bf..b04a57e17 100644
--- a/src/datumaro/plugins/data_formats/tf_detection_api/exporter.py
+++ b/src/datumaro/plugins/data_formats/tf_detection_api/exporter.py
@@ -14,7 +14,7 @@ from datumaro.components.annotation import AnnotationType, LabelCategories
 from datumaro.components.errors import DatasetExportError
 from datumaro.components.exporter import Exporter
 from datumaro.components.lazy_plugin import extra_deps
-from datumaro.components.media import ByteImage, Image, ImageFromBytes
+from datumaro.components.media import Image, ImageFromBytes
 from datumaro.util.annotation_util import find_group_leader, find_instances, max_bbox
 from datumaro.util.image import encode_image
 from datumaro.util.mask_tools import merge_masks
@@ -225,9 +225,7 @@ class TfDetectionApiExporter(Exporter):
 
         buffer = None
         if src_ext == dst_ext:
-            if isinstance(item.media, ByteImage):
-                buffer = item.media.get_bytes()
-            elif isinstance(item.media, ImageFromBytes):
+            if isinstance(item.media, ImageFromBytes):
                 buffer = item.media.bytes
         if buffer is None:
             buffer = encode_image(item.media.data, dst_ext)
diff --git a/src/datumaro/util/image.py b/src/datumaro/util/image.py
index 76e82fe9f..a698b1549 100644
--- a/src/datumaro/util/image.py
+++ b/src/datumaro/util/image.py
@@ -6,7 +6,6 @@ import importlib
 import os
 import os.path as osp
 import shlex
-import warnings
 import weakref
 from contextlib import contextmanager
 from enum import Enum, auto
@@ -45,22 +44,6 @@ from datumaro.util.image_cache import ImageCache
 from datumaro.util.os_util import find_files
 
 
-def __getattr__(name: str):
-    if name in {"Image", "ByteImage"}:
-        warnings.warn(
-            f"Using {name} from 'util.image' is deprecated, "
-            "the class is moved to 'components.media' "
-            "It will be deprecated in datumaro==1.5.0.",
-            DeprecationWarning,
-            stacklevel=2,
-        )
-
-        import datumaro.components.media as media_module
-
-        return getattr(media_module, name)
-    raise AttributeError(f"module {__name__} has no attribute {name}")
-
-
 def load_image(path: str, dtype: DTypeLike = np.uint8, crypter: Crypter = NULL_CRYPTER):
     """
     Reads an image in the HWC Grayscale/BGR(A) [0; 255] format (default dtype is uint8).


Test patch:
diff --git a/tests/integration/cli/test_generator.py b/tests/integration/cli/test_generator.py
index ba59bff08..3788b7496 100644
--- a/tests/integration/cli/test_generator.py
+++ b/tests/integration/cli/test_generator.py
@@ -2,6 +2,8 @@ import os
 import os.path as osp
 from unittest import TestCase
 
+import pytest
+
 import datumaro.util.image as image_module
 
 from ...requirements import Requirements, mark_requirement
@@ -10,6 +12,7 @@ from tests.utils.test_utils import TestDir
 from tests.utils.test_utils import run_datum as run
 
 
+@pytest.mark.xfail(reason="Cannot download the model file from the source")
 class ImageGeneratorTest(TestCase):
     def check_images_shape(self, img_dir, expected_shape):
         exp_h, exp_w, exp_c = expected_shape
diff --git a/tests/integration/cli/test_kitti_raw_format.py b/tests/integration/cli/test_kitti_raw_format.py
index 9c8f8b763..884cc0270 100644
--- a/tests/integration/cli/test_kitti_raw_format.py
+++ b/tests/integration/cli/test_kitti_raw_format.py
@@ -130,7 +130,7 @@ class KittiRawIntegrationScenarios(TestCase):
                 "-o",
                 export_dir,
                 "--",
-                "--save-images",
+                "--save-media",
             )
 
             parsed_dataset = Dataset.import_from(export_dir, format="sly_pointcloud")
diff --git a/tests/integration/cli/test_patch.py b/tests/integration/cli/test_patch.py
index 6a9ae3803..fbe35cc21 100644
--- a/tests/integration/cli/test_patch.py
+++ b/tests/integration/cli/test_patch.py
@@ -102,7 +102,7 @@ class PatchTest(TestCase):
                 patch_url + ":voc",
                 "--",
                 "--reindex=1",
-                "--save-images",
+                "--save-media",
             )
 
             compare_datasets(
diff --git a/tests/integration/cli/test_project.py b/tests/integration/cli/test_project.py
index a01090644..054a5c676 100644
--- a/tests/integration/cli/test_project.py
+++ b/tests/integration/cli/test_project.py
@@ -41,7 +41,7 @@ class ProjectIntegrationScenarios(TestCase):
                 "-o",
                 result_dir,
                 "--",
-                "--save-images",
+                "--save-media",
                 "--reindex",
                 "1",
             )
@@ -72,7 +72,7 @@ class ProjectIntegrationScenarios(TestCase):
                 "-o",
                 result_dir,
                 "--",
-                "--save-images",
+                "--save-media",
             )
 
             self.assertTrue(osp.isdir(result_dir))
@@ -183,7 +183,7 @@ class ProjectIntegrationScenarios(TestCase):
                 result_dir,
                 "source-1",
                 "--",
-                "--save-images",
+                "--save-media",
             )
             parsed = Dataset.import_from(result_dir, "coco")
             compare_datasets(
@@ -227,7 +227,7 @@ class ProjectIntegrationScenarios(TestCase):
                 "-o",
                 result_dir,
                 "--",
-                "--save-images",
+                "--save-media",
             )
             parsed = Dataset.import_from(result_dir, "coco")
             compare_datasets(
diff --git a/tests/integration/cli/test_sly_point_cloud_format.py b/tests/integration/cli/test_sly_point_cloud_format.py
index 6431656f9..9cd02b852 100644
--- a/tests/integration/cli/test_sly_point_cloud_format.py
+++ b/tests/integration/cli/test_sly_point_cloud_format.py
@@ -102,7 +102,7 @@ class SlyPointCloudIntegrationScenarios(TestCase):
                 "-o",
                 export_dir,
                 "--",
-                "--save-images",
+                "--save-media",
                 "--allow-attrs",
             )
 
diff --git a/tests/integration/cli/test_utils.py b/tests/integration/cli/test_utils.py
index 65be5ed5f..b6097f229 100644
--- a/tests/integration/cli/test_utils.py
+++ b/tests/integration/cli/test_utils.py
@@ -2,6 +2,8 @@ import os
 import os.path as osp
 from unittest.case import TestCase
 
+import pytest
+
 from datumaro.components.media_manager import MediaManager
 from datumaro.util.scope import on_exit_do, scope_add, scoped
 
@@ -12,6 +14,7 @@ from tests.utils.test_utils import TestDir
 from tests.utils.test_utils import run_datum as run
 
 
+@pytest.mark.xfail(reason="This test is flaky so that can fail randomly")
 class VideoSplittingTest:
     @mark_requirement(Requirements.DATUM_GENERAL_REQ)
     @scoped
diff --git a/tests/integration/cli/test_video.py b/tests/integration/cli/test_video.py
index 3e93d2808..05ce77a38 100644
--- a/tests/integration/cli/test_video.py
+++ b/tests/integration/cli/test_video.py
@@ -180,7 +180,7 @@ class VideoTest(TestCase):
                 "-o",
                 result_dir,
                 "--",
-                "--save-images",
+                "--save-media",
             )
             actual = Dataset.import_from(result_dir, "datumaro")
 
diff --git a/tests/integration/cli/test_voc_format.py b/tests/integration/cli/test_voc_format.py
index 603330564..58adcebda 100644
--- a/tests/integration/cli/test_voc_format.py
+++ b/tests/integration/cli/test_voc_format.py
@@ -46,7 +46,7 @@ class VocIntegrationScenarios(TestCase):
         )
 
         result_dir = osp.join(project_path, "result")
-        extra_args = ["--", "--save-images"]
+        extra_args = ["--", "--save-media"]
         if label_map:
             extra_args += ["--label-map", label_map]
         run(
@@ -231,7 +231,7 @@ class VocIntegrationScenarios(TestCase):
                 "-o",
                 voc_export,
                 "--",
-                "--save-images",
+                "--save-media",
             )
 
             parsed_dataset = Dataset.import_from(voc_export, format="voc")
@@ -302,7 +302,7 @@ class VocIntegrationScenarios(TestCase):
                 "-o",
                 voc_dir,
                 "--",
-                "--save-images",
+                "--save-media",
             )
 
             target_dataset = Dataset.import_from(voc_dir, format="voc")
@@ -358,7 +358,7 @@ class VocIntegrationScenarios(TestCase):
                 "-o",
                 imagenet_dir,
                 "--",
-                "--save-image",
+                "--save-media",
             )
 
             target_dataset = Dataset.import_from(imagenet_dir, format="imagenet")
diff --git a/tests/integration/cli/test_yolo_format.py b/tests/integration/cli/test_yolo_format.py
index 21b898d9d..f815ab67e 100644
--- a/tests/integration/cli/test_yolo_format.py
+++ b/tests/integration/cli/test_yolo_format.py
@@ -197,7 +197,7 @@ class YoloIntegrationScenarios(TestCase):
                 "-f",
                 "yolo",
                 "--",
-                "--save-image",
+                "--save-media",
             )
 
             parsed_dataset = Dataset.import_from(export_dir, format="yolo")
diff --git a/tests/unit/data_formats/test_yolo_strict_format.py b/tests/unit/data_formats/test_yolo_strict_format.py
index 94f9327c0..f892a3c56 100644
--- a/tests/unit/data_formats/test_yolo_strict_format.py
+++ b/tests/unit/data_formats/test_yolo_strict_format.py
@@ -402,7 +402,7 @@ class YoloStrictBaseTest:
             ],
             categories=["test"],
         )
-        dataset.export(test_dir, "yolo", save_images=True)
+        dataset.export(test_dir, "yolo", save_media=True)
 
         return dataset
 
diff --git a/tests/unit/test_fractal_image_generator.py b/tests/unit/test_fractal_image_generator.py
index 4889877b2..d078c9389 100644
--- a/tests/unit/test_fractal_image_generator.py
+++ b/tests/unit/test_fractal_image_generator.py
@@ -3,6 +3,7 @@ import os.path as osp
 from unittest import TestCase
 
 import numpy as np
+import pytest
 
 from datumaro.plugins.synthetic_data import FractalImageGenerator
 from datumaro.util.image import load_image
@@ -13,6 +14,7 @@ from tests.utils.assets import get_test_asset_path
 from tests.utils.test_utils import TestDir
 
 
+@pytest.mark.xfail(reason="Cannot download the model file from the source")
 class FractalImageGeneratorTest(TestCase):
     @mark_requirement(Requirements.DATUM_677)
     def test_save_image_can_create_dir(self):
diff --git a/tests/unit/test_images.py b/tests/unit/test_images.py
index b8a0964fb..bb4a56b5b 100644
--- a/tests/unit/test_images.py
+++ b/tests/unit/test_images.py
@@ -5,7 +5,7 @@ from unittest import TestCase
 
 import numpy as np
 
-from datumaro.components.media import ByteImage, Image, RoIImage
+from datumaro.components.media import Image, RoIImage
 from datumaro.util.image import (
     encode_image,
     lazy_image,
@@ -194,74 +194,6 @@ class ImageTest(TestCase):
         self.assertEqual(image.ext, None)
 
 
-class BytesImageTest(TestCase):
-    @staticmethod
-    def _gen_image_and_args_list(test_dir: str) -> Tuple[np.ndarray, bytes, List[Dict[str, Any]]]:
-        path = osp.join(test_dir, "path.png")
-        image = np.ones([2, 4, 3])
-        image_bytes = encode_image(image, "png")
-
-        return (
-            image,
-            image_bytes,
-            [
-                {"data": image_bytes},
-                {"data": lambda _: image_bytes},
-                {"data": lambda _: image_bytes, "ext": ".jpg"},
-                {"data": image_bytes, "path": path},
-                {"data": image_bytes, "path": path, "size": (2, 4)},
-                {"data": image_bytes, "path": path, "size": (2, 4)},
-                {"path": path},
-                {"path": path, "size": (2, 4)},
-            ],
-        )
-
-    @mark_requirement(Requirements.DATUM_GENERAL_REQ)
-    def test_lazy_image_shape(self):
-        data = encode_image(np.ones((5, 6, 3)), "png")
-
-        image_lazy = ByteImage(data=data, size=(2, 4))
-        image_eager = ByteImage(data=data)
-
-        self.assertEqual((2, 4), image_lazy.size)
-        self.assertEqual((5, 6), image_eager.size)
-
-    @mark_requirement(Requirements.DATUM_GENERAL_REQ)
-    def test_ctors(self):
-        with TestDir() as test_dir:
-            image, image_bytes, args_list = self._gen_image_and_args_list(test_dir)
-
-            for args in args_list:
-                with self.subTest(**args):
-                    img = ByteImage(**args)
-                    # pylint: disable=pointless-statement
-                    self.assertEqual("data" in args, img.has_data)
-                    if img.has_data:
-                        np.testing.assert_array_equal(img.data, image)
-                        self.assertEqual(img.get_bytes(), image_bytes)
-                    img.size
-                    if "size" in args:
-                        self.assertEqual(img.size, args["size"])
-                    if "ext" in args or "path" in args:
-                        self.assertEqual(img.ext, args.get("ext", ".png"))
-                    # pylint: enable=pointless-statement
-
-    @mark_requirement(Requirements.DATUM_GENERAL_REQ)
-    def test_ext_detection(self):
-        image_data = np.zeros((3, 4))
-
-        for ext in (".bmp", ".jpg", ".png"):
-            with self.subTest(ext=ext):
-                image = ByteImage(data=encode_image(image_data, ext))
-                self.assertEqual(image.ext, ext)
-
-    @mark_requirement(Requirements.DATUM_GENERAL_REQ)
-    def test_ext_detection_failure(self):
-        image_bytes = b"\xff" * 10  # invalid image
-        image = ByteImage(data=image_bytes)
-        self.assertEqual(image.ext, None)
-
-
 class RoIImageTest(TestCase):
     def _test_ctors(self, img_ctor, args_list, test_dir, is_bytes=False):
         for args in args_list:
diff --git a/tests/unit/test_project.py b/tests/unit/test_project.py
index 93f4ed0ee..94702df5f 100644
--- a/tests/unit/test_project.py
+++ b/tests/unit/test_project.py
@@ -1,3 +1,7 @@
+# Copyright (C) 2019-2023 Intel Corporation
+#
+# SPDX-License-Identifier: MIT
+
 import os
 import os.path as osp
 import shutil
@@ -6,6 +10,7 @@ from typing import List, Sequence
 from unittest import TestCase
 
 import numpy as np
+import pytest
 
 from datumaro.components.annotation import Annotation, Bbox, Label
 from datumaro.components.config_model import Model, Source
@@ -26,11 +31,13 @@ from datumaro.components.errors import (
     SourceUrlInsideProjectError,
     UnexpectedUrlError,
     UnknownTargetError,
+    VcsAlreadyExists,
 )
 from datumaro.components.launcher import Launcher
 from datumaro.components.media import Image
 from datumaro.components.project import DiffStatus, Project
 from datumaro.components.transformer import ItemTransform
+from datumaro.util.os_util import find_files
 from datumaro.util.scope import scope_add, scoped
 
 from ..requirements import Requirements, mark_requirement
@@ -39,6 +46,24 @@ from tests.utils.assets import get_test_asset_path
 from tests.utils.test_utils import TestDir, compare_datasets, compare_dirs
 
 
+class ProjectNewTest:
+    @pytest.fixture(params=[".git", ".dvc"])
+    def fxt_vcs_exist_dir(self, test_dir, request):
+        vcs_dir = osp.join(test_dir, request.param)
+        os.makedirs(vcs_dir)
+        with open(osp.join(vcs_dir, "dummy.file"), "w") as fp:
+            fp.write("dummy")
+        yield test_dir
+
+    @mark_requirement(Requirements.DATUM_GENERAL_REQ)
+    def test_project_init_failed_by_vcs_already_exist(self, fxt_vcs_exist_dir):
+        with pytest.raises(VcsAlreadyExists):
+            Project.init(fxt_vcs_exist_dir)
+
+        # Assert Project.init() do not the existing vcs directory
+        assert len(list(find_files(fxt_vcs_exist_dir, ".file", recursive=True))) > 0
+
+
 class ProjectTest(TestCase):
     @mark_requirement(Requirements.DATUM_GENERAL_REQ)
     @scoped
diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py
index 761569eb7..2144d9e59 100644
--- a/tests/utils/test_utils.py
+++ b/tests/utils/test_utils.py
@@ -170,7 +170,6 @@ def compare_datasets(
     actual: IDataset,
     ignored_attrs: Union[None, Literal["*"], Collection[str]] = None,
     require_media: bool = False,
-    require_images: bool = False,
     ignore_ann_id: bool = False,
     ignore_ann_group: bool = False,
     **kwargs,
@@ -195,16 +194,6 @@ def compare_datasets(
         elif not ignored_attrs:
             test.assertEqual(item_a.attributes, item_b.attributes, item_a.id)
 
-        if require_images:
-            warnings.warn(
-                "'require_images' is deprecated and will be "
-                "removed in future. Use 'require_media' instead. "
-                "It will be deprecated in datumaro==1.5.0.",
-                DeprecationWarning,
-                stacklevel=2,
-            )
-        require_media = require_media or require_images
-
         if require_media and item_a.media and item_b.media:
             if isinstance(item_a.media, Image):
                 test.assertEqual(item_a.media, item_b.media, item_a.id)