Skip to content

Commit

Permalink
Move image classes (#538)
Browse files Browse the repository at this point in the history
* Move image classes

* Update image class uses

* Update changelog

* update imports

* Fix cache parameter

* Remove extra parameter

* Fix import

* Provide backward compat alias for DTypeLike

* Add deprecation warning

* fix imports

* Add comment

* Add message

* Refactor cache parameter

* Update media class

* Update docstrings

* Fix test

* Redesign comparisons
  • Loading branch information
Maxim Zhiltsov authored Nov 10, 2021
1 parent 16a0761 commit fe0b13a
Show file tree
Hide file tree
Showing 45 changed files with 302 additions and 227 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
(<https://github.com/openvinotoolkit/datumaro/pull/534>)

### Deprecated
- TBD
- Using `Image`, `ByteImage` from `datumaro.util.image` - these classes
are moved to `datumaro.components.media`
(<https://github.com/openvinotoolkit/datumaro/pull/538>)

### Removed
- TBD
Expand Down
2 changes: 1 addition & 1 deletion datumaro/components/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from datumaro.components.cli_plugin import CliPlugin
from datumaro.components.dataset import DatasetPatch
from datumaro.components.extractor import DatasetItem
from datumaro.util.image import Image
from datumaro.components.media import Image
from datumaro.util.os_util import rmtree
from datumaro.util.scope import on_error_do, scoped

Expand Down
2 changes: 1 addition & 1 deletion datumaro/components/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
from datumaro.components.format_detection import (
FormatDetectionConfidence, FormatDetectionContext,
)
from datumaro.components.media import Image
from datumaro.util import is_method_redefined
from datumaro.util.attrs_util import default_if_none, not_empty
from datumaro.util.image import Image

# Re-export some names from .annotation for backwards compatibility.
import datumaro.components.annotation # isort:skip
Expand Down
180 changes: 180 additions & 0 deletions datumaro/components/media.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Copyright (C) 2021 Intel Corporation
#
# SPDX-License-Identifier: MIT

from typing import Callable, Optional, Tuple, Union
import os
import os.path as osp
import shutil

import numpy as np

from datumaro.util.image import (
_image_loading_errors, decode_image, lazy_image, save_image,
)


class MediaElement:
def __init__(self, path: str) -> None:
self._path = path

@property
def path(self) -> str:
"""Path to the media file"""
return self._path

@property
def ext(self) -> str:
"""Media file extension"""
return osp.splitext(osp.basename(self.path))[1]

def __eq__(self, other: object) -> bool:
# We need to compare exactly with this type
if type(other) is not __class__: # pylint: disable=unidiomatic-typecheck
return False
return self._path == other._path

class Image(MediaElement):
def __init__(self,
data: Union[np.ndarray, Callable[[str], np.ndarray], None] = None,
*,
path: Optional[str] = None,
size: Optional[Tuple[int, int]] = None):
assert size is None or len(size) == 2, size
if size is not None:
assert len(size) == 2 and 0 < size[0] and 0 < size[1], size
size = tuple(map(int, size))
self._size = size # (H, W)
if not self._size and isinstance(data, np.ndarray):
self._size = data.shape[:2]

assert path is None or isinstance(path, str), path
if path is None:
path = ''
elif path:
path = osp.abspath(path).replace('\\', '/')
self._path = path

if not isinstance(data, np.ndarray):
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._data = data

@property
def data(self) -> np.ndarray:
"""Image data in BGR HWC [0; 255] (float) format"""

if callable(self._data):
data = self._data()
else:
data = self._data

if self._size is None and data is not None:
self._size = tuple(map(int, data.shape[:2]))
return data

@property
def has_data(self) -> bool:
return self._data is not None

@property
def has_size(self) -> bool:
return self._size is not None or isinstance(self._data, np.ndarray)

@property
def size(self) -> Optional[Tuple[int, int]]:
"""Returns (H, W)"""

if self._size is None:
try:
data = self.data
except _image_loading_errors:
return None
if data is not None:
self._size = tuple(map(int, data.shape[:2]))
return self._size

def __eq__(self, other):
if isinstance(other, np.ndarray):
return self.has_data and np.array_equal(self.data, other)

if not isinstance(other, __class__):
return False
return \
(np.array_equal(self.size, other.size)) and \
(self.has_data == other.has_data) and \
(self.has_data and np.array_equal(self.data, other.data) or \
not self.has_data)

def save(self, path):
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)
else:
save_image(path, self.data)

class ByteImage(Image):
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):
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)

super().__init__(path=path, 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

self._bytes_data = data
if ext:
ext = ext.lower()
if not ext.startswith('.'):
ext = '.' + ext
self._ext = ext

def get_bytes(self):
if callable(self._bytes_data):
return self._bytes_data()
return self._bytes_data

@property
def ext(self):
if self._ext:
return self._ext
return super().ext

def save(self, path):
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)
3 changes: 2 additions & 1 deletion datumaro/plugins/coco_format/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
from datumaro.components.extractor import (
DEFAULT_SUBSET_NAME, DatasetItem, SourceExtractor,
)
from datumaro.util.image import Image, lazy_image, load_image
from datumaro.components.media import Image
from datumaro.util.image import lazy_image, load_image
from datumaro.util.mask_tools import bgr2index
from datumaro.util.os_util import suppress_output

