Browse Source

created another way of reading annotations

Dimitri Korsch 6 years ago
parent
commit
c1938054cb
2 changed files with 186 additions and 30 deletions
  1. 82 25
      nabirds/annotations/base.py
  2. 104 5
      scripts/display_from_info.py

+ 82 - 25
nabirds/annotations/base.py

@@ -10,29 +10,98 @@ except ImportError:
 	from yaml import Loader, Dumper
 
 import yaml
+import simplejson as json
 
 from nabirds.utils import attr_dict
+from nabirds.dataset import Dataset
 
 class BaseAnnotations(abc.ABC):
+
+	def __init__(self, root_or_infofile, parts=None, feature_model=None):
+		super(BaseAnnotations, self).__init__()
+		self.part_type = parts
+		self.feature_model = feature_model
+
+		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, feature_model)
+		else:
+			raise ValueError("Root folder or info file does not exist: \"{}\"".format(
+				root_or_infofile
+			))
+
+		for fname, attr in self.meta.structure:
+			self.read_content(fname, attr)
+
+		self.labels = np.array([int(l) for l in self.labels], dtype=np.int32)
+
+		self._load_uuids()
+		self._load_parts()
+		self._load_split()
+
 	@property
-	@abc.abstractmethod
-	def meta(self):
-		pass
+	def data_root(self):
+		if self.info is None: return None
+		return join(self.info.BASE_DIR, self.info.DATA_DIR)
+
+	@property
+	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]
 
-	@classmethod
-	def from_info_file(cls, info_file, parts=None, feature_model=None):
+	def root_from_infofile(self, info_file, parts=None, feature_model=None):
 		with open(info_file) as f:
-			info = attr_dict(yaml.load(f, Loader=Loader))
+			self.info = attr_dict(yaml.load(f, Loader=Loader))
 
-		data_root = join(info.BASE_DIR, info.DATA_DIR)
-		if parts is None:
-			dataset_info = info.DATASETS[cls.name]
-		else:
-			dataset_info = info.PARTS[parts]
-		annot_dir = join(data_root, dataset_info.folder, dataset_info.annotations)
+		dataset_info = self.dataset_info
+		# print(json.dumps(dataset_info, indent=2))
+		annot_dir = join(self.data_root, dataset_info.folder, dataset_info.annotations)
 
 		assert isdir(annot_dir), "Annotation folder does exist! \"{}\"".format(annot_dir)
-		return cls(annot_dir, parts=info, feature_model...)
+		return annot_dir
+
+	def new_dataset(self, subset=None, dataset_cls=Dataset, **kwargs):
+		if subset is not None:
+			uuids = getattr(self, "{}_uuids".format(subset))
+		else:
+			uuids = self.uuids
+
+		kwargs = self.check_parts_and_features(subset, **kwargs)
+		return dataset_cls(uuids=uuids, annotations=self, **kwargs)
+
+	def check_parts_and_features(self, subset, **kwargs):
+		dataset_info = self.dataset_info
+		# TODO: pass all scales
+		new_opts = {
+			"ratio": dataset_info.scales[0]
+		}
+
+		if self.part_type is not None:
+			new_opts["part_rescale_size"] = dataset_info.rescale_size
+
+		if None not in [subset, self.feature_model]:
+			features = "{subset}_{suffix}.{model}.npz".format(
+				subset=subset,
+				suffix=dataset_info.feature_suffix,
+				model=self.feature_model)
+			feature_path = join(self.root, "features", features)
+			assert isfile(feature_path), \
+				"Features do not exist: \"{}\"".format(feature_path)
+			new_opts["features"] = feature_path
+		new_opts.update(kwargs)
+
+		print(new_opts)
+		return new_opts
+
+	@property
+	@abc.abstractmethod
+	def meta(self):
+		pass
 
 	def _path(self, file):
 		return join(self.root, file)
@@ -51,18 +120,6 @@ class BaseAnnotations(abc.ABC):
 
 		setattr(self, attr, content)
 
-	def __init__(self, root):
-		super(BaseAnnotations, self).__init__()
-		self.root = root
-
-		for fname, attr in self.meta.structure:
-			self.read_content(fname, attr)
-
-		self.labels = np.array([int(l) for l in self.labels], dtype=np.int32)
-
-		self._load_uuids()
-		self._load_parts()
-		self._load_split()
 
 	def _load_uuids(self):
 		assert self._images is not None, "Images were not loaded!"

+ 104 - 5
scripts/display_from_info.py

@@ -10,9 +10,13 @@ except ImportError:
 
 import yaml
 import logging
+import numpy as np
+import matplotlib.pyplot as plt
+
 from argparse import ArgumentParser
 
 from nabirds import CUB_Annotations, Dataset
