base.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import abc
  2. import logging
  3. import numpy as np
  4. from collections import OrderedDict
  5. from collections import defaultdict
  6. from pathlib import Path
  7. from typing import Tuple
  8. from cvdatasets.annotation import mixins
  9. from cvdatasets.annotation.files import AnnotationFiles
  10. from cvdatasets.dataset import Dataset
  11. from cvdatasets.utils import feature_file_name
  12. from cvdatasets.utils import pretty_print_dict
  13. from cvdatasets.utils import read_info_file
  14. from cvdatasets.utils.decorators import only_with_info
  15. class BaseAnnotations(abc.ABC):
  16. @classmethod
  17. def extract_kwargs(cls, opts, *args, **kwargs):
  18. return dict(
  19. root_or_infofile=opts.data,
  20. load_strict=getattr(opts, "load_strict", False),
  21. dataset_key=getattr(opts, "dataset", None)
  22. )
  23. @classmethod
  24. def new(cls, opts, *, ds_info=None, **_kwargs):
  25. kwargs = cls.extract_kwargs(opts, ds_info)
  26. kwargs.update(_kwargs)
  27. kwargs_str = pretty_print_dict(kwargs)
  28. try:
  29. annot = cls(**kwargs)
  30. except Exception as e:
  31. logging.error(f"Failed to create \"{cls.__name__}\" annotations " + \
  32. f"with following kwargs: \"{kwargs_str}\". " + \
  33. f"Error was: {e}"
  34. )
  35. raise
  36. else:
  37. logging.info(f"Loaded \"{annot.dataset_key}\" annotations " + \
  38. f"with following kwargs: \"{kwargs_str}\""
  39. )
  40. return annot
  41. def __init__(self, *, root_or_infofile, dataset_key=None, images_folder="images", load_strict=True, **kwargs):
  42. self.dataset_key = dataset_key
  43. self.images_folder = images_folder
  44. self.load_strict = load_strict
  45. root_or_infofile = Path(root_or_infofile)
  46. if root_or_infofile.is_dir():
  47. self.info = None
  48. self.root = root_or_infofile
  49. elif root_or_infofile.is_file():
  50. self.info = read_info_file(root_or_infofile)
  51. ds_info = self.dataset_info
  52. self.root = self.data_root / ds_info.folder / ds_info.annotations
  53. else:
  54. msg = f"Root folder or info file does not exist: \"{root_or_infofile}\""
  55. raise ValueError(msg)
  56. assert self.root.is_dir(), \
  57. f"Annotation directory does not exist: \"{self.root}\"!"
  58. self.files = self.read_annotation_files()
  59. self.parse_annotations()
  60. @property
  61. @only_with_info
  62. def data_root(self):
  63. return Path(self.info.BASE_DIR) / self.info.DATA_DIR
  64. @property
  65. @only_with_info
  66. def dataset_key(self):
  67. if self._dataset_key is not None:
  68. return self._dataset_key
  69. else:
  70. return self.__class__.__name__
  71. @dataset_key.setter
  72. def dataset_key(self, value):
  73. self._dataset_key = value
  74. @property
  75. @only_with_info
  76. def dataset_info(self):
  77. key = self.dataset_key
  78. if key not in self.info.DATASETS:
  79. raise ValueError(f"Cannot find dataset with key \"{key}\"")
  80. return self.info.DATASETS[key]
  81. def parse_annotations(self):
  82. logging.debug("Parsing read annotations (uuids, labels and train-test splits)")
  83. self._parse_uuids()
  84. self._parse_labels()
  85. self._parse_split()
  86. def __getitem__(self, uuid) -> Tuple[str, int]:
  87. return self.image(uuid), self.label(uuid)
  88. def image_path(self, image) -> str:
  89. return str(self.root / self.images_folder / image)
  90. def image(self, uuid) -> str:
  91. fname = self.image_names[self.uuid_to_idx[uuid]]
  92. return self.image_path(fname)
  93. def label(self, uuid) -> int:
  94. return self.labels[self.uuid_to_idx[uuid]].copy()
  95. def bounding_box(self, uuid) -> object:
  96. return None
  97. def _uuids(self, split) -> np.ndarray:
  98. return self.uuids[split]
  99. @property
  100. def train_uuids(self):
  101. return self._uuids(self.train_split)
  102. @property
  103. def test_uuids(self):
  104. return self._uuids(self.test_split)
  105. def new_train_test_datasets(self, dataset_cls=Dataset, **kwargs):
  106. return (self.new_dataset(subset, dataset_cls) for subset in ["train", "test"])
  107. def new_dataset(self, subset=None, dataset_cls=Dataset, **kwargs):
  108. if subset is not None:
  109. uuids = getattr(self, "{}_uuids".format(subset))
  110. else:
  111. uuids = self.uuids
  112. kwargs = self.check_dataset_kwargs(subset, **kwargs)
  113. return dataset_cls(uuids=uuids, annotations=self, **kwargs)
  114. def check_dataset_kwargs(self, subset, **kwargs):
  115. dataset_info = self.dataset_info
  116. if dataset_info is None:
  117. return kwargs
  118. logging.debug("Dataset info: {}".format(pretty_print_dict(dataset_info)))
  119. # TODO: pass all scales
  120. new_kwargs = {}
  121. if "scales" in dataset_info and len(dataset_info.scales):
  122. new_kwargs["ratio"] = dataset_info.scales[0]
  123. if "is_uniform" in dataset_info:
  124. new_kwargs["uniform_parts"] = dataset_info.is_uniform
  125. new_kwargs.update(kwargs)
  126. logging.debug("Final kwargs: {}".format(pretty_print_dict(new_kwargs)))
  127. return new_kwargs
  128. def read_annotation_files(self) -> AnnotationFiles:
  129. logging.debug("Creating default AnnotationFiles object")
  130. files = AnnotationFiles(root=self.root, load_strict=self.load_strict)
  131. return self.load_files(files)
  132. @abc.abstractmethod
  133. def load_files(self, files_obj) -> AnnotationFiles:
  134. return files_obj
  135. @abc.abstractmethod
  136. def _parse_uuids(self) -> None:
  137. pass
  138. @abc.abstractmethod
  139. def _parse_labels(self) -> None:
  140. pass
  141. @abc.abstractmethod
  142. def _parse_split(self) -> None:
  143. pass
  144. class Annotations(
  145. mixins.BBoxMixin,
  146. mixins.MultiBoxMixin,
  147. mixins.PartsMixin,
  148. mixins.FeaturesMixin,
  149. BaseAnnotations):
  150. pass