Parcourir la source

refactored annotation implementations: reading of parts and bboxes is now handled by mixin classes

Dimitri Korsch il y a 5 ans
Parent
commit
57cbd7e227

+ 1 - 1
cvdatasets/_version.py

@@ -1 +1 @@
-__version__ = "0.4.3"
+__version__ = "0.5.0"

+ 13 - 9
cvdatasets/annotations/__init__.py

@@ -1,13 +1,15 @@
-from .cub import CUB_Annotations
-from .nab import NAB_Annotations
-from .cars import CARS_Annotations
-from .inat import INAT19_Annotations
-from .flowers import FLOWERS_Annotations
-from .dogs import DOGS_Annotations
-from .hed import HED_Annotations
-from .tigers import TIGERS_Annotations
+from cvdatasets.annotations.impl.cub import CUB_Annotations
+from cvdatasets.annotations.impl.nab import NAB_Annotations
+from cvdatasets.annotations.impl.cars import CARS_Annotations
+from cvdatasets.annotations.impl.inat import INAT19_Annotations
+from cvdatasets.annotations.impl.flowers import FLOWERS_Annotations
+from cvdatasets.annotations.impl.dogs import DOGS_Annotations
+from cvdatasets.annotations.impl.hed import HED_Annotations
+from cvdatasets.annotations.impl.tigers import TIGERS_Annotations
 
-from .base import BaseAnnotations
+from cvdatasets.annotations.base import BaseAnnotations
+from cvdatasets.annotations.base.bbox_mixin import BBoxMixin
+from cvdatasets.annotations.base.parts_mixin import PartsMixin
 
 from cvargparse.utils import BaseChoiceType
 from functools import partial
@@ -15,12 +17,14 @@ from functools import partial
 class AnnotationType(BaseChoiceType):
 	CUB200 = CUB_Annotations
 	CUB200_2FOLD = partial(CUB_Annotations)
+	CUB200_GOOGLE = partial(CUB_Annotations)
 	NAB = NAB_Annotations
 	CARS = CARS_Annotations
 	DOGS = DOGS_Annotations
 	FLOWERS = FLOWERS_Annotations
 	HED = HED_Annotations
 	TIGERS = TIGERS_Annotations
+	TIGERS_TEST = partial(TIGERS_Annotations)
 
 	INAT19 = INAT19_Annotations
 	INAT19_MINI = partial(INAT19_Annotations)

+ 47 - 89
cvdatasets/annotations/base.py → cvdatasets/annotations/base/__init__.py

@@ -1,49 +1,35 @@
-import numpy as np
 import abc
-import warnings
 import logging
-from os.path import join, isfile, isdir
-from collections import defaultdict, OrderedDict
-
-from cvdatasets.utils import read_info_file, feature_file_name
-from cvdatasets.dataset import Dataset
-
-# def _parse_index(idx, offset):
-# 	if idx.isdigit():
-# 		idx = str(int(idx) - offset)
-# 	return idx
-
-class BBoxMixin(abc.ABC):
-	def _load_bounding_boxes(self):
-		assert self._bounding_boxes is not None, "Bouding boxes were not loaded!"
-
-		uuid_to_bbox = {}
-		for content in [i.split() for i in self._bounding_boxes]:
-			uuid, bbox = content[0], content[1:]
-			uuid_to_bbox[uuid] = [float(i) for i in bbox]
-
-		self.bounding_boxes = np.array(
-			[tuple(uuid_to_bbox[uuid]) for uuid in self.uuids],
-			dtype=self.meta.bounding_box_dtype)
+import numpy as np
 
-	def bounding_box(self, uuid):
-		return self.bounding_boxes[self.uuid_to_idx[uuid]].copy()
+from collections import OrderedDict
+from collections import defaultdict
+from os.path import isdir
+from os.path import isfile
+from os.path import join
 
+from cvdatasets.dataset import Dataset
+from cvdatasets.utils import feature_file_name
+from cvdatasets.utils import read_info_file
+from cvdatasets.utils import pretty_print_dict
+from cvdatasets.utils.decorators import only_with_info
 
 class BaseAnnotations(abc.ABC):
 
 	FEATURE_PHONY = dict(train=["train"], test=["test", "val"])
 
-	def __init__(self, root_or_infofile, parts=None, feature_model=None):
-		super(BaseAnnotations, self).__init__()
-		self.part_type = parts
+	def __init__(self, *, root_or_infofile, feature_model=None, load_strict=True, **kwargs):
+		super(BaseAnnotations, self).__init__(**kwargs)
 		self.feature_model = feature_model