+from nabirds.dataset import utils
 
 
 def init_logger(args):
@@ -24,22 +28,85 @@ def init_logger(args):
 		filemode="w")
 
 
+def plot_crops(crops, title, scatter_mid=False, names=None):
+
+	n_crops = crops.shape[0]
+	rows = int(np.ceil(np.sqrt(n_crops)))
+	cols = int(np.ceil(n_crops / rows))
+
+	fig, axs = plt.subplots(rows, cols, figsize=(16,9))
+	fig.suptitle(title, fontsize=16)
+	for i, crop in enumerate(crops):
+		ax = axs[np.unravel_index(i, axs.shape)]
+		if names is not None:
+			ax.set_title(names[i])
+		ax.imshow(crop)
+		ax.axis("off")
+		if scatter_mid:
+			middle_h, middle_w = crop.shape[0] / 2, crop.shape[1] / 2
+			ax.scatter(middle_w, middle_h, marker="x")
+
+
 def main(args):
 	init_logger(args)
 
-	annot = CUB_Annotations.from_info_file(
+	annot = CUB_Annotations(
 		args.info, args.parts, args.feature_model)
+
 	logging.info("Loaded data from \"{}\"".format(annot.root))
 
 	uuids = getattr(annot, "{}_uuids".format(args.subset))
 
-	data = Dataset(
-		uuids=uuids, annotations=annot,
+	data = annot.new_dataset(
+		args.subset,
+
+		uniform_parts=args.uniform_parts,
+
+		crop_to_bb=args.crop_to_bb,
+		crop_uniform=args.crop_uniform,
+
+		parts_in_bb=args.parts_in_bb,
+
+		rnd_select=args.rnd,
 		seed=args.seed
 	)
 
 	logging.info("Loaded {} {} images".format(len(data), args.subset))
 
+	start = max(args.start, 0)
+	n_images = min(args.n_images, len(data) - start)
+
+	for i in range(start, max(start, start + n_images)):
+		im, parts, label = data[i]
+
+		idxs, xy = utils.visible_part_locs(parts)
+		part_crops = utils.visible_crops(im, parts, ratio=data.ratio)
+		if args.rnd:
+			selected = parts[:, -1].astype(bool)
+			parts[selected, -1] = 0
+			parts[np.logical_not(selected), -1] = 1
+			action_crops = utils.visible_crops(im, parts, ratio=data.ratio)
+
+		fig1 = plt.figure(figsize=(16,9))
+		ax = fig1.add_subplot(2,1,1)
+		ax.imshow(im)
+		ax.set_title("Visible Parts")
+		ax.scatter(*xy, marker="x", c=idxs)
+		ax.axis("off")
+
+		ax = fig1.add_subplot(2,1,2)
+		ax.set_title("{}selected parts".format("randomly " if args.rnd else ""))
+		ax.imshow(utils.reveal_parts(im, xy, ratio=data.ratio))
+		# ax.scatter(*xy, marker="x", c=idxs)
+		ax.axis("off")
+		crop_names = list(data._annot.part_names.values())
+		plot_crops(part_crops, "Selected parts", names=crop_names)
+
+		if args.rnd:
+			plot_crops(action_crops, "Actions")
+
+		plt.show()
+		plt.close()
 
 
 parser = ArgumentParser()
@@ -50,15 +117,47 @@ parser.add_argument("--parts", "-p",
 	choices=["GT", "GT2", "NAC", "L1_pred", "L1_full"]
 )
 
-parser.add_argument("--feature_model",
+parser.add_argument("--feature_model", "-fm",
 	choices=["inception", "inception_tf", "resnet"]
 )
 
-parser.add_argument("--subset", "-s",
+parser.add_argument("--subset", "-sub",
 	help="Possible subsets: train, test",
 	choices=["train", "test"],
 	default="train", type=str)
 
+
+parser.add_argument("--start", "-s",
+	help="Image id to start with",
+	type=int, default=0)
+
+parser.add_argument("--n_images", "-n",
+	help="Number of images to display",
+	type=int, default=10)
+
+
+
+parser.add_argument("--rnd",
+	help="select random subset of present parts",
+	action="store_true")
+
+parser.add_argument("--uniform_parts", "-u",
+	help="Do not use GT parts, but sample parts uniformly from the image",
+	action="store_true")
+
+parser.add_argument("--crop_to_bb",
+	help="Crop image to the bounding box",
+	action="store_true")
+
+parser.add_argument("--crop_uniform",
+	help="Try to extend the bounding box to same height and width",
+	action="store_true")
+
+parser.add_argument("--parts_in_bb",
+	help="Only display parts, that are inside the bounding box",
+	action="store_true")
+
+
 parser.add_argument(
 	'--logfile', type=str, default='',
 	help='File for logging output')