From 8fc59135799f63005afbd51be248bdac1e9a4453 Mon Sep 17 00:00:00 2001 From: sallysyw Date: Sun, 9 Jan 2022 01:19:52 +0000 Subject: [PATCH 01/10] add fvgc_aircraft dataset --- test/test_datasets.py | 46 ++++++++++ torchvision/datasets/__init__.py | 2 + torchvision/datasets/fvgc_aircraft.py | 120 ++++++++++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 torchvision/datasets/fvgc_aircraft.py diff --git a/test/test_datasets.py b/test/test_datasets.py index 0787a6f8e1d..debb87bd3a1 100644 --- a/test/test_datasets.py +++ b/test/test_datasets.py @@ -2206,6 +2206,52 @@ def inject_fake_data(self, tmpdir: str, config): return len(sampled_classes * n_samples_per_class) +class FVGCAircraftTestCase(datasets_utils.ImageDatasetTestCase): + DATASET_CLASS = datasets.FVGCAircraft + FEATURE_TYPES = (PIL.Image.Image, int) + + ADDITIONAL_CONFIGS = datasets_utils.combinations_grid(split=("train", "val", "trainval", "test")) + + def inject_fake_data(self, tmpdir: str, config): + split = config["split"] + root_folder = pathlib.Path(tmpdir) / "fgvc-aircraft-2013b" + data_folder = root_folder / "data" + + num_images_per_class = 5 + variants = ["707-320", "Hawk T1", "Tornado"] + n_samples_per_class = 4 if split == "trainval" else 2 + + datasets_utils.create_image_folder( + data_folder, + "images", + file_name_fn=lambda idx: f"{idx}.jpg", + num_examples=num_images_per_class * len(variants), + ) + + images_variants = [] + for i in range(len(variants)): + variant = variants[i] + images_variants.extend( + [ + f"{idx} {variant}" + for idx in random.sample( + range(i * num_images_per_class, (i + 1) * num_images_per_class), n_samples_per_class + ) + ] + ) + + varients_file = root_folder / "data" / "variants.txt" + images_variant_file = root_folder / "data" / f"images_variant_{split}.txt" + + with open(varients_file, "w") as file: + file.write("\n".join(variants)) + + with open(images_variant_file, "w") as file: + file.write("\n".join(images_variants)) + + return len(variants * n_samples_per_class) + + class DTDTestCase(datasets_utils.ImageDatasetTestCase): DATASET_CLASS = datasets.DTD FEATURE_TYPES = (PIL.Image.Image, int) diff --git a/torchvision/datasets/__init__.py b/torchvision/datasets/__init__.py index 57c00b8554d..b1f1dcf68c2 100644 --- a/torchvision/datasets/__init__.py +++ b/torchvision/datasets/__init__.py @@ -11,6 +11,7 @@ from .flickr import Flickr8k, Flickr30k from .folder import ImageFolder, DatasetFolder from .food101 import Food101 +from .fvgc_aircraft import FVGCAircraft from .gtsrb import GTSRB from .hmdb51 import HMDB51 from .imagenet import ImageNet @@ -89,4 +90,5 @@ "GTSRB", "CLEVRClassification", "OxfordIIITPet", + "FVGCAircraft", ) diff --git a/torchvision/datasets/fvgc_aircraft.py b/torchvision/datasets/fvgc_aircraft.py new file mode 100644 index 00000000000..8a2f82c82a1 --- /dev/null +++ b/torchvision/datasets/fvgc_aircraft.py @@ -0,0 +1,120 @@ +import os +import shutil +from typing import Any, Callable, List, Optional, Tuple + +import PIL.Image +import tqdm + +from .utils import download_and_extract_archive, verify_str_arg +from .vision import VisionDataset + + +class FVGCAircraft(VisionDataset): + """`FVGC Aircraft `_ Dataset. + + The dataset contains 10,200 images of aircraft, with 100 images for each of 102 + different aircraft model variants, most of which are airplanes. + + Args: + root (string): Root directory of the FVGC Aircraft dataset. + split (string, optional): The dataset split, supports ``train``, ``val``, + ``trainval`` and ``test``. + download (bool, optional): If True, downloads the dataset from the internet and + puts it in root directory. If dataset is already downloaded, it is not + downloaded again. + transform (callable, optional): A function/transform that takes in an PIL image + and returns a transformed version. E.g, ``transforms.RandomCrop`` + target_transform (callable, optional): A function/transform that takes in the + target and transforms it. + """ + + _URL = "https://www.robots.ox.ac.uk/~vgg/data/fgvc-aircraft/archives/" + _URL_FILE = "fgvc-aircraft-2013b.tar.gz" + + def __init__( + self, + root: str, + split: str = "trainval", + download: Optional[str] = None, + transform: Optional[Callable] = None, + target_transform: Optional[Callable] = None, + **kwargs: Any, + ) -> None: + super().__init__(root, transform=transform, target_transform=target_transform) + self._split = verify_str_arg(split, "split", ("train", "val", "trainval", "test")) + + self._data_path = os.path.join(self.root, "fgvc-aircraft-2013b") + if download: + self._download() + + if not self._check_exists(): + raise RuntimeError("Dataset not found. You can use download=True to download it") + + self._label_names = sorted(self._get_label_names(self._data_path)) + + # Parse the downloaded files + self._image_folder = os.path.join(self.root, self._split) + self._create_fgvc_aircrafts_disk_folder(self._data_path) + + self._label_name_to_idx = dict(zip(self._label_names, range(len(self._label_names)))) + + self._image_files = [] + self._labels = [] + for label_name in self._label_names: + img_rel_folder = os.path.join(self._image_folder, label_name) + img_file_name_list = [ + f for f in os.listdir(img_rel_folder) if os.path.isfile(os.path.join(img_rel_folder, f)) + ] + self._labels += [self._label_name_to_idx[label_name]] * len(img_file_name_list) + self._image_files += [os.path.join(img_rel_folder, img_name) for img_name in img_file_name_list] + + def __len__(self) -> int: + return len(self._image_files) + + def __getitem__(self, idx) -> Tuple[Any, Any]: + image_file, label = self._image_files[idx], self._labels[idx] + image = PIL.Image.open(image_file).convert("RGB") + + if self.transform: + image = self.transform(image) + + if self.target_transform: + label = self.target_transform(label) + + return image, label + + def _download(self): + """ + Download the FGVC Aircraft dataset archive and extract it under root. + """ + if self._check_exists(): + return + download_and_extract_archive(self._URL + self._URL_FILE, self.root) + + def _check_exists(self) -> bool: + return os.path.exists(self._data_path) and os.path.isdir(self._data_path) + + def _create_fgvc_aircrafts_disk_folder(self, input_path: str): + img_data_folder = os.path.join(input_path, "data", "images") + labels_path = os.path.join(input_path, "data", f"images_variant_{self._split}.txt") + for label in self._label_names: + os.makedirs(os.path.join(self._image_folder, label), exist_ok=True) + + with open(labels_path, "r") as labels_file: + lines = [line.strip() for line in labels_file] + for line in lines: + line = line.split(" ") + image_name = line[0] + label_name = self._parse_aircraft_name(" ".join(line[1:])) + shutil.copy( + src=os.path.join(img_data_folder, f"{image_name}.jpg"), + dst=os.path.join(self._image_folder, label_name), + ) + + def _get_label_names(self, input_path: str) -> List[str]: + variants_file = os.path.join(input_path, "data", "variants.txt") + with open(variants_file, "r") as f: + return [self._parse_aircraft_name(line.strip()) for line in f] + + def _parse_aircraft_name(self, name: str) -> str: + return name.replace("/", "-").replace(" ", "-") From 3a057035e0d82c325fbb7bda499e684f1ef2f022 Mon Sep 17 00:00:00 2001 From: sallysyw Date: Sun, 9 Jan 2022 01:32:18 +0000 Subject: [PATCH 02/10] add docstring & remove useless import --- docs/source/datasets.rst | 1 + torchvision/datasets/fvgc_aircraft.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/datasets.rst b/docs/source/datasets.rst index 1b31fd5ee0d..2b4ce5e5271 100644 --- a/docs/source/datasets.rst +++ b/docs/source/datasets.rst @@ -48,6 +48,7 @@ You can also create your own datasets using the provided :ref:`base classes Date: Sun, 9 Jan 2022 07:32:48 +0000 Subject: [PATCH 03/10] resolve lint issue --- test/test_datasets.py | 6 +++--- torchvision/datasets/fvgc_aircraft.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/test_datasets.py b/test/test_datasets.py index 7c39cf69043..63a75511e75 100644 --- a/test/test_datasets.py +++ b/test/test_datasets.py @@ -2205,7 +2205,7 @@ def inject_fake_data(self, tmpdir: str, config): return len(sampled_classes * n_samples_per_class) - + class FVGCAircraftTestCase(datasets_utils.ImageDatasetTestCase): DATASET_CLASS = datasets.FVGCAircraft FEATURE_TYPES = (PIL.Image.Image, int) @@ -2251,7 +2251,7 @@ def inject_fake_data(self, tmpdir: str, config): return len(variants * n_samples_per_class) - + class SUN397TestCase(datasets_utils.ImageDatasetTestCase): DATASET_CLASS = datasets.SUN397 @@ -2296,7 +2296,7 @@ def inject_fake_data(self, tmpdir: str, config): num_samples = len(im_paths) return num_samples - + class DTDTestCase(datasets_utils.ImageDatasetTestCase): DATASET_CLASS = datasets.DTD diff --git a/torchvision/datasets/fvgc_aircraft.py b/torchvision/datasets/fvgc_aircraft.py index a08463f1676..286165bdfcc 100644 --- a/torchvision/datasets/fvgc_aircraft.py +++ b/torchvision/datasets/fvgc_aircraft.py @@ -102,9 +102,9 @@ def _create_fgvc_aircrafts_disk_folder(self, input_path: str): with open(labels_path, "r") as labels_file: lines = [line.strip() for line in labels_file] for line in lines: - line = line.split(" ") - image_name = line[0] - label_name = self._parse_aircraft_name(" ".join(line[1:])) + line_list = line.split(" ") + image_name = line_list[0] + label_name = self._parse_aircraft_name(" ".join(line_list[1:])) shutil.copy( src=os.path.join(img_data_folder, f"{image_name}.jpg"), dst=os.path.join(self._image_folder, label_name), From fedb68a22170ac4b75b349871287db71ef3ba103 Mon Sep 17 00:00:00 2001 From: sallysyw Date: Wed, 12 Jan 2022 06:16:21 +0000 Subject: [PATCH 04/10] address comments --- docs/source/datasets.rst | 2 +- test/test_datasets.py | 14 +++---- torchvision/datasets/__init__.py | 4 +- .../{fvgc_aircraft.py => fgvc_aircraft.py} | 37 ++++++------------- 4 files changed, 22 insertions(+), 35 deletions(-) rename torchvision/datasets/{fvgc_aircraft.py => fgvc_aircraft.py} (73%) diff --git a/docs/source/datasets.rst b/docs/source/datasets.rst index 5eb2051372d..0b6af61bd9e 100644 --- a/docs/source/datasets.rst +++ b/docs/source/datasets.rst @@ -48,7 +48,7 @@ You can also create your own datasets using the provided :ref:`base classes `_ Dataset. +class FGVCAircraft(VisionDataset): + """`FGVC Aircraft `_ Dataset. The dataset contains 10,200 images of aircraft, with 100 images for each of 102 different aircraft model variants, most of which are airplanes. Args: - root (string): Root directory of the FVGC Aircraft dataset. + root (string): Root directory of the FGVC Aircraft dataset. split (string, optional): The dataset split, supports ``train``, ``val``, ``trainval`` and ``test``. download (bool, optional): If True, downloads the dataset from the internet and @@ -34,10 +33,9 @@ def __init__( self, root: str, split: str = "trainval", - download: Optional[str] = None, + download: bool = False, transform: Optional[Callable] = None, target_transform: Optional[Callable] = None, - **kwargs: Any, ) -> None: super().__init__(root, transform=transform, target_transform=target_transform) self._split = verify_str_arg(split, "split", ("train", "val", "trainval", "test")) @@ -49,23 +47,16 @@ def __init__( if not self._check_exists(): raise RuntimeError("Dataset not found. You can use download=True to download it") - self._label_names = sorted(self._get_label_names(self._data_path)) + self._label_names = self._get_label_names(self._data_path) # Parse the downloaded files self._image_folder = os.path.join(self.root, self._split) - self._create_fgvc_aircrafts_disk_folder(self._data_path) - - self._label_name_to_idx = dict(zip(self._label_names, range(len(self._label_names)))) self._image_files = [] self._labels = [] - for label_name in self._label_names: - img_rel_folder = os.path.join(self._image_folder, label_name) - img_file_name_list = [ - f for f in os.listdir(img_rel_folder) if os.path.isfile(os.path.join(img_rel_folder, f)) - ] - self._labels += [self._label_name_to_idx[label_name]] * len(img_file_name_list) - self._image_files += [os.path.join(img_rel_folder, img_name) for img_name in img_file_name_list] + self._label_name_to_idx = dict(zip(self._label_names, range(len(self._label_names)))) + + self._read_fgvc_aircrafts_images_labels(self._data_path) def __len__(self) -> int: return len(self._image_files) @@ -93,11 +84,9 @@ def _download(self): def _check_exists(self) -> bool: return os.path.exists(self._data_path) and os.path.isdir(self._data_path) - def _create_fgvc_aircrafts_disk_folder(self, input_path: str): - img_data_folder = os.path.join(input_path, "data", "images") + def _read_fgvc_aircrafts_images_labels(self, input_path: str): + image_data_folder = os.path.join(input_path, "data", "images") labels_path = os.path.join(input_path, "data", f"images_variant_{self._split}.txt") - for label in self._label_names: - os.makedirs(os.path.join(self._image_folder, label), exist_ok=True) with open(labels_path, "r") as labels_file: lines = [line.strip() for line in labels_file] @@ -105,10 +94,8 @@ def _create_fgvc_aircrafts_disk_folder(self, input_path: str): line_list = line.split(" ") image_name = line_list[0] label_name = self._parse_aircraft_name(" ".join(line_list[1:])) - shutil.copy( - src=os.path.join(img_data_folder, f"{image_name}.jpg"), - dst=os.path.join(self._image_folder, label_name), - ) + self._labels.append(self._label_name_to_idx[label_name]) + self._image_files.append(os.path.join(image_data_folder, image_name + str(".jpg"))) def _get_label_names(self, input_path: str) -> List[str]: variants_file = os.path.join(input_path, "data", "variants.txt") From 3e317e34afe75e5e103f1843daebd957cc5fbf21 Mon Sep 17 00:00:00 2001 From: sallysyw Date: Fri, 14 Jan 2022 06:36:56 +0000 Subject: [PATCH 05/10] adding more annotation level --- test/test_datasets.py | 40 ++++++++++-------- torchvision/datasets/fgvc_aircraft.py | 61 +++++++++++++++++---------- 2 files changed, 61 insertions(+), 40 deletions(-) diff --git a/test/test_datasets.py b/test/test_datasets.py index 7f0c6e2db9f..f64738d8c8d 100644 --- a/test/test_datasets.py +++ b/test/test_datasets.py @@ -2208,48 +2208,54 @@ def inject_fake_data(self, tmpdir: str, config): class FGVCAircraftTestCase(datasets_utils.ImageDatasetTestCase): DATASET_CLASS = datasets.FGVCAircraft - FEATURE_TYPES = (PIL.Image.Image, int) - - ADDITIONAL_CONFIGS = datasets_utils.combinations_grid(split=("train", "val", "trainval", "test")) + ADDITIONAL_CONFIGS = datasets_utils.combinations_grid( + split=("train", "val", "trainval", "test"), annotation_level=("variant", "family", "manufacturer") + ) def inject_fake_data(self, tmpdir: str, config): split = config["split"] + annotation_level = config["annotation_level"] + annotation_level_to_file = { + "variant": "variants.txt", + "family": "families.txt", + "manufacturer": "manufacturers.txt", + } + root_folder = pathlib.Path(tmpdir) / "fgvc-aircraft-2013b" data_folder = root_folder / "data" num_images_per_class = 5 - variants = ["707-320", "Hawk T1", "Tornado"] + classes = ["707-320", "Hawk T1", "Tornado"] num_samples_per_class = 4 if split == "trainval" else 2 datasets_utils.create_image_folder( data_folder, "images", file_name_fn=lambda idx: f"{idx}.jpg", - num_examples=num_images_per_class * len(variants), + num_examples=num_images_per_class * len(classes), ) - images_variants = [] - for i in range(len(variants)): - variant = variants[i] - images_variants.extend( + images_classes = [] + for i in range(len(classes)): + images_classes.extend( [ - f"{idx} {variant}" + f"{idx} {classes[i]}" for idx in random.sample( range(i * num_images_per_class, (i + 1) * num_images_per_class), num_samples_per_class ) ] ) - variants_file = root_folder / "data" / "variants.txt" - images_variant_file = root_folder / "data" / f"images_variant_{split}.txt" + annotation_file = root_folder / "data" / annotation_level_to_file[annotation_level] + images_annotation_file = root_folder / "data" / f"images_{annotation_level}_{split}.txt" - with open(variants_file, "w") as file: - file.write("\n".join(variants)) + with open(annotation_file, "w") as file: + file.write("\n".join(classes)) - with open(images_variant_file, "w") as file: - file.write("\n".join(images_variants)) + with open(images_annotation_file, "w") as file: + file.write("\n".join(images_classes)) - return len(variants * num_samples_per_class) + return len(classes * num_samples_per_class) class SUN397TestCase(datasets_utils.ImageDatasetTestCase): diff --git a/torchvision/datasets/fgvc_aircraft.py b/torchvision/datasets/fgvc_aircraft.py index 7346fb8c982..c8414304dab 100644 --- a/torchvision/datasets/fgvc_aircraft.py +++ b/torchvision/datasets/fgvc_aircraft.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os from typing import Any, Callable, List, Optional, Tuple @@ -6,12 +8,27 @@ from .utils import download_and_extract_archive, verify_str_arg from .vision import VisionDataset +annotation_level_to_file = { + "variant": "variants.txt", + "family": "families.txt", + "manufacturer": "manufacturers.txt", +} + class FGVCAircraft(VisionDataset): """`FGVC Aircraft `_ Dataset. The dataset contains 10,200 images of aircraft, with 100 images for each of 102 different aircraft model variants, most of which are airplanes. + Aircraft models are organized in a four-levels hierarchy. The four levels, from + finer to coarser, are: + + - Model, e.g. Boeing 737-76J. Since certain models are nearly visually indistinguishable, + this level is not used in the evaluation. + - Variant, e.g. Boeing 737-700. A variant collapses all the models that are visually + indistinguishable into one class. The dataset comprises 102 different variants. + - Family, e.g. Boeing 737. The dataset comprises 70 different families. + - Manufacturer, e.g. Boeing. The dataset comprises 41 different manufacturers. Args: root (string): Root directory of the FGVC Aircraft dataset. @@ -20,6 +37,8 @@ class FGVCAircraft(VisionDataset): download (bool, optional): If True, downloads the dataset from the internet and puts it in root directory. If dataset is already downloaded, it is not downloaded again. + annotation_level (str, optional): The annotation level, supports ``variant``, + ``family`` and ``manufacturer``. transform (callable, optional): A function/transform that takes in an PIL image and returns a transformed version. E.g, ``transforms.RandomCrop`` target_transform (callable, optional): A function/transform that takes in the @@ -34,11 +53,15 @@ def __init__( root: str, split: str = "trainval", download: bool = False, + annotation_level: str = "variant", transform: Optional[Callable] = None, target_transform: Optional[Callable] = None, ) -> None: super().__init__(root, transform=transform, target_transform=target_transform) self._split = verify_str_arg(split, "split", ("train", "val", "trainval", "test")) + self._annotation_level = verify_str_arg( + annotation_level, "annotation_level", ("variant", "family", "manufacturer") + ) self._data_path = os.path.join(self.root, "fgvc-aircraft-2013b") if download: @@ -47,16 +70,24 @@ def __init__( if not self._check_exists(): raise RuntimeError("Dataset not found. You can use download=True to download it") - self._label_names = self._get_label_names(self._data_path) + self.classes = self._get_classes(self._data_path) # Parse the downloaded files self._image_folder = os.path.join(self.root, self._split) + self.class_to_idx = dict(zip(self.classes, range(len(self.classes)))) self._image_files = [] self._labels = [] - self._label_name_to_idx = dict(zip(self._label_names, range(len(self._label_names)))) - self._read_fgvc_aircrafts_images_labels(self._data_path) + image_data_folder = os.path.join(self._data_path, "data", "images") + labels_path = os.path.join(self._data_path, "data", f"images_{annotation_level}_{self._split}.txt") + + with open(labels_path, "r") as labels_file: + lines = [line.strip() for line in labels_file] + for line in lines: + image_name, label_name = line.strip().split(" ", 1) + self._image_files.append(os.path.join(image_data_folder, f"{image_name}.jpg")) + self._labels.append(self.class_to_idx[label_name]) def __len__(self) -> int: return len(self._image_files) @@ -84,23 +115,7 @@ def _download(self): def _check_exists(self) -> bool: return os.path.exists(self._data_path) and os.path.isdir(self._data_path) - def _read_fgvc_aircrafts_images_labels(self, input_path: str): - image_data_folder = os.path.join(input_path, "data", "images") - labels_path = os.path.join(input_path, "data", f"images_variant_{self._split}.txt") - - with open(labels_path, "r") as labels_file: - lines = [line.strip() for line in labels_file] - for line in lines: - line_list = line.split(" ") - image_name = line_list[0] - label_name = self._parse_aircraft_name(" ".join(line_list[1:])) - self._labels.append(self._label_name_to_idx[label_name]) - self._image_files.append(os.path.join(image_data_folder, image_name + str(".jpg"))) - - def _get_label_names(self, input_path: str) -> List[str]: - variants_file = os.path.join(input_path, "data", "variants.txt") - with open(variants_file, "r") as f: - return [self._parse_aircraft_name(line.strip()) for line in f] - - def _parse_aircraft_name(self, name: str) -> str: - return name.replace("/", "-").replace(" ", "-") + def _get_classes(self, input_path: str) -> List[str]: + annotation_file = os.path.join(input_path, "data", annotation_level_to_file[self._annotation_level]) + with open(annotation_file, "r") as f: + return [line.strip() for line in f] From dcaa2ec3d50679d9e415d6f83f468e7f3102e7d0 Mon Sep 17 00:00:00 2001 From: sallysyw Date: Fri, 14 Jan 2022 06:40:35 +0000 Subject: [PATCH 06/10] nit --- torchvision/datasets/fgvc_aircraft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/torchvision/datasets/fgvc_aircraft.py b/torchvision/datasets/fgvc_aircraft.py index c8414304dab..ec6a23642b2 100644 --- a/torchvision/datasets/fgvc_aircraft.py +++ b/torchvision/datasets/fgvc_aircraft.py @@ -80,7 +80,7 @@ def __init__( self._labels = [] image_data_folder = os.path.join(self._data_path, "data", "images") - labels_path = os.path.join(self._data_path, "data", f"images_{annotation_level}_{self._split}.txt") + labels_path = os.path.join(self._data_path, "data", f"images_{self._annotation_level}_{self._split}.txt") with open(labels_path, "r") as labels_file: lines = [line.strip() for line in labels_file] From 9dc5b6d478814d769b5c5b7ea8768843f62093b3 Mon Sep 17 00:00:00 2001 From: sallysyw Date: Fri, 14 Jan 2022 21:09:29 +0000 Subject: [PATCH 07/10] address comments --- test/test_datasets.py | 15 ++++----- torchvision/datasets/fgvc_aircraft.py | 48 ++++++++++++--------------- 2 files changed, 28 insertions(+), 35 deletions(-) diff --git a/test/test_datasets.py b/test/test_datasets.py index f64738d8c8d..60cab440c5c 100644 --- a/test/test_datasets.py +++ b/test/test_datasets.py @@ -2224,9 +2224,8 @@ def inject_fake_data(self, tmpdir: str, config): root_folder = pathlib.Path(tmpdir) / "fgvc-aircraft-2013b" data_folder = root_folder / "data" - num_images_per_class = 5 classes = ["707-320", "Hawk T1", "Tornado"] - num_samples_per_class = 4 if split == "trainval" else 2 + num_images_per_class = 5 datasets_utils.create_image_folder( data_folder, @@ -2235,6 +2234,11 @@ def inject_fake_data(self, tmpdir: str, config): num_examples=num_images_per_class * len(classes), ) + annotation_file = data_folder / annotation_level_to_file[annotation_level] + with open(annotation_file, "w") as file: + file.write("\n".join(classes)) + + num_samples_per_class = 4 if split == "trainval" else 2 images_classes = [] for i in range(len(classes)): images_classes.extend( @@ -2246,12 +2250,7 @@ def inject_fake_data(self, tmpdir: str, config): ] ) - annotation_file = root_folder / "data" / annotation_level_to_file[annotation_level] - images_annotation_file = root_folder / "data" / f"images_{annotation_level}_{split}.txt" - - with open(annotation_file, "w") as file: - file.write("\n".join(classes)) - + images_annotation_file = data_folder / f"images_{annotation_level}_{split}.txt" with open(images_annotation_file, "w") as file: file.write("\n".join(images_classes)) diff --git a/torchvision/datasets/fgvc_aircraft.py b/torchvision/datasets/fgvc_aircraft.py index ec6a23642b2..3452dfb0cb9 100644 --- a/torchvision/datasets/fgvc_aircraft.py +++ b/torchvision/datasets/fgvc_aircraft.py @@ -8,27 +8,19 @@ from .utils import download_and_extract_archive, verify_str_arg from .vision import VisionDataset -annotation_level_to_file = { - "variant": "variants.txt", - "family": "families.txt", - "manufacturer": "manufacturers.txt", -} - class FGVCAircraft(VisionDataset): """`FGVC Aircraft `_ Dataset. The dataset contains 10,200 images of aircraft, with 100 images for each of 102 different aircraft model variants, most of which are airplanes. - Aircraft models are organized in a four-levels hierarchy. The four levels, from + Aircraft models are organized in a three-levels hierarchy. The three levels, from finer to coarser, are: - - Model, e.g. Boeing 737-76J. Since certain models are nearly visually indistinguishable, - this level is not used in the evaluation. - - Variant, e.g. Boeing 737-700. A variant collapses all the models that are visually + - ``variant``, e.g. Boeing 737-700. A variant collapses all the models that are visually indistinguishable into one class. The dataset comprises 102 different variants. - - Family, e.g. Boeing 737. The dataset comprises 70 different families. - - Manufacturer, e.g. Boeing. The dataset comprises 41 different manufacturers. + - ``family``, e.g. Boeing 737. The dataset comprises 70 different families. + - ``manufacturer``, e.g. Boeing. The dataset comprises 41 different manufacturers. Args: root (string): Root directory of the FGVC Aircraft dataset. @@ -45,8 +37,7 @@ class FGVCAircraft(VisionDataset): target and transforms it. """ - _URL = "https://www.robots.ox.ac.uk/~vgg/data/fgvc-aircraft/archives/" - _URL_FILE = "fgvc-aircraft-2013b.tar.gz" + _URL = "https://www.robots.ox.ac.uk/~vgg/data/fgvc-aircraft/archives/fgvc-aircraft-2013b.tar.gz" def __init__( self, @@ -70,18 +61,26 @@ def __init__( if not self._check_exists(): raise RuntimeError("Dataset not found. You can use download=True to download it") - self.classes = self._get_classes(self._data_path) + annotation_file = os.path.join( + self._data_path, + "data", + { + "variant": "variants.txt", + "family": "families.txt", + "manufacturer": "manufacturers.txt", + }[self._annotation_level], + ) + with open(annotation_file, "r") as f: + self.classes = [line.strip() for line in f] - # Parse the downloaded files - self._image_folder = os.path.join(self.root, self._split) self.class_to_idx = dict(zip(self.classes, range(len(self.classes)))) - self._image_files = [] - self._labels = [] - image_data_folder = os.path.join(self._data_path, "data", "images") labels_path = os.path.join(self._data_path, "data", f"images_{self._annotation_level}_{self._split}.txt") + self._image_files = [] + self._labels = [] + with open(labels_path, "r") as labels_file: lines = [line.strip() for line in labels_file] for line in lines: @@ -104,18 +103,13 @@ def __getitem__(self, idx) -> Tuple[Any, Any]: return image, label - def _download(self): + def _download(self) -> None: """ Download the FGVC Aircraft dataset archive and extract it under root. """ if self._check_exists(): return - download_and_extract_archive(self._URL + self._URL_FILE, self.root) + download_and_extract_archive(self._URL, self.root) def _check_exists(self) -> bool: return os.path.exists(self._data_path) and os.path.isdir(self._data_path) - - def _get_classes(self, input_path: str) -> List[str]: - annotation_file = os.path.join(input_path, "data", annotation_level_to_file[self._annotation_level]) - with open(annotation_file, "r") as f: - return [line.strip() for line in f] From 2db1bf45407df1a78f8254a6c4aee7906e22a32b Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Fri, 14 Jan 2022 22:22:38 +0100 Subject: [PATCH 08/10] Apply suggestions from code review --- torchvision/datasets/fgvc_aircraft.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/torchvision/datasets/fgvc_aircraft.py b/torchvision/datasets/fgvc_aircraft.py index 3452dfb0cb9..f705d30bbf6 100644 --- a/torchvision/datasets/fgvc_aircraft.py +++ b/torchvision/datasets/fgvc_aircraft.py @@ -1,7 +1,7 @@ from __future__ import annotations import os -from typing import Any, Callable, List, Optional, Tuple +from typing import Any, Callable, Optional, Tuple import PIL.Image @@ -82,7 +82,6 @@ def __init__( self._labels = [] with open(labels_path, "r") as labels_file: - lines = [line.strip() for line in labels_file] for line in lines: image_name, label_name = line.strip().split(" ", 1) self._image_files.append(os.path.join(image_data_folder, f"{image_name}.jpg")) From 3098399d8e1091229e72255b4380e529dc883064 Mon Sep 17 00:00:00 2001 From: sallysyw Date: Fri, 14 Jan 2022 21:41:51 +0000 Subject: [PATCH 09/10] unify format --- torchvision/datasets/fgvc_aircraft.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/torchvision/datasets/fgvc_aircraft.py b/torchvision/datasets/fgvc_aircraft.py index 3452dfb0cb9..55e0efa3be8 100644 --- a/torchvision/datasets/fgvc_aircraft.py +++ b/torchvision/datasets/fgvc_aircraft.py @@ -76,13 +76,13 @@ def __init__( self.class_to_idx = dict(zip(self.classes, range(len(self.classes)))) image_data_folder = os.path.join(self._data_path, "data", "images") - labels_path = os.path.join(self._data_path, "data", f"images_{self._annotation_level}_{self._split}.txt") + labels_file = os.path.join(self._data_path, "data", f"images_{self._annotation_level}_{self._split}.txt") self._image_files = [] self._labels = [] - with open(labels_path, "r") as labels_file: - lines = [line.strip() for line in labels_file] + with open(labels_file, "r") as f: + lines = [line.strip() for line in f] for line in lines: image_name, label_name = line.strip().split(" ", 1) self._image_files.append(os.path.join(image_data_folder, f"{image_name}.jpg")) From caf762b2cf79793ff0d481f349131ab4a8921428 Mon Sep 17 00:00:00 2001 From: sallysyw Date: Fri, 14 Jan 2022 21:53:26 +0000 Subject: [PATCH 10/10] remove useless line --- torchvision/datasets/fgvc_aircraft.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/torchvision/datasets/fgvc_aircraft.py b/torchvision/datasets/fgvc_aircraft.py index 48cd3b7099b..687d44fb7f0 100644 --- a/torchvision/datasets/fgvc_aircraft.py +++ b/torchvision/datasets/fgvc_aircraft.py @@ -82,8 +82,7 @@ def __init__( self._labels = [] with open(labels_file, "r") as f: - lines = [line.strip() for line in f] - for line in lines: + for line in f: image_name, label_name = line.strip().split(" ", 1) self._image_files.append(os.path.join(image_data_folder, f"{image_name}.jpg")) self._labels.append(self.class_to_idx[label_name])