+		self.load_strict = load_strict
 
 		if isdir(root_or_infofile):
 			self.info = None
 			self.root = root_or_infofile
+
 		elif isfile(root_or_infofile):
-			self.root = self.root_from_infofile(root_or_infofile, parts)
+			self.root = self.root_from_infofile(root_or_infofile)
+
 		else:
 			raise ValueError("Root folder or info file does not exist: \"{}\"".format(
 				root_or_infofile
@@ -52,25 +38,20 @@ class BaseAnnotations(abc.ABC):
 		for fname, attr in self.meta.structure:
 			self.read_content(fname, attr)
 
-		self._load_uuids()
-		self._load_labels()
-		self._load_parts()
-		self._load_split()
+		self.load()
+
 
 	@property
+	@only_with_info
 	def data_root(self):
-		if self.info is None: return None
 		return join(self.info.BASE_DIR, self.info.DATA_DIR)
 
 	@property
+	@only_with_info
 	def dataset_info(self):
-		if self.info is None: return None
-		if self.part_type is None:
-			return self.info.DATASETS[self.__class__.name]
-		else:
-			return self.info.PARTS[self.part_type]
+		return self.info.DATASETS[self.__class__.name]
 
-	def root_from_infofile(self, info_file, parts=None):
+	def root_from_infofile(self, info_file):
 		self.info = read_info_file(info_file)
 
 		dataset_info = self.dataset_info
@@ -85,26 +66,24 @@ class BaseAnnotations(abc.ABC):
 		else:
 			uuids = self.uuids
 
-		kwargs = self.check_parts_and_features(subset, **kwargs)
+		kwargs = self.check_dataset_kwargs(subset, **kwargs)
 		return dataset_cls(uuids=uuids, annotations=self, **kwargs)
 
-	def check_parts_and_features(self, subset, **kwargs):
+	def check_dataset_kwargs(self, subset, **kwargs):
 		dataset_info = self.dataset_info
 		if dataset_info is None:
 			return kwargs
-		logging.debug("Dataset info: {}".format(dataset_info))
+
+		logging.debug("Dataset info: {}".format(pretty_print_dict(dataset_info)))
 
 		# TODO: pass all scales
-		new_opts = {}
+		new_kwargs = {}
 
 		if "scales" in dataset_info:
-			new_opts["ratio"] = dataset_info.scales[0]
+			new_kwargs["ratio"] = dataset_info.scales[0]
 
 		if "is_uniform" in dataset_info:
-			new_opts["uniform_parts"] = dataset_info.is_uniform
-
-		if self.part_type is not None:
-			new_opts["part_rescale_size"] = dataset_info.rescale_size
+			new_kwargs["uniform_parts"] = dataset_info.is_uniform
 
 		if None not in [subset, self.feature_model]:
 			tried = []
@@ -120,15 +99,11 @@ class BaseAnnotations(abc.ABC):
 					join(self.root, "features"), subset, tried))
 
 			logging.info("Using features file from \"{}\"".format(feature_path))
-			new_opts["features"] = feature_path
-		new_opts.update(kwargs)
+			new_kwargs["features"] = feature_path
+		new_kwargs.update(kwargs)
 
-		logging.debug("Final kwargs: {}".format(new_opts))
-		return new_opts
-
-	@property
-	def has_parts(self):
-		return hasattr(self, "_part_locs") and self._part_locs is not None
+		logging.debug("Final kwargs: {}".format(pretty_print_dict(new_kwargs)))
+		return new_kwargs
 
 	@property
 	@abc.abstractmethod
@@ -148,11 +123,21 @@ class BaseAnnotations(abc.ABC):
 			with self._open(file) as f:
 				content = [line.strip() for line in f if line.strip()]
 		else:
-			warnings.warn("File \"{}\" was not found!".format(fpath))
+			msg = "File \"{}\" was not found!".format(fpath)
+			if self.load_strict:
+				raise AssertionError(msg)
+			else:
+				logging.warning(msg)
 
 		setattr(self, attr, content)
 
 
+	def load(self):
+		logging.debug("Loading uuids, labels and training-test split")
+		self._load_uuids()
+		self._load_labels()
+		self._load_split()
+
 	def _load_labels(self):
 		self.labels = np.array([int(l) for l in self.labels], dtype=np.int32)
 
@@ -162,31 +147,6 @@ class BaseAnnotations(abc.ABC):
 		self.uuids, self.images = map(np.array, zip(*uuid_fnames))
 		self.uuid_to_idx = {uuid: i for i, uuid in enumerate(self.uuids)}
 
