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

Generic media support #539

Merged
merged 68 commits into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
f274bd3
Move image classes
Nov 6, 2021
4bf67e3
Update image class uses
Nov 6, 2021
fdf3258
Update changelog
Nov 6, 2021
ae12800
update imports
Nov 6, 2021
46d41e7
Fix cache parameter
Nov 6, 2021
8b06f57
Remove extra parameter
Nov 6, 2021
a03c21c
Fix import
Nov 6, 2021
1716652
Introduce pcint cloud, deprecate old members
Nov 6, 2021
e0675cf
Support for generic media in datumaro format
Nov 6, 2021
24ca9f2
Provide backward compat alias for DTypeLike
Nov 6, 2021
9f13a58
Merge branch 'zm/update-image-classes' into zm/generic-media
Nov 6, 2021
e360e24
Ignore own deprecation warnings
Nov 8, 2021
b864a0a
Add deprecation warnings on annotation imports
Nov 8, 2021
d80de52
Add message
Nov 8, 2021
d3359b3
Merge develop
Nov 10, 2021
93e3126
Merge branch 'develop' into zm/generic-media
Nov 10, 2021
0f8520c
Add save-media, replace image and point cloud with media
yasakova-anastasia Feb 3, 2022
18cbb05
Add 'media_type' to Extractors
yasakova-anastasia Feb 14, 2022
304a988
Resolve conflicts
yasakova-anastasia Feb 14, 2022
3a033bc
Fix pylint
yasakova-anastasia Feb 14, 2022
867b9d3
Replace image with media in Tensorflow Extractor
yasakova-anastasia Feb 14, 2022
e0e3681
Some fixes in MPII format
yasakova-anastasia Feb 14, 2022
8e0d538
Small fix
yasakova-anastasia Feb 14, 2022
c182e5b
Correction of some points
yasakova-anastasia Feb 15, 2022
8c6fb75
Move 'check_media_type' to 'init_cache()'
yasakova-anastasia Feb 16, 2022
6a66917
Fixes
yasakova-anastasia Feb 16, 2022
9b78f7a
Add error when multiple media types are in Datumaro format
yasakova-anastasia Feb 16, 2022
8ad43eb
Fixes
yasakova-anastasia Feb 16, 2022
0eb3e97
Add checks for media type in converters
yasakova-anastasia Feb 16, 2022
2b3f2ee
Fixes
yasakova-anastasia Feb 16, 2022
c31858d
Resolve conflicts
yasakova-anastasia Feb 16, 2022
45fd612
Replace 'require_images' with 'require_media'
yasakova-anastasia Feb 16, 2022
1a2355e
Sort imports
yasakova-anastasia Feb 16, 2022
4f9df5f
Fixes
yasakova-anastasia Feb 16, 2022
b942965
Add 'media_type' to 'from_iterable()'
yasakova-anastasia Feb 18, 2022
f730e10
Fix checks for media type in converters
yasakova-anastasia Feb 18, 2022
c330a10
Fix pylint
yasakova-anastasia Feb 18, 2022
92af796
Fix merging
yasakova-anastasia Feb 18, 2022
6b416ec
Fix pylint
yasakova-anastasia Feb 18, 2022
bcde3d9
Resolve conflicts
yasakova-anastasia Feb 19, 2022
7f8d19d
Fix pylint
yasakova-anastasia Feb 19, 2022
ec1d9fa
Fixes
yasakova-anastasia Feb 21, 2022
c32b1ea
Fix pylint
yasakova-anastasia Feb 22, 2022
4d1fc6d
Add test for point cloud merging
yasakova-anastasia Feb 22, 2022
1c7d1d8
Small fix in test
yasakova-anastasia Feb 22, 2022
17b3b0b
Fixes
yasakova-anastasia Feb 22, 2022
7ee9277
Fix codacy
yasakova-anastasia Feb 22, 2022
8dee9ed
Fix checks for media types in extractors
yasakova-anastasia Feb 22, 2022
2300ec0
Fixes
yasakova-anastasia Feb 22, 2022
59a1d5a
Merge branch 'develop' into zm/generic-media
yasakova-anastasia Feb 24, 2022
2549a21
Improve deprecation messages
Mar 3, 2022
4c7c9cf
Update changelog
Mar 3, 2022
0266c68
Fix transform media type
Mar 3, 2022
f9e57f7
Improve type annotations in extractor
Mar 3, 2022
89de275
Fix cli defaults
Mar 3, 2022
24a0a1f
Add method docs in IExtractor
Mar 5, 2022
c978d88
Remove media_type from baseextractor
Mar 5, 2022
851f04d
Support media type in Dataset
Mar 5, 2022
21af31c
Fix media type merging
Mar 5, 2022
bce5a9f
Add media type checks in compare_datasets
Mar 5, 2022
52ede1d
Update TFDS extractors
Mar 5, 2022
213f73b
Update tests
Mar 5, 2022
957227d
Add comment about default media_type
Mar 5, 2022
0ddd4b4
Simplify checks
Mar 9, 2022
d473b43
Disallow dataset media_type changes by transforms
Mar 9, 2022
1a56813
Add media type check on addition to dataset
Mar 9, 2022
21017d0
Add dataset c-tor docs
Mar 9, 2022
c17cb5b
Update changelog
Mar 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions datumaro/cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import logging as log
import os.path as osp
import sys
import warnings

