Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move image classes #538

Merged
merged 17 commits into from
Nov 10, 2021
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,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