-	def _load_parts(self):
-		assert self.has_parts, "Part locations were not loaded!"
-		# this part is quite slow... TODO: some runtime improvements?
-		uuid_to_parts = defaultdict(list)
-		for content in [i.split() for i in self._part_locs]:
-			uuid = content[0]
-			# assert uuid in self.uuids, \
-			# 	"Could not find UUID \"\" from part annotations in image annotations!".format(uuid)
-			uuid_to_parts[uuid].append([float(c) for c in content[1:]])
-
-		uuid_to_parts = dict(uuid_to_parts)
-		self.part_locs = np.stack([
-			uuid_to_parts[uuid] for uuid in self.uuids]).astype(int)
-
-		if hasattr(self, "_part_names") and self._part_names is not None:
-			self._load_part_names()
-
-	def _load_part_names(self):
-		self.part_names = OrderedDict()
-		self.part_name_list = []
-		for line in self._part_names:
-			part_idx, _, name = line.partition(" ")
-			self.part_names[int(part_idx)] = name
-			self.part_name_list.append(name)
-
 	def _load_split(self):
 		assert self._split is not None, "Train-test split was not loaded!"
 		uuid_to_split = {uuid: int(split) for uuid, split in zip(self.uuids, self._split)}
@@ -203,10 +163,6 @@ class BaseAnnotations(abc.ABC):
 	def label(self, uuid):
 		return self.labels[self.uuid_to_idx[uuid]].copy()
 
-	def parts(self, uuid):
-		return self.part_locs[self.uuid_to_idx[uuid]].copy()
-
-
 	def _uuids(self, split):
 		return self.uuids[split]
 
@@ -218,3 +174,5 @@ class BaseAnnotations(abc.ABC):
 	def test_uuids(self):
 		return self._uuids(self.test_split)
 
+from .bbox_mixin import BBoxMixin
+from .parts_mixin import PartsMixin

+ 34 - 0
cvdatasets/annotations/base/bbox_mixin.py

@@ -0,0 +1,34 @@
+import abc
+import logging
+import numpy as np
+
+class BBoxMixin(abc.ABC):
+
+	@property
+	def has_bounding_boxes(self):
+		return hasattr(self, "_bounding_boxes") and self._bounding_boxes is not None
+
+	def load(self):
+		super(BBoxMixin, self).load()
+
+		if self.has_bounding_boxes:
+			self._load_bounding_boxes()
+
+	def _load_bounding_boxes(self):
+		logging.debug("Loading bounding box annotations")
+		assert self._bounding_boxes is not None, "Bouding boxes were not loaded!"
+
+		uuid_to_bbox = {}
+		for content in [i.split() for i in self._bounding_boxes]:
+			uuid, bbox = content[0], content[1:]
+			uuid_to_bbox[uuid] = [float(i) for i in bbox]
+
+		self.bounding_boxes = np.array(
+			[tuple(uuid_to_bbox[uuid]) for uuid in self.uuids],
+			dtype=self.meta.bounding_box_dtype)
+
+	def bounding_box(self, uuid):
+		if self.has_bounding_boxes:
+			return self.bounding_boxes[self.uuid_to_idx[uuid]].copy()
+
+		return np.array((0,0, 1,1), dtype=self.meta.bounding_box_dtype)

+ 82 - 0
cvdatasets/annotations/base/parts_mixin.py