from ..util.telemetry_utils import (
close_telemetry_session, init_telemetry_session,
Expand Down Expand Up @@ -39,6 +40,10 @@ def init_logger(cls, args=None):
log.basicConfig(format='%(asctime)s %(levelname)s: %(message)s',
level=args.loglevel)

# Suppress own deprecation warnings
warnings.filterwarnings('ignore', category=DeprecationWarning,
module=r'datumaro\..*')

@staticmethod
def _define_loglevel_option(parser):
parser.add_argument('--loglevel', type=loglevel, default='info',
Expand Down
2 changes: 1 addition & 1 deletion datumaro/cli/commands/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def build_parser(parser_ctor=argparse.ArgumentParser):
|n
Examples:|n
- Download the MNIST dataset:|n
|s|s%(prog)s -i tfds:mnist -- --save-images|n
|s|s%(prog)s -i tfds:mnist -- --save-media|n
|n
- Download the VOC 2012 dataset, saving only the annotations in the COCO
format into a specific directory:|n
Expand Down
12 changes: 6 additions & 6 deletions datumaro/cli/contexts/project/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,10 @@ def filter_command(args):

# Source might be missing in the working dir, so we specify
# the output directory.
# We specify save_images here as a heuristic. It can probably
# We specify save_media here as a heuristic. It can probably
# be improved by checking if there are images in the dataset
# directory.
dataset.save(project.source_data_dir(target), save_images=True)
dataset.save(project.source_data_dir(target), save_media=True)

log.info("Finished")
else:
Expand All @@ -389,7 +389,7 @@ def filter_command(args):
dst_dir = osp.abspath(dst_dir)

dataset.filter(filter_expr, *filter_args)
dataset.save(dst_dir, save_images=True)
dataset.save(dst_dir, save_media=True)

log.info("Results have been saved to '%s'" % dst_dir)

Expand Down Expand Up @@ -557,10 +557,10 @@ def transform_command(args):

# Source might be missing in the working dir, so we specify
# the output directory
# We specify save_images here as a heuristic. It can probably
# We specify save_media here as a heuristic. It can probably
# be improved by checking if there are images in the dataset
# directory.
dataset.save(project.source_data_dir(target), save_images=True)
dataset.save(project.source_data_dir(target), save_media=True)

log.info("Finished")
else:
Expand All @@ -575,7 +575,7 @@ def transform_command(args):
dst_dir = osp.abspath(dst_dir)

dataset.transform(args.transform, **extra_args)
dataset.save(dst_dir, save_images=True)
dataset.save(dst_dir, save_media=True)

log.info("Results have been saved to '%s'" % dst_dir)

Expand Down
11 changes: 8 additions & 3 deletions datumaro/cli/contexts/project/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import cv2
import numpy as np

from datumaro.components.media import Image

with warnings.catch_warnings():
warnings.simplefilter("ignore")
import tensorboardX as tb
Expand Down Expand Up @@ -121,7 +123,10 @@ def save(self, a: IDataset, b: IDataset):
self.update_mask_confusion(mask_diff)

self.save_item_label_diff(item_a, item_b, label_diff)
self.save_item_bbox_diff(item_a, item_b, bbox_diff)

if a.media_type() and issubclass(a.media_type(), Image) and \
b.media_type() and issubclass(b.media_type(), Image):
self.save_item_bbox_diff(item_a, item_b, bbox_diff)

if len(self.label_confusion_matrix) != 0:
self.save_conf_matrix(self.label_confusion_matrix,
Expand Down Expand Up @@ -243,11 +248,11 @@ def save_item_bbox_diff(self, item_a, item_b, diff):
_, mispred, a_unmatched, b_unmatched = diff

if 0 < len(a_unmatched) + len(b_unmatched) + len(mispred):
if not item_a.has_image or not item_a.image.has_data:
if not isinstance(item_a.media, Image) or not item_a.media.has_data:
zhiltsov-max marked this conversation as resolved.
Show resolved Hide resolved
log.warning("Item %s: item has no image data, "
"it will be skipped" % (item_a.id))
return
img_a = item_a.image.data.copy()
img_a = item_a.media.data.copy()
img_b = img_a.copy()
for a_bbox, b_bbox in mispred:
self.draw_bbox(img_a, a_bbox, self.get_a_label(a_bbox.label),
Expand Down
45 changes: 31 additions & 14 deletions datumaro/components/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@
import os
import os.path as osp
import shutil
import warnings

from attrs import define, field
import attr

from datumaro.components.cli_plugin import CliPlugin
from datumaro.components.errors import (
AnnotationExportError, DatumaroError, ItemExportError,
AnnotationExportError, DatasetExportError, DatumaroError, ItemExportError,
)
from datumaro.components.extractor import DatasetItem, IExtractor
from datumaro.components.media import Image
from datumaro.components.media import Image, PointCloud
from datumaro.components.progress_reporting import (
NullProgressReporter, ProgressReporter,
)
Expand Down Expand Up @@ -89,8 +90,10 @@ class Converter(CliPlugin):
@classmethod
def build_cmdline_parser(cls, **kwargs):
parser = super().build_cmdline_parser(**kwargs)
parser.add_argument('--save-images', action='store_true',
help="Save images (default: %(default)s)")
parser.add_argument('--save-images', action='store_true', default=None,
help="Save images (default: %s)" % (None))
parser.add_argument('--save-media', action='store_true', default=None,
help="Save media (default: %s)" % (None))
parser.add_argument('--image-ext', default=None,
help="Image extension (default: keep or use format default%s)" % \
(' ' + cls.DEFAULT_IMAGE_EXT if cls.DEFAULT_IMAGE_EXT else ''))
Expand Down Expand Up @@ -138,7 +141,8 @@ def apply(self):
raise NotImplementedError("Should be implemented in a subclass")

def __init__(self, extractor: IExtractor, save_dir: str, *,
save_images: bool = False,
save_images = None,
save_media: bool = None,
image_ext: Optional[str] = None,
default_image_ext: Optional[str] = None,
save_dataset_meta: bool = False,
Expand All @@ -147,7 +151,20 @@ def __init__(self, extractor: IExtractor, save_dir: str, *,
assert default_image_ext
self._default_image_ext = default_image_ext

self._save_images = save_images
if save_images is not None and save_media is not None:
raise DatasetExportError("Can't use 'save-media' and "
"save-images together")

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.",
DeprecationWarning, stacklevel=2)
else:
self._save_media = False

self._image_ext = image_ext

self._extractor = extractor
Expand All @@ -168,8 +185,8 @@ def __init__(self, extractor: IExtractor, save_dir: str, *,
def _find_image_ext(self, item: Union[DatasetItem, Image]):
src_ext = None

if isinstance(item, DatasetItem) and item.has_image:
src_ext = item.image.ext
if isinstance(item, DatasetItem) and isinstance(item.media, Image):
src_ext = item.media.ext
elif isinstance(item, Image):
src_ext = item.ext

Expand All @@ -192,7 +209,7 @@ def _save_image(self, item, path=None, *,
assert not ((subdir or name or basedir) and path), \
"Can't use both subdir or name or basedir and path arguments"

if not item.has_image or not item.image.has_data:
if not isinstance(item.media, Image) or not item.media.has_data:
log.warning("Item '%s' has no image", item.id)
return

Expand All @@ -201,14 +218,14 @@ def _save_image(self, item, path=None, *,
self._make_image_filename(item, name=name, subdir=subdir))
path = osp.abspath(path)

item.image.save(path)
item.media.save(path)

def _save_point_cloud(self, item=None, path=None, *,
name=None, subdir=None, basedir=None):
sizov-kirill marked this conversation as resolved.
Show resolved Hide resolved
assert not ((subdir or name or basedir) and path), \
"Can't use both subdir or name or basedir and path arguments"

if not item.point_cloud:
if not item.media or not isinstance(item.media, PointCloud):
log.warning("Item '%s' has no pcd", item.id)
return

Expand All @@ -218,9 +235,9 @@ def _save_point_cloud(self, item=None, path=None, *,
path = osp.abspath(path)

os.makedirs(osp.dirname(path), exist_ok=True)
if item.point_cloud and osp.isfile(item.point_cloud):
if item.point_cloud != path:
shutil.copyfile(item.point_cloud, path)
if item.media and osp.isfile(item.media.path):
if item.media.path != path:
shutil.copyfile(item.media.path, path)

def _save_meta_file(self, path):
save_meta_file(path, self._extractor.categories())
53 changes: 43 additions & 10 deletions datumaro/components/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
)
from datumaro.components.environment import Environment
from datumaro.components.errors import (
CategoriesRedefinedError, ConflictingCategoriesError,
CategoriesRedefinedError, ConflictingCategoriesError, MediaTypeError,
MultipleFormatsMatchError, NoMatchingFormatsError, RepeatedItemError,
UnknownFormatError,
)
Expand All @@ -36,6 +36,7 @@
ImportContext, ImportErrorPolicy, ItemTransform, Transform, _ImportFail,
)
from datumaro.components.launcher import Launcher, ModelTransform
from datumaro.components.media import Image, MediaElement
from datumaro.components.progress_reporting import (
NullProgressReporter, ProgressReporter,
)
Expand Down Expand Up @@ -159,9 +160,11 @@ def categories(self):
return self.parent.categories()


def __init__(self, parent: DatasetItemStorage, categories: CategoriesInfo):
def __init__(self, parent: DatasetItemStorage, categories: CategoriesInfo,
media_type: Optional[Type[MediaElement]]):
self._parent = parent
self._categories = categories
self._media_type = media_type

def __iter__(self):
yield from self._parent
Expand All @@ -184,6 +187,8 @@ def subsets(self):
def get(self, id, subset=None):
return self._parent.get(id, subset=subset)

def media_type(self):
return self._media_type

class ItemStatus(Enum):
added = auto()
Expand All @@ -195,7 +200,7 @@ class DatasetPatchWrapper(DatasetItemStorageDatasetView):
# The purpose of this class is to indicate that the input dataset is
# a patch and autofill patch info in Converter
def __init__(self, patch: DatasetPatch, parent: IDataset):
super().__init__(patch.data, parent.categories())
super().__init__(patch.data, parent.categories(), parent.media_type())
self.patch = patch

def subsets(self):
Expand Down Expand Up @@ -261,19 +266,25 @@ def subsets(self):
def categories(self):
return self.parent.categories()

def media_type(self):
return self.parent.media_type()

def as_dataset(self) -> Dataset:
return Dataset.from_extractors(self, env=self.parent.env)


class DatasetStorage(IDataset):
def __init__(self, source: Union[IDataset, DatasetItemStorage] = None,
categories: CategoriesInfo = None):
categories: CategoriesInfo = None,
media_type: Optional[Type[MediaElement]] = None):
if source is None and categories is None:
categories = {}
elif isinstance(source, IDataset) and categories is not None:
raise ValueError("Can't use both source and categories")
self._categories = categories

self._media_type = media_type

# Possible combinations:
# 1. source + storage
# - Storage contains a patch to the Source data.
Expand Down Expand Up @@ -392,6 +403,11 @@ def _update_status(item_id, new_status: ItemStatus):

i = -1
for i, item in enumerate(source):
if source.media_type():
if item.media and not isinstance(item.media, source.media_type()):
raise MediaTypeError("Dataset elements must have a '%s' " \
"media type" % source.media_type())

if transform and transform.is_local:
old_id = (item.id, item.subset)
item = transform.transform_item(item)
Expand Down Expand Up @@ -478,7 +494,8 @@ def _merged(self) -> IDataset:
return self._source
elif self._source is not None:
self.init_cache()
return DatasetItemStorageDatasetView(self._storage, self._categories)
return DatasetItemStorageDatasetView(self._storage,
self._categories, self._media_type)

def __len__(self) -> int:
if self._length is None:
Expand All @@ -502,6 +519,14 @@ def define_categories(self, categories: CategoriesInfo):
raise CategoriesRedefinedError()
self._categories = categories

def media_type(self):
if self.is_cache_initialized():
return self._media_type
elif self._media_type is not None:
return self._media_type
else:
return self._source.media_type()

def put(self, item):
is_new = self._storage.put(item)

Expand Down Expand Up @@ -624,7 +649,8 @@ class Dataset(IDataset):
@classmethod
def from_iterable(cls, iterable: Iterable[DatasetItem],
categories: Union[CategoriesInfo, List[str], None] = None,
env: Optional[Environment] = None) -> Dataset:
env: Optional[Environment] = None,
media_type: Type = Image) -> Dataset:
if isinstance(categories, list):
categories = { AnnotationType.label:
LabelCategories.from_iterable(categories)
Expand All @@ -636,7 +662,8 @@ def from_iterable(cls, iterable: Iterable[DatasetItem],
class _extractor(Extractor):
def __init__(self):
super().__init__(length=len(iterable) \
if hasattr(iterable, '__len__') else None)
if hasattr(iterable, '__len__') else None,
media_type=media_type)

def __iter__(self):
return iter(iterable)
Expand All @@ -657,19 +684,23 @@ def from_extractors(*sources: IDataset,
source = ExactMerge.merge(*sources)
categories = ExactMerge.merge_categories(
s.categories() for s in sources)
dataset = Dataset(source=source, categories=categories, env=env)
media_type=ExactMerge.merge_media_types(sources)
dataset = Dataset(source=source, categories=categories,
media_type=media_type, env=env)
return dataset

def __init__(self, source: Optional[IDataset] = None, *,
categories: Optional[CategoriesInfo] = None,
media_type: Optional[Type[MediaElement]] = None,
env: Optional[Environment] = None) -> None:
super().__init__()

assert env is None or isinstance(env, Environment), env
self._env = env

self.eager = None
self._data = DatasetStorage(source, categories=categories)
self._data = DatasetStorage(source, categories=categories,
media_type=media_type)
if self.is_eager:
self.init_cache()

Expand Down Expand Up @@ -698,6 +729,9 @@ def subsets(self) -> Dict[str, DatasetSubset]:
def categories(self) -> CategoriesInfo:
return self._data.categories()

def media_type(self) -> Optional[Type[MediaElement]]:
return self._data.media_type()

def get(self, id: str, subset: Optional[str] = None) \
-> Optional[DatasetItem]:
return self._data.get(id, subset)
Expand Down Expand Up @@ -1125,7 +1159,6 @@ def detect(path: str, *,
if 1 < len(matches):
raise MultipleFormatsMatchError(matches)


@contextmanager
def eager_mode(new_mode: bool = True, dataset: Optional[Dataset] = None) -> None:
if dataset is not None:
Expand Down
Loading