base.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import numpy as np
  2. import abc
  3. import warnings
  4. import logging
  5. from os.path import join, isfile, isdir
  6. from collections import defaultdict, OrderedDict
  7. from nabirds.utils import read_info_file, feature_file_name
  8. from nabirds.dataset import Dataset
  9. # def _parse_index(idx, offset):
  10. # if idx.isdigit():
  11. # idx = str(int(idx) - offset)
  12. # return idx
  13. class BBoxMixin(abc.ABC):
  14. def _load_bounding_boxes(self):
  15. assert self._bounding_boxes is not None, "Bouding boxes were not loaded!"
  16. uuid_to_bbox = {}
  17. for content in [i.split() for i in self._bounding_boxes]:
  18. uuid, bbox = content[0], content[1:]
  19. uuid_to_bbox[uuid] = [float(i) for i in bbox]
  20. self.bounding_boxes = np.array(
  21. [tuple(uuid_to_bbox[uuid]) for uuid in self.uuids],
  22. dtype=self.meta.bounding_box_dtype)
  23. def bounding_box(self, uuid):
  24. return self.bounding_boxes[self.uuid_to_idx[uuid]].copy()
  25. class BaseAnnotations(abc.ABC):
  26. FEATURE_PHONY = dict(train=["train"], test=["test", "val"])
  27. def __init__(self, root_or_infofile, parts=None, feature_model=None):
  28. super(BaseAnnotations, self).__init__()
  29. self.part_type = parts
  30. self.feature_model = feature_model
  31. if isdir(root_or_infofile):
  32. self.info = None
  33. self.root = root_or_infofile
  34. elif isfile(root_or_infofile):
  35. self.root = self.root_from_infofile(root_or_infofile, parts)
  36. else:
  37. raise ValueError("Root folder or info file does not exist: \"{}\"".format(
  38. root_or_infofile
  39. ))
  40. for fname, attr in self.meta.structure:
  41. self.read_content(fname, attr)
  42. self._load_uuids()
  43. self._load_labels()
  44. self._load_parts()
  45. self._load_split()
  46. @property
  47. def data_root(self):
  48. if self.info is None: return None
  49. return join(self.info.BASE_DIR, self.info.DATA_DIR)
  50. @property
  51. def dataset_info(self):
  52. if self.info is None: return None
  53. if self.part_type is None:
  54. return self.info.DATASETS[self.__class__.name]
  55. else:
  56. return self.info.PARTS[self.part_type]
  57. def root_from_infofile(self, info_file, parts=None):
  58. self.info = read_info_file(info_file)
  59. dataset_info = self.dataset_info
  60. annot_dir = join(self.data_root, dataset_info.folder, dataset_info.annotations)
  61. assert isdir(annot_dir), "Annotation folder does exist! \"{}\"".format(annot_dir)
  62. return annot_dir
  63. def new_dataset(self, subset=None, dataset_cls=Dataset, **kwargs):
  64. if subset is not None:
  65. uuids = getattr(self, "{}_uuids".format(subset))
  66. else:
  67. uuids = self.uuids
  68. kwargs = self.check_parts_and_features(subset, **kwargs)
  69. return dataset_cls(uuids=uuids, annotations=self, **kwargs)
  70. def check_parts_and_features(self, subset, **kwargs):
  71. dataset_info = self.dataset_info
  72. if dataset_info is None:
  73. return kwargs
  74. logging.debug("Dataset info: {}".format(dataset_info))
  75. # TODO: pass all scales
  76. new_opts = {}
  77. if "scales" in dataset_info:
  78. new_opts["ratio"] = dataset_info.scales[0]
  79. if "is_uniform" in dataset_info:
  80. new_opts["uniform_parts"] = dataset_info.is_uniform
  81. if self.part_type is not None:
  82. new_opts["part_rescale_size"] = dataset_info.rescale_size
  83. if None not in [subset, self.feature_model]:
  84. tried = []
  85. model_info = self.info.MODELS[self.feature_model]
  86. for subset_phony in BaseAnnotations.FEATURE_PHONY[subset]:
  87. features = feature_file_name(subset_phony, dataset_info, model_info)
  88. feature_path = join(self.root, "features", features)
  89. if isfile(feature_path): break
  90. tried.append(feature_path)
  91. else:
  92. raise ValueError(
  93. "Could not find any features in \"{}\" for {} subset. Tried features: {}".format(
  94. join(self.root, "features"), subset, tried))
  95. new_opts["features"] = feature_path
  96. new_opts.update(kwargs)
  97. logging.debug("Final kwargs: {}".format(new_opts))
  98. return new_opts
  99. @property
  100. def has_parts(self):
  101. return hasattr(self, "_part_locs") and self._part_locs is not None
  102. @property
  103. @abc.abstractmethod
  104. def meta(self):
  105. pass
  106. def _path(self, file):
  107. return join(self.root, file)
  108. def _open(self, file):
  109. return open(self._path(file))
  110. def read_content(self, file, attr):
  111. content = None
  112. fpath = self._path(file)
  113. if isfile(fpath):
  114. with self._open(file) as f:
  115. content = [line.strip() for line in f if line.strip()]
  116. else:
  117. warnings.warn("File \"{}\" was not found!".format(fpath))
  118. setattr(self, attr, content)
  119. def _load_labels(self):
  120. self.labels = np.array([int(l) for l in self.labels], dtype=np.int32)
  121. def _load_uuids(self):
  122. assert self._images is not None, "Images were not loaded!"
  123. uuid_fnames = [i.split() for i in self._images]
  124. self.uuids, self.images = map(np.array, zip(*uuid_fnames))
  125. self.uuid_to_idx = {uuid: i for i, uuid in enumerate(self.uuids)}
  126. def _load_parts(self):
  127. assert self.has_parts, "Part locations were not loaded!"
  128. # this part is quite slow... TODO: some runtime improvements?
  129. uuid_to_parts = defaultdict(list)
  130. for content in [i.split() for i in self._part_locs]:
  131. uuid = content[0]
  132. assert uuid in self.uuids, \
  133. "Could not find UUID \"\" from part annotations in image annotations!".format(uuid)
  134. uuid_to_parts[uuid].append([float(c) for c in content[1:]])
  135. uuid_to_parts = dict(uuid_to_parts)
  136. self.part_locs = np.stack([
  137. uuid_to_parts[uuid] for uuid in self.uuids]).astype(int)
  138. if hasattr(self, "_part_names") and self._part_names is not None:
  139. self._load_part_names()
  140. def _load_part_names(self):
  141. self.part_names = OrderedDict()
  142. self.part_name_list = []
  143. for line in self._part_names:
  144. part_idx, _, name = line.partition(" ")
  145. self.part_names[int(part_idx)] = name
  146. self.part_name_list.append(name)
  147. def _load_split(self):
  148. assert self._split is not None, "Train-test split was not loaded!"
  149. uuid_to_split = {uuid: int(split) for uuid, split in zip(self.uuids, self._split)}
  150. self.train_split = np.array([uuid_to_split[uuid] for uuid in self.uuids], dtype=bool)
  151. self.test_split = np.logical_not(self.train_split)
  152. def image_path(self, image):
  153. return join(self.root, self.meta.images_folder, image)
  154. def image(self, uuid):
  155. fname = self.images[self.uuid_to_idx[uuid]]
  156. return self.image_path(fname)
  157. def label(self, uuid):
  158. return self.labels[self.uuid_to_idx[uuid]].copy()
  159. def parts(self, uuid):
  160. return self.part_locs[self.uuid_to_idx[uuid]].copy()
  161. def _uuids(self, split):
  162. return self.uuids[split]
  163. @property
  164. def train_uuids(self):
  165. return self._uuids(self.train_split)
  166. @property
  167. def test_uuids(self):
  168. return self._uuids(self.test_split)