@@ -0,0 +1,82 @@
+import abc
+import logging
+import numpy as np
+
+from collections import OrderedDict
+from collections import defaultdict
+from cvdatasets.utils.decorators import only_with_info
+
+class PartsMixin(abc.ABC):
+
+	def __init__(self, *, parts=None, **kwargs):
+		self.part_type = parts
+		self.part_names = OrderedDict()
+		self.part_name_list = []
+
+		super(PartsMixin, self).__init__(**kwargs)
+
+	@property
+	@only_with_info
+	def dataset_info(self):
+		if self.part_type is not None:
+			return self.info.PARTS[self.part_type]
+		else:
+			return super(PartsMixin, self).dataset_info
+
+	def check_dataset_kwargs(self, subset, **kwargs):
+		if self.dataset_info is None:
+			return kwargs
+
+		new_kwargs = {}
+
+		if self.part_type is not None:
+			new_kwargs["part_rescale_size"] = self.dataset_info.rescale_size
+
+		return super(PartsMixin, self).check_dataset_kwargs(subset, **new_kwargs)
+
+	@property
+	def has_parts(self):
+		return hasattr(self, "_part_locs") and self._part_locs is not None
+
+	@property
+	def has_part_names(self):
+		return hasattr(self, "_part_names") and self._part_names is not None
+
+	def load(self):
+		super(PartsMixin, self).load()
+
+		if self.has_parts:
+			self._load_parts()
+
+	def _load_parts(self):
+		logging.debug("Loading part annotations")
+		assert self.has_parts, "Part locations were not loaded!"
+		# this part is quite slow... TODO: some runtime improvements?
+		uuid_to_parts = defaultdict(list)
+		for content in [i.split() for i in self._part_locs]:
+			uuid = content[0]
+			# assert uuid in self.uuids, \
+			# 	"Could not find UUID \"\" from part annotations in image annotations!".format(uuid)
+			uuid_to_parts[uuid].append([float(c) for c in content[1:]])
+
+		uuid_to_parts = dict(uuid_to_parts)
+		self.part_locs = np.stack([
+			uuid_to_parts[uuid] for uuid in self.uuids]).astype(int)
+
+		if self.has_part_names:
+			self._load_part_names()
+
+	def _load_part_names(self):
+		self.part_names.clear()
+		self.part_name_list.clear()
+
+		for line in self._part_names:
+			part_idx, _, name = line.partition(" ")
+			self.part_names[int(part_idx)] = name
+			self.part_name_list.append(name)
+
+	def parts(self, uuid):
+		if self.has_parts:
+			return self.part_locs[self.uuid_to_idx[uuid]].copy()
+
+		return None

+ 0 - 52
cvdatasets/annotations/cub.py

@@ -1,52 +0,0 @@
-import numpy as np
-
-from os.path import join
-
-from cvdatasets.utils import _MetaInfo
-from .base import BaseAnnotations, BBoxMixin
-
-
-class CUB_Annotations(BaseAnnotations, BBoxMixin):
-	name="CUB200"
-
-	@property
-	def meta(self):
-		info = _MetaInfo(
-			images_folder="images",
-			images_file="images.txt",
-			labels_file="labels.txt",
-			split_file="tr_ID.txt",
-			bounding_boxes="bounding_boxes.txt",
-			bounding_box_dtype=np.dtype([(v, np.int32) for v in "xywh"]),
-			parts_file=join("parts", "part_locs.txt"),
-			part_names_file=join("parts", "parts.txt"),
-
-		)
-
-		info.structure = [
-			[info.images_file, "_images"],
-			[info.labels_file, "labels"],
-			[info.split_file, "_split"],
-			[info.parts_file, "_part_locs"],
-			[info.part_names_file, "_part_names"],
-			[info.bounding_boxes, "_bounding_boxes"],
-		]
-		return info
-
-	def __init__(self, *args, **kwargs):
-		super(CUB_Annotations, self).__init__(*args, **kwargs)
-		# set labels from [1..200] to [0..199]
-		self.labels -= 1
-
-	def _load_split(self):
-		assert self._split is not None, "Train-test split was not loaded!"
-		uuid_to_split = {uuid: int(split) for uuid, split in zip(self.uuids, self._split)}
-		self.train_split = np.array([uuid_to_split[uuid] for uuid in self.uuids], dtype=bool)
-		self.test_split = np.logical_not(self.train_split)
-
-	def _load_parts(self):
-		super(CUB_Annotations, self)._load_parts()
-		# set part idxs from 1-idxs to 0-idxs
-		self.part_locs[..., 0] -= 1
-
-		self._load_bounding_boxes()

+ 6 - 18
cvdatasets/annotations/cars.py → cvdatasets/annotations/impl/cars.py

@@ -2,11 +2,13 @@ import numpy as np
 
 from os.path import join
 
+from cvdatasets.annotations.base import BaseAnnotations
+from cvdatasets.annotations.base.bbox_mixin import BBoxMixin
 from cvdatasets.utils import _MetaInfo
-from .base import BaseAnnotations, BBoxMixin
 
 
-class CARS_Annotations(BaseAnnotations, BBoxMixin):
+
+class CARS_Annotations(BBoxMixin, BaseAnnotations):
 	name="CARS"
 
 	@property
@@ -32,21 +34,7 @@ class CARS_Annotations(BaseAnnotations, BBoxMixin):
 		]
 		return info
 
-	def __init__(self, *args, **kwargs):
-		super(CARS_Annotations, self).__init__(*args, **kwargs)
+	def load(self, *args, **kwargs):
+		super(CARS_Annotations, self).load(*args, **kwargs)
 		# set labels from [1..N] to [0..N-1]
 		self.labels -= 1