Expand Down
2 changes: 1 addition & 1 deletion datumaro/plugins/cvat_format/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
AnnotationType, Bbox, Label, LabelCategories, Points, Polygon, PolyLine,
)
from datumaro.components.extractor import DatasetItem, Importer, SourceExtractor
from datumaro.util.image import Image
from datumaro.components.media import Image

from .format import CvatPath

Expand Down
2 changes: 1 addition & 1 deletion datumaro/plugins/datumaro_format/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
MaskCategories, Points, PointsCategories, Polygon, PolyLine, RleMask,
)
from datumaro.components.extractor import DatasetItem, Importer, SourceExtractor
from datumaro.util.image import Image
from datumaro.components.media import Image

from .format import DatumaroPath

Expand Down
3 changes: 2 additions & 1 deletion datumaro/plugins/image_zip_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@

from datumaro.components.converter import Converter
from datumaro.components.extractor import DatasetItem, Importer, SourceExtractor
from datumaro.components.media import ByteImage
from datumaro.util import parse_str_enum_value
from datumaro.util.image import IMAGE_EXTENSIONS, ByteImage, encode_image
from datumaro.util.image import IMAGE_EXTENSIONS, encode_image


class Compression(Enum):
Expand Down
3 changes: 2 additions & 1 deletion datumaro/plugins/labelme_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
)
from datumaro.components.converter import Converter
from datumaro.components.extractor import DatasetItem, Extractor, Importer
from datumaro.components.media import Image
from datumaro.util import cast, escape, unescape
from datumaro.util.image import Image, save_image
from datumaro.util.image import save_image
from datumaro.util.mask_tools import find_mask_bbox, load_mask
from datumaro.util.os_util import split_path

Expand Down
3 changes: 2 additions & 1 deletion datumaro/plugins/mot_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
from datumaro.components.annotation import AnnotationType, Bbox, LabelCategories
from datumaro.components.converter import Converter
from datumaro.components.extractor import DatasetItem, Importer, SourceExtractor
from datumaro.components.media import Image
from datumaro.util import cast
from datumaro.util.image import Image, find_images
from datumaro.util.image import find_images

MotLabel = Enum('MotLabel', [
('pedestrian', 1),
Expand Down
3 changes: 2 additions & 1 deletion datumaro/plugins/open_images_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@
DatasetError, RepeatedItemError, UndefinedLabel,
)
from datumaro.components.extractor import DatasetItem, Extractor, Importer
from datumaro.components.media import Image
from datumaro.components.validator import Severity
from datumaro.util.annotation_util import find_instances
from datumaro.util.image import (
DEFAULT_IMAGE_META_FILE_NAME, Image, find_images, lazy_image, load_image,
DEFAULT_IMAGE_META_FILE_NAME, find_images, lazy_image, load_image,
load_image_meta_file, save_image, save_image_meta_file,
)
from datumaro.util.os_util import make_file_name, split_path
Expand Down
3 changes: 2 additions & 1 deletion datumaro/plugins/tf_detection_api_format/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@

from datumaro.components.annotation import AnnotationType, LabelCategories
from datumaro.components.converter import Converter
from datumaro.components.media import ByteImage
from datumaro.util.annotation_util import (
find_group_leader, find_instances, max_bbox,
)
from datumaro.util.image import ByteImage, encode_image
from datumaro.util.image import encode_image
from datumaro.util.mask_tools import merge_masks
from datumaro.util.tf_util import import_tf as _import_tf

Expand Down
3 changes: 2 additions & 1 deletion datumaro/plugins/tf_detection_api_format/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
AnnotationType, Bbox, LabelCategories, Mask,
)
from datumaro.components.extractor import DatasetItem, Importer, SourceExtractor
from datumaro.util.image import ByteImage, decode_image, lazy_image
from datumaro.components.media import ByteImage
from datumaro.util.image import decode_image, lazy_image
from datumaro.util.tf_util import import_tf as _import_tf

from .format import DetectionApiPath
Expand Down
3 changes: 2 additions & 1 deletion datumaro/plugins/voc_format/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
AnnotationType, Bbox, CompiledMask, Label, Mask,
)
from datumaro.components.extractor import DatasetItem, SourceExtractor
from datumaro.util.image import Image, find_images
from datumaro.components.media import Image
from datumaro.util.image import find_images
from datumaro.util.mask_tools import invert_colormap, lazy_mask

from .format import (
Expand Down
3 changes: 2 additions & 1 deletion datumaro/plugins/yolo_format/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
from datumaro.components.extractor import (
DatasetItem, Extractor, Importer, SourceExtractor,
)
from datumaro.components.media import Image
from datumaro.util.image import (
DEFAULT_IMAGE_META_FILE_NAME, Image, load_image_meta_file,
DEFAULT_IMAGE_META_FILE_NAME, load_image_meta_file,
)
from datumaro.util.os_util import split_path

Expand Down
Loading

0 comments on commit fe0b13a

Please sign in to comment.