-
-	def _load_parts(self):
-		self.part_names = {}
-
-		# load only if present
-		if self.has_parts:
-			super(CARS_Annotations, self)._load_parts()
-
-		self._load_bounding_boxes()
-
-	def parts(self, *args, **kwargs):
-		if self.has_parts:
-			return super(CARS_Annotations, self).parts(*args, **kwargs)
-		return None

+ 12 - 18
cvdatasets/annotations/flowers.py → cvdatasets/annotations/impl/cub.py

@@ -2,12 +2,15 @@ import numpy as np
 
 from os.path import join
 
+from cvdatasets.annotations.base import BaseAnnotations
+from cvdatasets.annotations.base.bbox_mixin import BBoxMixin
+from cvdatasets.annotations.base.parts_mixin import PartsMixin
 from cvdatasets.utils import _MetaInfo
-from .base import BaseAnnotations, BBoxMixin
 
 
-class FLOWERS_Annotations(BaseAnnotations, BBoxMixin):
-	name="FLOWERS"
+
+class CUB_Annotations(BBoxMixin, PartsMixin, BaseAnnotations):
+	name="CUB200"
 
 	@property
 	def meta(self):
@@ -20,33 +23,24 @@ class FLOWERS_Annotations(BaseAnnotations, BBoxMixin):
 			bounding_box_dtype=np.dtype([(v, np.int32) for v in "xywh"]),
 			parts_file=join("parts", "part_locs.txt"),
 			part_names_file=join("parts", "parts.txt"),
+
 		)
 
 		info.structure = [
 			[info.images_file, "_images"],
 			[info.labels_file, "labels"],
 			[info.split_file, "_split"],
-			[info.bounding_boxes, "_bounding_boxes"],
 			[info.parts_file, "_part_locs"],
 			[info.part_names_file, "_part_names"],
+			[info.bounding_boxes, "_bounding_boxes"],
 		]
 		return info
 
-	def __init__(self, *args, **kwargs):
-		super(FLOWERS_Annotations, self).__init__(*args, **kwargs)
+	def load(self):
+		super(CUB_Annotations, self).load()
 		# set labels from [1..200] to [0..199]
 		self.labels -= 1
 
-	def _load_parts(self):
-		self.part_names = {}
-
-		# load only if present
-		if self.has_parts:
-			super(FLOWERS_Annotations, self)._load_parts()
-
-		self._load_bounding_boxes()
-
-	def parts(self, *args, **kwargs):
 		if self.has_parts:
-			return super(FLOWERS_Annotations, self).parts(*args, **kwargs)
-		return None
+			# set part idxs from 1-idxs to 0-idxs
+			self.part_locs[..., 0] -= 1

+ 5 - 17
cvdatasets/annotations/dogs.py → cvdatasets/annotations/impl/dogs.py

@@ -2,11 +2,14 @@ import numpy as np
 
 from os.path import join
 
+from cvdatasets.annotations.base import BaseAnnotations
+from cvdatasets.annotations.base.bbox_mixin import BBoxMixin
+from cvdatasets.annotations.base.parts_mixin import PartsMixin
 from cvdatasets.utils import _MetaInfo
-from .base import BaseAnnotations, BBoxMixin
 
 
-class DOGS_Annotations(BaseAnnotations, BBoxMixin):
+
+class DOGS_Annotations(BBoxMixin, PartsMixin, BaseAnnotations):
 	name="DOGS"
 
 	@property
@@ -32,18 +35,3 @@ class DOGS_Annotations(BaseAnnotations, BBoxMixin):
 			# [info.part_names_file, "_part_names"],
 		]
 		return info
-
-
-	def parts(self, *args, **kwargs):
-		if self.has_parts:
-			return super(DOGS_Annotations, self).parts(*args, **kwargs)
-		return None
-
-	def _load_parts(self):
-		self.part_names = {}
-
-		# load only if present
-		if self.has_parts:
-			super(DOGS_Annotations, self)._load_parts()
-
-		self._load_bounding_boxes()

+ 41 - 0
cvdatasets/annotations/impl/flowers.py

@@ -0,0 +1,41 @@
+import numpy as np
+
+from os.path import join
+
+from cvdatasets.annotations.base import BaseAnnotations
+from cvdatasets.annotations.base.bbox_mixin import BBoxMixin
+from cvdatasets.annotations.base.parts_mixin import PartsMixin
+from cvdatasets.utils import _MetaInfo
+
+
+
+class FLOWERS_Annotations(BBoxMixin, PartsMixin, BaseAnnotations):
+	name="FLOWERS"
+
+	@property
+	def meta(self):
+		info = _MetaInfo(
+			images_folder="images",
+			images_file="images.txt",
+			labels_file="labels.txt",
+			split_file="tr_ID.txt",
+			bounding_boxes="bounding_boxes.txt",
+			bounding_box_dtype=np.dtype([(v, np.int32) for v in "xywh"]),
+			parts_file=join("parts", "part_locs.txt"),
+			part_names_file=join("parts", "parts.txt"),
+		)
+
+		info.structure = [
+			[info.images_file, "_images"],
+			[info.labels_file, "labels"],
+			[info.split_file, "_split"],
+			[info.bounding_boxes, "_bounding_boxes"],
+			[info.parts_file, "_part_locs"],
+			[info.part_names_file, "_part_names"],
+		]
+		return info
+
+	def load(self, *args, **kwargs):
+		super(FLOWERS_Annotations, self).load(*args, **kwargs)
+		# set labels from [1..120] to [0..119]
+		self.labels -= 1

+ 4 - 16
cvdatasets/annotations/hed.py → cvdatasets/annotations/impl/hed.py

@@ -3,11 +3,13 @@ import simplejson as json
 
 from os.path import join
 
+from cvdatasets.annotations.base import BaseAnnotations
+from cvdatasets.annotations.base.bbox_mixin import BBoxMixin
+from cvdatasets.annotations.base.parts_mixin import PartsMixin
 from cvdatasets.utils import _MetaInfo
 
-from .base import BaseAnnotations, BBoxMixin
 
-class HED_Annotations(BaseAnnotations, BBoxMixin):
+class HED_Annotations(BBoxMixin, PartsMixin, BaseAnnotations):
 
 	name="HED"
 
@@ -33,11 +35,6 @@ class HED_Annotations(BaseAnnotations, BBoxMixin):
 		]
 		return info
 
-	def parts(self, *args, **kwargs):
-		if self.has_parts:
-			return super(HED_Annotations, self).parts(*args, **kwargs)
-		return None
-
 	def _load_bounding_boxes(self):
 		self.bounding_boxes = np.zeros(len(self.uuids),
 			dtype=self.meta.bounding_box_dtype)
@@ -45,12 +42,3 @@ class HED_Annotations(BaseAnnotations, BBoxMixin):
 		for i in range(len(self.uuids)):
 			self.bounding_boxes[i]["w"] = 224
 			self.bounding_boxes[i]["h"] = 224
-
-	def _load_parts(self):
-		self.part_names = {}
-
-		# load only if present
-		if self.has_parts:
-			super(HED_Annotations, self)._load_parts()
-
-		self._load_bounding_boxes()

+ 4 - 18
cvdatasets/annotations/inat.py → cvdatasets/annotations/impl/inat.py

@@ -3,11 +3,13 @@ import simplejson as json
 
 from os.path import join
 
+from cvdatasets.annotations.base import BaseAnnotations
+from cvdatasets.annotations.base.bbox_mixin import BBoxMixin
+from cvdatasets.annotations.base.parts_mixin import PartsMixin
 from cvdatasets.utils import _MetaInfo
 
-from .base import BaseAnnotations
 
-class INAT19_Annotations(BaseAnnotations):
+class INAT19_Annotations(BBoxMixin, PartsMixin, BaseAnnotations):
 
 	name="INAT19"
 
@@ -41,13 +43,6 @@ class INAT19_Annotations(BaseAnnotations):
 			content = json.load(f)
 			setattr(self, attr, content)
 
-	def parts(self, *args, **kwargs):
-		if self.has_parts:
-			return super(INAT19_Annotations, self).parts(*args, **kwargs)
-		return None
-
-	def bounding_box(self, uuid):
-		return self.bounding_boxes[self.uuid_to_idx[uuid]].copy()
 
 	def _load_bounding_boxes(self):
 		self.bounding_boxes = np.zeros(len(self.uuids), dtype=self.meta.bounding_box_dtype)
@@ -56,15 +51,6 @@ class INAT19_Annotations(BaseAnnotations):
 			self.bounding_boxes[i]["w"] = im["width"]
 			self.bounding_boxes[i]["h"] = im["height"]
 
-	def _load_parts(self):
-		self.part_names = {}
-
-		# load only if present
-		if self.has_parts:
-			super(INAT19_Annotations, self)._load_parts()
-
-		self._load_bounding_boxes()
-
 	def _load_split(self):
 		self.train_split = np.ones(len(self.uuids), dtype=bool)
 		val_uuids = [str(im["id"]) for im in self._val_content["images"]]

+ 4 - 2
cvdatasets/annotations/nab.py → cvdatasets/annotations/impl/nab.py

@@ -2,11 +2,13 @@ import numpy as np
 
 from os.path import join
 
+from cvdatasets.annotations.base import BaseAnnotations
+from cvdatasets.annotations.base.bbox_mixin import BBoxMixin
+from cvdatasets.annotations.base.parts_mixin import PartsMixin
 from cvdatasets.utils import _MetaInfo
-from .base import BaseAnnotations
 
 
-class NAB_Annotations(BaseAnnotations):
+class NAB_Annotations(BBoxMixin, PartsMixin, BaseAnnotations):
 	name="NABirds"
 
 	@property

+ 19 - 25
cvdatasets/annotations/tigers.py → cvdatasets/annotations/impl/tigers.py

@@ -1,20 +1,20 @@
 import numpy as np
 import simplejson as json
 
-from cvdatasets.utils import _MetaInfo
-from .base import BaseAnnotations
-
-from os.path import join, isfile
-
+from os.path import isfile
+from os.path import join
 from sklearn.model_selection import StratifiedShuffleSplit
 
+from cvdatasets.annotations.base import BaseAnnotations
+from cvdatasets.utils import _MetaInfo
+
 class TIGERS_Annotations(BaseAnnotations):
 	name="tigers"
 
 	@property
 	def meta(self):
 		info = _MetaInfo(
-			images_folder="train",
+			images_folder="images",
 			images_file=join("atrw_anno_reid_train", "reid_list_train.csv"),
 			parts_file=join("atrw_anno_reid_train", "reid_keypoints_train.json"),
 		)
@@ -32,8 +32,8 @@ class TIGERS_Annotations(BaseAnnotations):
 
 		for i, line in enumerate(self._images):
 			cls_id, imname = line.split(",")
-			self.uuids.append(str(i))
-			self.uuid_to_idx[str(i)] = i
+			self.uuids.append(imname)
+			self.uuid_to_idx[imname] = i
 
 			self.images.append(imname)
 			self.cls_ids.append(int(cls_id))
@@ -43,6 +43,16 @@ class TIGERS_Annotations(BaseAnnotations):
 	def _load_labels(self):
 		self.classes, self.labels = np.unique(self.cls_ids, return_inverse=True)
 
+	def _load_split(self, seed=4211):
+
+		splitter = StratifiedShuffleSplit(n_splits=1, test_size=0.3, random_state=seed)
+
+		(train_IDs, test_IDs), = splitter.split(X=self.uuids, y=self.labels)
+
+		self.train_split = np.zeros_like(self.uuids, dtype=bool)
+
+		self.train_split[train_IDs] = 1
+		self.test_split = np.logical_not(self.train_split)
 
 	def _load_parts(self):
 		keypoints = []
@@ -55,27 +65,11 @@ class TIGERS_Annotations(BaseAnnotations):
 			keypoints.append(kpts)
 
 		self.part_locs = np.array(keypoints)
-
+		self.part_locs = np.minimum(self.part_locs, 0)
 		n_parts = self.part_locs.shape[1]
 		self._part_names = [f"{i} part #{i}" for i in range(n_parts)]
 		self._load_part_names()
 
-	def _load_split(self, seed=4211):
-
-		splitter = StratifiedShuffleSplit(n_splits=1, test_size=0.3, random_state=seed)
-
-		(train_IDs, test_IDs), = splitter.split(X=self.uuids, y=self.labels)
-
-		self.train_split = np.zeros_like(self.uuids, dtype=bool)
-
-		self.train_split[train_IDs] = 1
-		self.test_split = np.logical_not(self.train_split)
-
-	def parts(self, *args, **kwargs):
-		if self.has_parts:
-			return super(TIGERS_Annotations, self).parts(*args, **kwargs)
-		return None
-
 	def read_content(self, file, attr):
 		if not file.endswith(".json"):
 			return super(TIGERS_Annotations, self).read_content(file, attr)

+ 2 - 2
cvdatasets/dataset/mixins/features.py

@@ -13,8 +13,8 @@ class PreExtractedFeaturesMixin(BaseMixin):
 				len(self.features), len(self)
 			)
 
-	def __init__(self, features=None, *args, **kw):
-		super(PreExtractedFeaturesMixin, self).__init__(*args, **kw)
+	def __init__(self, *, features=None, **kw):
+		super(PreExtractedFeaturesMixin, self).__init__(**kw)
 
 		self.features = None
 		if features is not None and isfile(features):

+ 2 - 2
cvdatasets/dataset/mixins/parts.py

@@ -4,8 +4,8 @@ from . import BaseMixin
 
 class BBCropMixin(BaseMixin):
 
-	def __init__(self, crop_to_bb=False, crop_uniform=False, *args, **kwargs):
-		super(BBCropMixin, self).__init__(*args, **kwargs)
+	def __init__(self, *, crop_to_bb=False, crop_uniform=False, **kwargs):
+		super(BBCropMixin, self).__init__(**kwargs)
 		self.crop_to_bb = crop_to_bb
 		self.crop_uniform = crop_uniform
 

+ 2 - 2
cvdatasets/dataset/mixins/reading.py

@@ -7,7 +7,7 @@ from ..image import ImageWrapper
 
 class AnnotationsReadMixin(BaseMixin):
 
-	def __init__(self, uuids, annotations, part_rescale_size=None, center_cropped=True, mode="RGB"):
+	def __init__(self, *, uuids, annotations, part_rescale_size=None, center_cropped=True, mode="RGB"):
 		super(AnnotationsReadMixin, self).__init__()
 		self.uuids = uuids
 		self._annot = annotations
@@ -45,7 +45,7 @@ class AnnotationsReadMixin(BaseMixin):
 
 class ImageListReadingMixin(BaseMixin):
 
-	def __init__(self, pairs, root="."):
+	def __init__(self, *, pairs, root="."):
 		super(ImageListReadingMixin, self).__init__()
 		with open(pairs) as f:
 			self._pairs = [line.strip().split() for line in f]

+ 4 - 0
cvdatasets/utils/__init__.py

@@ -9,6 +9,10 @@ import yaml
 
 from os.path import isfile
 
+def pretty_print_dict(dictionary):
+	return ", ".join(["{key}={value}".format(key=key, value=value)
+		for key, value in dictionary.items()])
+
 def feature_file_name(subset, part_info, model_info):
 
 	return "{subset}{suffix}.{model}.npz".format(

+ 9 - 0
cvdatasets/utils/decorators.py

@@ -0,0 +1,9 @@
+
+class only_with_info(object):
+
+	def __init__(self, method):
+		self.method = method
+
+	def __call__(self, obj, *args, **kwargs):
+		if obj.info is None: return None
+		return self.method(obj, *args, **kwargs)

+ 5 - 1
scripts/display.py

@@ -19,7 +19,11 @@ def main(args):
 	annotation_cls = AnnotationType[args.dataset].value
 
 	logging.info(f"Loading \"{args.dataset}\" annnotations from \"{args.data}\"")
-	annot = annotation_cls(args.data, args.parts, args.feature_model)
+	annot = annotation_cls(
+		root_or_infofile=args.data,
+		feature_model=args.feature_model,
+		load_strict=False,
+		parts=args.parts)
 
 	kwargs = {}
 	if annot.info is None:

+ 24 - 1
scripts/info_files/info.yml

@@ -60,6 +60,12 @@ DATASETS:
     annotations: "ORIGINAL"
     n_classes: 200
 
+  CUB200_GOOGLE: &cub200_google
+    folder: birds/cub200_google_images
+    annotations: "ORIGINAL"
+    n_classes: 200
+
+
   NAB:         &nabirds
     folder: birds/nabirds
     annotations: "ORIGINAL"
@@ -101,7 +107,12 @@ DATASETS:
 
   TIGERS:         &tigers
     folder: tigers
-    annotations: "ORIGINAL"
+    annotations: "reid/train"
+    n_classes: 107
+
+  TIGERS_TEST:         &tigers_test
+    folder: tigers
+    annotations: "reid/test"
     n_classes: 107
 
 ############ Existing Part Annotations and Part Features
@@ -161,6 +172,11 @@ PARTS:
     <<: *cub200
     <<: *parts_global
 
+  CUB200_GOOGLE_GLOBAL:
+    <<: *cub200_google
+    <<: *parts_global
+
+
   CARS_GLOBAL:
     <<: *cars
     <<: *parts_global
@@ -198,6 +214,13 @@ PARTS:
   TIGERS_GLOBAL:
     <<: *tigers
     <<: *parts_global
+    rescale_size: !!int -1
+    scales:
+      - .31
+
+  TIGERS_TEST_GLOBAL:
+    <<: *tigers_test
+    <<: *parts_global
 
   #### With Parts Annotations