From 53e0ffde95fed4f0e6afa8845f2524a6fa057530 Mon Sep 17 00:00:00 2001 From: Joshua Bradley Date: Mon, 26 Oct 2020 02:41:18 -0400 Subject: [PATCH 01/16] add vggface dataset class --- torchvision/datasets/__init__.py | 3 +- torchvision/datasets/vggface2.py | 71 ++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 torchvision/datasets/vggface2.py diff --git a/torchvision/datasets/__init__.py b/torchvision/datasets/__init__.py index 4531d937c68..ae429e098ac 100644 --- a/torchvision/datasets/__init__.py +++ b/torchvision/datasets/__init__.py @@ -18,6 +18,7 @@ from .celeba import CelebA from .sbd import SBDataset from .vision import VisionDataset +from .vggface2 import VGGFace2 from .usps import USPS from .kinetics import Kinetics400 from .hmdb51 import HMDB51 @@ -32,4 +33,4 @@ 'Omniglot', 'SBU', 'Flickr8k', 'Flickr30k', 'VOCSegmentation', 'VOCDetection', 'Cityscapes', 'ImageNet', 'Caltech101', 'Caltech256', 'CelebA', 'SBDataset', 'VisionDataset', - 'USPS', 'Kinetics400', 'HMDB51', 'UCF101', 'Places365') + 'VGGFace2', 'USPS', 'Kinetics400', 'HMDB51', 'UCF101', 'Places365') diff --git a/torchvision/datasets/vggface2.py b/torchvision/datasets/vggface2.py new file mode 100644 index 00000000000..b8d17d3003b --- /dev/null +++ b/torchvision/datasets/vggface2.py @@ -0,0 +1,71 @@ +import os +from PIL import Image +import torch +from torchvision.datasets import VisionDataset + +class VGGFace2(VisionDataset): + + def __init__(self, root, split="train", transform=None, target_transform=None): + """ + Args: + root (string): Root directory of the VGGFace2 Dataset. + Expects the following folder structure if download=False: + . + └── vggface2 + ├── vggface2_train.tar.gz (or 'train' if uncompressed) + ├── vggface2_test.tar.gz (or 'test' if uncompressed) + ├── train_list.txt + └── test_list.txt + split (string): One of {``train``, ``test``}. + The dataset split to use. Defaults to ``train``. + target_type (string): The type of target to use, can be one of {``identity``, ``bbox``, ``attr``.``""``} + Can also be a list to output a tuple with all specified target types. + The targets represent: + ``raw`` (torch.tensor shape=(10,) dtype=int): all annotations combined (bbox + attr) + ``bbox`` (torch.tensor shape=(4,) dtype=int): bounding box (x, y, width, height) + ``attr`` (torch.tensor shape=(6,) dtype=int): label values for attributes + that represent (blur, expression, illumination, occlusion, pose, invalid) + Defaults to ``raw``. If empty, ``None`` will be returned as target. + transform (callable, optional): A function/transform that takes in a 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. + """ + super(VGGFace2, self).__init__(root, transform=transform, target_transform=target_transform) + # check arguments + if split not in ('train','test'): + raise ValueError('split \"{}\" is not recognized.'.format(split)) + self.split = split + self.img_info = [] + + image_list_file = 'train_list.txt' if self.split=='train' else 'test_list.txt' + self.image_list_file = os.path.join(self.root, image_list_file) + + with open(self.image_list_file, 'r') as f: + for i, img_file in enumerate(f): + img_file = img_file.strip() # e.g. train/n004332/0317_01.jpg + class_id = img_file.split("/")[0] # like n004332 + img_file = os.path.join(self.root, self.split, img_file) + self.img_info.append({ + 'img_path': img_file, + 'class_id': class_id, + }) + if i % 1000 == 0: + print("processing: {} images for {}".format(i, self.split)) + + def __len__(self): + return len(self.img_info) + + def __getitem__(self, index): + img_info = self.img_info[index] + img = Image.open(img_info['img_path']) + if self.transform: + img = self.transform(img) + + target = None + if self.split == "test": + return img, target + + if self.target_transform is not None: + target = self.target_transform(target) + return img, target From 5e75f3c34a397fabf72ab86accf5e9e1615536c5 Mon Sep 17 00:00:00 2001 From: Joshua Bradley Date: Mon, 26 Oct 2020 03:04:35 -0400 Subject: [PATCH 02/16] fix flake8 errors --- torchvision/datasets/vggface2.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/torchvision/datasets/vggface2.py b/torchvision/datasets/vggface2.py index b8d17d3003b..c05c433798f 100644 --- a/torchvision/datasets/vggface2.py +++ b/torchvision/datasets/vggface2.py @@ -3,6 +3,7 @@ import torch from torchvision.datasets import VisionDataset + class VGGFace2(VisionDataset): def __init__(self, root, split="train", transform=None, target_transform=None): @@ -33,12 +34,12 @@ def __init__(self, root, split="train", transform=None, target_transform=None): """ super(VGGFace2, self).__init__(root, transform=transform, target_transform=target_transform) # check arguments - if split not in ('train','test'): + if split not in ('train', 'test'): raise ValueError('split \"{}\" is not recognized.'.format(split)) self.split = split self.img_info = [] - image_list_file = 'train_list.txt' if self.split=='train' else 'test_list.txt' + image_list_file = 'train_list.txt' if self.split == 'train' else 'test_list.txt' self.image_list_file = os.path.join(self.root, image_list_file) with open(self.image_list_file, 'r') as f: From dc365803d3cda9c366c26db04b9dc1f2c6ff5382 Mon Sep 17 00:00:00 2001 From: Joshua Bradley Date: Mon, 26 Oct 2020 18:20:51 -0400 Subject: [PATCH 03/16] add standard dataset arguments --- torchvision/datasets/vggface2.py | 78 ++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 23 deletions(-) diff --git a/torchvision/datasets/vggface2.py b/torchvision/datasets/vggface2.py index c05c433798f..620dbaa5fc0 100644 --- a/torchvision/datasets/vggface2.py +++ b/torchvision/datasets/vggface2.py @@ -1,13 +1,12 @@ -import os from PIL import Image +import os import torch -from torchvision.datasets import VisionDataset - +from .vision import VisionDataset +from .utils import check_integrity, extract_archive class VGGFace2(VisionDataset): + """ VGGFace2 `_ Dataset. - def __init__(self, root, split="train", transform=None, target_transform=None): - """ Args: root (string): Root directory of the VGGFace2 Dataset. Expects the following folder structure if download=False: @@ -17,22 +16,46 @@ def __init__(self, root, split="train", transform=None, target_transform=None): ├── vggface2_test.tar.gz (or 'test' if uncompressed) ├── train_list.txt └── test_list.txt - split (string): One of {``train``, ``test``}. - The dataset split to use. Defaults to ``train``. - target_type (string): The type of target to use, can be one of {``identity``, ``bbox``, ``attr``.``""``} - Can also be a list to output a tuple with all specified target types. - The targets represent: - ``raw`` (torch.tensor shape=(10,) dtype=int): all annotations combined (bbox + attr) - ``bbox`` (torch.tensor shape=(4,) dtype=int): bounding box (x, y, width, height) - ``attr`` (torch.tensor shape=(6,) dtype=int): label values for attributes - that represent (blur, expression, illumination, occlusion, pose, invalid) - Defaults to ``raw``. If empty, ``None`` will be returned as target. - transform (callable, optional): A function/transform that takes in a 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. + split (string): One of {``train``, ``test``}. + The dataset split to use. Defaults to ``train``. + target_type (string): The type of target to use, can be one of {``identity``, ``bbox``, ``attr``.``""``} + Can also be a list to output a tuple with all specified target types. + The targets represent: + ``raw`` (torch.tensor shape=(10,) dtype=int): all annotations combined (bbox + attr) + ``bbox`` (torch.tensor shape=(4,) dtype=int): bounding box (x, y, width, height) + ``attr`` (torch.tensor shape=(6,) dtype=int): label values for attributes + that represent (blur, expression, illumination, occlusion, pose, invalid) + Defaults to ``raw``. If empty, ``None`` will be returned as target. + transform (callable, optional): A function/transform that takes in a 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. """ + + base_folder = "vggface2" + file_list = [ + # Filename MD5 Hash Uncompressed filename + ("vggface2_train.tar.gz", "88813c6b15de58afc8fa75ea83361d7f", "train"), + ("vggface2_test.tar.gz", "bb7a323824d1004e14e00c23974facd3", "test"), + ] + + def __init__( + self, + root, + split = "train", + download = False, + transform = None, + target_transform = None): + + root = os.path.join(root, self.base_folder) super(VGGFace2, self).__init__(root, transform=transform, target_transform=target_transform) + + if download: + msg = ("The dataset is not publicly accessible. You must login and " + "download the archives externally and place them in the root " + "directory.") + raise RuntimeError(msg) + # check arguments if split not in ('train', 'test'): raise ValueError('split \"{}\" is not recognized.'.format(split)) @@ -42,17 +65,22 @@ def __init__(self, root, split="train", transform=None, target_transform=None): image_list_file = 'train_list.txt' if self.split == 'train' else 'test_list.txt' self.image_list_file = os.path.join(self.root, image_list_file) + # are files downloaded and extracted? + for (filename, md5, extracted_folder) in self.file_list: + extracted_folder_path = os.path.join(self.root, extracted_folder) + if not os.path.isdir(extracted_folder_path): + raise RuntimeError('Can not find folder \"{}\"'.format(extracted_folder_path)) + with open(self.image_list_file, 'r') as f: for i, img_file in enumerate(f): - img_file = img_file.strip() # e.g. train/n004332/0317_01.jpg + img_file = img_file.strip() # e.g. n004332/0317_01.jpg class_id = img_file.split("/")[0] # like n004332 - img_file = os.path.join(self.root, self.split, img_file) + img_file = os.path.join(self.root, self.split, img_file) # like root/vggface2/train/n000002/0001_01.jpg + print("img_file: " + img_file) self.img_info.append({ 'img_path': img_file, 'class_id': class_id, }) - if i % 1000 == 0: - print("processing: {} images for {}".format(i, self.split)) def __len__(self): return len(self.img_info) @@ -70,3 +98,7 @@ def __getitem__(self, index): if self.target_transform is not None: target = self.target_transform(target) return img, target + + def extra_repr(self) -> str: + lines = ["Split: {split}"] + return '\n'.join(lines).format(**self.__dict__) From ab31f883eeec2ba2f5ca03739688141f48953381 Mon Sep 17 00:00:00 2001 From: Joshua Bradley Date: Tue, 27 Oct 2020 00:23:16 -0400 Subject: [PATCH 04/16] fix code formatting and standardize dataset class --- torchvision/datasets/vggface2.py | 117 +++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 36 deletions(-) diff --git a/torchvision/datasets/vggface2.py b/torchvision/datasets/vggface2.py index 620dbaa5fc0..62d613363ec 100644 --- a/torchvision/datasets/vggface2.py +++ b/torchvision/datasets/vggface2.py @@ -1,8 +1,11 @@ +from functools import partial from PIL import Image import os import torch -from .vision import VisionDataset +from typing import Any, Callable, Dict, List, Optional, Tuple, Union from .utils import check_integrity, extract_archive +from .vision import VisionDataset + class VGGFace2(VisionDataset): """ VGGFace2 `_ Dataset. @@ -15,41 +18,50 @@ class VGGFace2(VisionDataset): ├── vggface2_train.tar.gz (or 'train' if uncompressed) ├── vggface2_test.tar.gz (or 'test' if uncompressed) ├── train_list.txt - └── test_list.txt + ├── test_list.txt + └── bb_landmark.tar.gz (or 'bb_landmark' if uncompressed) split (string): One of {``train``, ``test``}. The dataset split to use. Defaults to ``train``. - target_type (string): The type of target to use, can be one of {``identity``, ``bbox``, ``attr``.``""``} + target_type (string): The type of target to use. One of + {``class_id``, ``image_id``, ``face_id``, ``bbox``, ``landmarks``.``""``} Can also be a list to output a tuple with all specified target types. The targets represent: - ``raw`` (torch.tensor shape=(10,) dtype=int): all annotations combined (bbox + attr) + ``class_id`` (string) + ``image_id`` (string) + ``face_id`` (string) ``bbox`` (torch.tensor shape=(4,) dtype=int): bounding box (x, y, width, height) - ``attr`` (torch.tensor shape=(6,) dtype=int): label values for attributes - that represent (blur, expression, illumination, occlusion, pose, invalid) - Defaults to ``raw``. If empty, ``None`` will be returned as target. + ``landmarks`` (torch.tensor shape=(10,) dtype=float): values that + represent five points (P1X, P1Y, P2X, P2Y, P3X, P3Y, P4X, P4Y, P5X, P5Y) + Defaults to ``bbox``. If empty, ``None`` will be returned as target. transform (callable, optional): A function/transform that takes in a 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. """ - + base_folder = "vggface2" file_list = [ # Filename MD5 Hash Uncompressed filename ("vggface2_train.tar.gz", "88813c6b15de58afc8fa75ea83361d7f", "train"), - ("vggface2_test.tar.gz", "bb7a323824d1004e14e00c23974facd3", "test"), + ("vggface2_test.tar.gz", "bb7a323824d1004e14e00c23974facd3", "test"), + ("bb_landmark.tar.gz", "26f7ba288a782862d137348a1cb97540", "bb_landmark") ] def __init__( self, - root, - split = "train", - download = False, - transform = None, - target_transform = None): - - root = os.path.join(root, self.base_folder) - super(VGGFace2, self).__init__(root, transform=transform, target_transform=target_transform) + root: str, + split: str = "train", + target_type: Union[List[str], str] = "bbox", + transform: Optional[Callable] = None, + target_transform: Optional[Callable] = None, + download: bool = False, + ) -> None: + import pandas + super(VGGFace2, self).__init__(root=os.path.join(root, self.base_folder), + transform=transform, + target_transform=target_transform) + # stay consistent with other datasets and check for a download option if download: msg = ("The dataset is not publicly accessible. You must login and " "download the archives externally and place them in the root " @@ -60,45 +72,78 @@ def __init__( if split not in ('train', 'test'): raise ValueError('split \"{}\" is not recognized.'.format(split)) self.split = split - self.img_info = [] + self.img_info: List[Dict[str, object]] = [] + + if isinstance(target_type, list): + self.target_type = target_type + else: + self.target_type = [target_type] + + if not (all(x in ["class_id", "image_id", "face_id", "bbox", "landmarks", ""] for x in self.target_type)): + raise ValueError("target_type \"{}\" is not recognized.".format(self.target_type)) + if not self.target_type and self.target_transform is not None: + raise RuntimeError('target_transform is specified but target_type is empty') image_list_file = 'train_list.txt' if self.split == 'train' else 'test_list.txt' self.image_list_file = os.path.join(self.root, image_list_file) - # are files downloaded and extracted? - for (filename, md5, extracted_folder) in self.file_list: - extracted_folder_path = os.path.join(self.root, extracted_folder) - if not os.path.isdir(extracted_folder_path): - raise RuntimeError('Can not find folder \"{}\"'.format(extracted_folder_path)) + # prepare dataset + for (filename, _, extracted_dir) in self.file_list: + filename = os.path.join(self.root, filename) + extracted_dir_path = os.path.join(self.root, extracted_dir) + if not os.path.isdir(extracted_dir_path): + extract_archive(filename) + + # process dataset + fn = partial(os.path.join, self.root, self.file_list[2][2]) + bbox_frames = [pandas.read_csv(fn("loose_bb_train.csv"), index_col=0), + pandas.read_csv(fn("loose_bb_test.csv"), index_col=0)] + self.bbox = pandas.concat(bbox_frames) + landmark_frames = [pandas.read_csv(fn("loose_landmark_train.csv"), index_col=0), + pandas.read_csv(fn("loose_landmark_test.csv"), index_col=0)] + self.landmarks = pandas.concat(landmark_frames) with open(self.image_list_file, 'r') as f: for i, img_file in enumerate(f): - img_file = img_file.strip() # e.g. n004332/0317_01.jpg - class_id = img_file.split("/")[0] # like n004332 - img_file = os.path.join(self.root, self.split, img_file) # like root/vggface2/train/n000002/0001_01.jpg - print("img_file: " + img_file) + img_file = img_file.strip() + img_filename, ext = os.path.splitext(img_file) # e.g. ["n004332/0317_01", "jpg"] + class_id, image_face_id = img_filename.split("/") + image_id, face_id = image_face_id.split("_") + img_filepath = os.path.join(self.root, self.split, img_file) self.img_info.append({ - 'img_path': img_file, + 'img_path': img_filepath, 'class_id': class_id, + 'image_id': image_id, + 'face_id': face_id, + 'bbox': torch.tensor(self.bbox.loc[img_filename].values), + 'landmarks': torch.tensor(self.landmarks.loc[img_filename].values), }) - def __len__(self): + def __len__(self) -> int: return len(self.img_info) - def __getitem__(self, index): + def __getitem__(self, index) -> Tuple[Any, Any]: img_info = self.img_info[index] + + # prepare image img = Image.open(img_info['img_path']) if self.transform: img = self.transform(img) - target = None - if self.split == "test": - return img, target + # prepare target + target: Any = [] + for t in self.target_type: + if t == "": + target = None + break + target.append(img_info[t]) + if target: + target = tuple(target) if len(target) > 1 else target[0] + if self.target_transform is not None: + target = self.target_transform(target) - if self.target_transform is not None: - target = self.target_transform(target) return img, target def extra_repr(self) -> str: - lines = ["Split: {split}"] + lines = ["Target type: {target_type}", "Split: {split}"] return '\n'.join(lines).format(**self.__dict__) From 9d3590db7f1b44e574ac9ad75c11ee913463a18b Mon Sep 17 00:00:00 2001 From: Joshua Bradley Date: Tue, 27 Oct 2020 13:05:58 -0400 Subject: [PATCH 05/16] add dataset citation --- torchvision/datasets/vggface2.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/torchvision/datasets/vggface2.py b/torchvision/datasets/vggface2.py index 62d613363ec..495b6f9de02 100644 --- a/torchvision/datasets/vggface2.py +++ b/torchvision/datasets/vggface2.py @@ -10,6 +10,13 @@ class VGGFace2(VisionDataset): """ VGGFace2 `_ Dataset. + Citation: + @inproceedings{Cao18, + author = "Cao, Q. and Shen, L. and Xie, W. and Parkhi, O. M. and Zisserman, A.", + title = "VGGFace2: A dataset for recognising faces across pose and age", + booktitle = "International Conference on Automatic Face and Gesture Recognition", + year = "2018"} + Args: root (string): Root directory of the VGGFace2 Dataset. Expects the following folder structure if download=False: @@ -37,6 +44,9 @@ class VGGFace2(VisionDataset): and returns a transformed version. E.g, ``transforms.RandomCrop`` target_transform (callable, optional): A function/transform that takes in the target and transforms it. + 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. """ base_folder = "vggface2" From 0ea263a9e5b64a51b11158a6e6aa89c25c31e91b Mon Sep 17 00:00:00 2001 From: Joshua Bradley Date: Tue, 27 Oct 2020 13:08:55 -0400 Subject: [PATCH 06/16] more formatting fixes --- torchvision/datasets/vggface2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/torchvision/datasets/vggface2.py b/torchvision/datasets/vggface2.py index 495b6f9de02..500740e5fcb 100644 --- a/torchvision/datasets/vggface2.py +++ b/torchvision/datasets/vggface2.py @@ -27,8 +27,8 @@ class VGGFace2(VisionDataset): ├── train_list.txt ├── test_list.txt └── bb_landmark.tar.gz (or 'bb_landmark' if uncompressed) - split (string): One of {``train``, ``test``}. - The dataset split to use. Defaults to ``train``. + split (string): The dataset split to use. One of {``train``, ``test``}. + Defaults to ``train``. target_type (string): The type of target to use. One of {``class_id``, ``image_id``, ``face_id``, ``bbox``, ``landmarks``.``""``} Can also be a list to output a tuple with all specified target types. From 7bb168eed65899607d0e2848aa9d8fb7d0d68abb Mon Sep 17 00:00:00 2001 From: Joshua Bradley Date: Fri, 30 Oct 2020 01:13:00 -0400 Subject: [PATCH 07/16] docstring update --- torchvision/datasets/vggface2.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/torchvision/datasets/vggface2.py b/torchvision/datasets/vggface2.py index 500740e5fcb..813c8917da2 100644 --- a/torchvision/datasets/vggface2.py +++ b/torchvision/datasets/vggface2.py @@ -22,21 +22,21 @@ class VGGFace2(VisionDataset): Expects the following folder structure if download=False: . └── vggface2 + ├── bb_landmark.tar.gz (or 'bb_landmark' if uncompressed) ├── vggface2_train.tar.gz (or 'train' if uncompressed) ├── vggface2_test.tar.gz (or 'test' if uncompressed) ├── train_list.txt - ├── test_list.txt - └── bb_landmark.tar.gz (or 'bb_landmark' if uncompressed) + └── test_list.txt split (string): The dataset split to use. One of {``train``, ``test``}. Defaults to ``train``. target_type (string): The type of target to use. One of {``class_id``, ``image_id``, ``face_id``, ``bbox``, ``landmarks``.``""``} Can also be a list to output a tuple with all specified target types. The targets represent: - ``class_id`` (string) - ``image_id`` (string) - ``face_id`` (string) - ``bbox`` (torch.tensor shape=(4,) dtype=int): bounding box (x, y, width, height) + ``class_id`` (string) + ``image_id`` (string) + ``face_id`` (string) + ``bbox`` (torch.tensor shape=(4,) dtype=int): bounding box (x, y, width, height) ``landmarks`` (torch.tensor shape=(10,) dtype=float): values that represent five points (P1X, P1Y, P2X, P2Y, P3X, P3Y, P4X, P4Y, P5X, P5Y) Defaults to ``bbox``. If empty, ``None`` will be returned as target. @@ -116,8 +116,9 @@ def __init__( with open(self.image_list_file, 'r') as f: for i, img_file in enumerate(f): img_file = img_file.strip() - img_filename, ext = os.path.splitext(img_file) # e.g. ["n004332/0317_01", "jpg"] - class_id, image_face_id = img_filename.split("/") + img_filename, ext = os.path.splitext(img_file) # e.g. ["n004332/0317_01", "jpg"] + class_id, image_face_id = img_filename.split("/") # e.g. ["n004332", "0317_01"] + class_id = class_id[1:] image_id, face_id = image_face_id.split("_") img_filepath = os.path.join(self.root, self.split, img_file) self.img_info.append({ From 7510edfc898ddc4a359c1f07c4859b948669d4ce Mon Sep 17 00:00:00 2001 From: Joshua Bradley Date: Fri, 30 Oct 2020 01:17:41 -0400 Subject: [PATCH 08/16] formatting update --- torchvision/datasets/vggface2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/torchvision/datasets/vggface2.py b/torchvision/datasets/vggface2.py index 813c8917da2..1a45a71d3bd 100644 --- a/torchvision/datasets/vggface2.py +++ b/torchvision/datasets/vggface2.py @@ -116,8 +116,8 @@ def __init__( with open(self.image_list_file, 'r') as f: for i, img_file in enumerate(f): img_file = img_file.strip() - img_filename, ext = os.path.splitext(img_file) # e.g. ["n004332/0317_01", "jpg"] - class_id, image_face_id = img_filename.split("/") # e.g. ["n004332", "0317_01"] + img_filename, ext = os.path.splitext(img_file) # e.g. ["n004332/0317_01", "jpg"] + class_id, image_face_id = img_filename.split("/") # e.g. ["n004332", "0317_01"] class_id = class_id[1:] image_id, face_id = image_face_id.split("_") img_filepath = os.path.join(self.root, self.split, img_file) From 170eff19be8414db00aa71a0036c58db0dfd83a9 Mon Sep 17 00:00:00 2001 From: Josh Bradley Date: Fri, 30 Oct 2020 13:33:27 -0400 Subject: [PATCH 09/16] remove unused variable --- torchvision/datasets/vggface2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/torchvision/datasets/vggface2.py b/torchvision/datasets/vggface2.py index 1a45a71d3bd..0bf298ad97f 100644 --- a/torchvision/datasets/vggface2.py +++ b/torchvision/datasets/vggface2.py @@ -114,7 +114,7 @@ def __init__( self.landmarks = pandas.concat(landmark_frames) with open(self.image_list_file, 'r') as f: - for i, img_file in enumerate(f): + for img_file in f: img_file = img_file.strip() img_filename, ext = os.path.splitext(img_file) # e.g. ["n004332/0317_01", "jpg"] class_id, image_face_id = img_filename.split("/") # e.g. ["n004332", "0317_01"] From d8d5e02c5de0afd9b16ac75d2e400b45aeddf184 Mon Sep 17 00:00:00 2001 From: Joshua Bradley Date: Sat, 31 Oct 2020 04:16:56 -0400 Subject: [PATCH 10/16] use double quoted strings --- torchvision/datasets/vggface2.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/torchvision/datasets/vggface2.py b/torchvision/datasets/vggface2.py index 0bf298ad97f..f5a97453a23 100644 --- a/torchvision/datasets/vggface2.py +++ b/torchvision/datasets/vggface2.py @@ -79,7 +79,7 @@ def __init__( raise RuntimeError(msg) # check arguments - if split not in ('train', 'test'): + if split not in ("train", "test"): raise ValueError('split \"{}\" is not recognized.'.format(split)) self.split = split self.img_info: List[Dict[str, object]] = [] @@ -92,9 +92,9 @@ def __init__( if not (all(x in ["class_id", "image_id", "face_id", "bbox", "landmarks", ""] for x in self.target_type)): raise ValueError("target_type \"{}\" is not recognized.".format(self.target_type)) if not self.target_type and self.target_transform is not None: - raise RuntimeError('target_transform is specified but target_type is empty') + raise RuntimeError("target_transform is specified but target_type is empty") - image_list_file = 'train_list.txt' if self.split == 'train' else 'test_list.txt' + image_list_file = "train_list.txt" if self.split == "train" else "test_list.txt" self.image_list_file = os.path.join(self.root, image_list_file) # prepare dataset @@ -122,12 +122,12 @@ def __init__( image_id, face_id = image_face_id.split("_") img_filepath = os.path.join(self.root, self.split, img_file) self.img_info.append({ - 'img_path': img_filepath, - 'class_id': class_id, - 'image_id': image_id, - 'face_id': face_id, - 'bbox': torch.tensor(self.bbox.loc[img_filename].values), - 'landmarks': torch.tensor(self.landmarks.loc[img_filename].values), + "img_path": img_filepath, + "class_id": class_id, + "image_id": image_id, + "face_id": face_id, + "bbox": torch.tensor(self.bbox.loc[img_filename].values), + "landmarks": torch.tensor(self.landmarks.loc[img_filename].values), }) def __len__(self) -> int: @@ -137,7 +137,7 @@ def __getitem__(self, index) -> Tuple[Any, Any]: img_info = self.img_info[index] # prepare image - img = Image.open(img_info['img_path']) + img = Image.open(img_info["img_path"]) if self.transform: img = self.transform(img) @@ -157,4 +157,4 @@ def __getitem__(self, index) -> Tuple[Any, Any]: def extra_repr(self) -> str: lines = ["Target type: {target_type}", "Split: {split}"] - return '\n'.join(lines).format(**self.__dict__) + return "\n".join(lines).format(**self.__dict__) From 58dfd04150d72381b5abc1ce1060a965d0544b06 Mon Sep 17 00:00:00 2001 From: Joshua Bradley Date: Sat, 31 Oct 2020 04:26:02 -0400 Subject: [PATCH 11/16] add vggface2 unit test --- test/fakedata_generation.py | 96 +++++++++++++++++++++++++++++++++++++ test/test_datasets.py | 14 +++++- 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/test/fakedata_generation.py b/test/fakedata_generation.py index e17a4309cdc..452822d7bbe 100644 --- a/test/fakedata_generation.py +++ b/test/fakedata_generation.py @@ -171,6 +171,102 @@ def _make_devkit_archive(root): yield root +@contextlib.contextmanager +def vggface2_root(): + + class_id = 'n000001' + image_id = '0001' + face_id = '01' + basefolder = 'vggface2' + + def _make_image(file): + PIL.Image.fromarray(np.zeros((32, 32, 3), dtype=np.uint8)).save(file) + + def _make_tar(archive, content, arcname=None, compress=False): + mode = 'w:gz' if compress else 'w' + if arcname is None: + arcname = os.path.basename(content) + with tarfile.open(archive, mode) as fh: + fh.add(content, arcname=arcname) + + def _make_image_list_files(root): + image_list_contents = os.path.join(class_id, image_id + '_' + face_id + '.jpg') # e.g. n000001/0001_01.jpg + # train image list + image_list_file = os.path.join(root, "train_list.txt") + with open(image_list_file, "w") as txt_file: + txt_file.write(image_list_contents) + # test image list + image_list_file = os.path.join(root, "test_list.txt") + with open(image_list_file, "w") as txt_file: + txt_file.write(image_list_contents) + + def _make_train_archive(root): + with get_tmp_dir() as tmp: + extracted_dir = os.path.join(tmp, 'train', class_id) + os.makedirs(extracted_dir) + print("EXTRACTED_DIR: " + extracted_dir) + _make_image(os.path.join(extracted_dir, image_id + '_' + face_id + '.jpg')) + train_archive = os.path.join(root, 'vggface2_train.tar.gz') + top_level_dir = os.path.join(tmp, 'train') + _make_tar(train_archive, top_level_dir, arcname='train', compress=True) + + def _make_test_archive(root): + with get_tmp_dir() as tmp: + extracted_dir = os.path.join(tmp, 'test', class_id) + os.makedirs(extracted_dir) + _make_image(os.path.join(extracted_dir, image_id + '_' + face_id + '.jpg')) + test_archive = os.path.join(root, 'vggface2_test.tar.gz') + top_level_dir = os.path.join(tmp, 'test') + _make_tar(test_archive, top_level_dir, arcname='test', compress=True) + + def _make_bb_landmark_archive(root): + train_bb_contents = 'NAME_ID,X,Y,W,H\n"n000001/0001_01",161,140,224,324' + test_bb_contents = 'NAME_ID,X,Y,W,H\n"n000001/0001_01",161,140,224,324' + train_landmark_contents = ('NAME_ID,P1X,P1Y,P2X,P2Y,P3X,P3Y,P4X,P4Y,P5X,P5Y\n' + '"n000001/0001_01",75.81253,110.2077,103.1778,104.6074,' + '90.06353,133.3624,85.39182,149.4176,114.9009,144.9259') + test_landmark_contents = ('NAME_ID,P1X,P1Y,P2X,P2Y,P3X,P3Y,P4X,P4Y,P5X,P5Y\n' + '"n000001/0001_01",75.81253,110.2077,103.1778,104.6074,' + '90.06353,133.3624,85.39182,149.4176,114.9009,144.9259') + + with get_tmp_dir() as tmp: + extracted_dir = os.path.join(tmp, 'bb_landmark') + os.makedirs(extracted_dir) + + # bbox training file + bbox_file = os.path.join(extracted_dir, "loose_bb_train.csv") + with open(bbox_file, "w") as csv_file: + csv_file.write(train_bb_contents) + + # bbox testing file + bbox_file = os.path.join(extracted_dir, "loose_bb_test.csv") + with open(bbox_file, "w") as csv_file: + csv_file.write(test_bb_contents) + + # landmark training file + landmark_file = os.path.join(extracted_dir, "loose_landmark_train.csv") + with open(landmark_file, "w") as csv_file: + csv_file.write(train_landmark_contents) + + # landmark testing file + landmark_file = os.path.join(extracted_dir, "loose_landmark_test.csv") + with open(landmark_file, "w") as csv_file: + csv_file.write(test_landmark_contents) + + archive = os.path.join(root, 'bb_landmark.tar.gz') + _make_tar(archive, extracted_dir, compress=True) + + with get_tmp_dir() as root: + root_base = os.path.join(root, basefolder) + os.makedirs(root_base) + _make_train_archive(root_base) + _make_test_archive(root_base) + _make_image_list_files(root_base) + _make_bb_landmark_archive(root_base) + + yield root + + @contextlib.contextmanager def cityscapes_root(): diff --git a/test/test_datasets.py b/test/test_datasets.py index af092e1845d..ea9fdd5a350 100644 --- a/test/test_datasets.py +++ b/test/test_datasets.py @@ -9,7 +9,7 @@ import torchvision from common_utils import get_tmp_dir from fakedata_generation import mnist_root, cifar_root, imagenet_root, \ - cityscapes_root, svhn_root, voc_root, ucf101_root, places365_root + cityscapes_root, svhn_root, voc_root, ucf101_root, places365_root, vggface2_root import xml.etree.ElementTree as ET from urllib.request import Request, urlopen import itertools @@ -139,6 +139,18 @@ def test_imagenet(self, mock_verify): dataset = torchvision.datasets.ImageNet(root, split='val') self.generic_classification_dataset_test(dataset) + def test_vggface2(self): + with vggface2_root() as root: + dataset = torchvision.datasets.VGGFace2(root, split='train') + self.assertEqual(len(dataset), 1) + img, target = dataset[0] + self.assertTrue(isinstance(img, PIL.Image.Image)) + + dataset = torchvision.datasets.VGGFace2(root, split='test') + self.assertEqual(len(dataset), 1) + img, target = dataset[0] + self.assertTrue(isinstance(img, PIL.Image.Image)) + @mock.patch('torchvision.datasets.cifar.check_integrity') @mock.patch('torchvision.datasets.cifar.CIFAR10._check_integrity') def test_cifar10(self, mock_ext_check, mock_int_check): From baf22b79f582737ac218e377eee4c66a0617e7ac Mon Sep 17 00:00:00 2001 From: Joshua Bradley Date: Sat, 31 Oct 2020 13:49:09 -0400 Subject: [PATCH 12/16] add pandas check --- test/test_datasets.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test_datasets.py b/test/test_datasets.py index ea9fdd5a350..a1a173b5d95 100644 --- a/test/test_datasets.py +++ b/test/test_datasets.py @@ -15,6 +15,12 @@ import itertools +try: + import pandas + HAS_PANDAS = True +except ImportError: + HAS_PANDAS = False + try: import scipy HAS_SCIPY = True @@ -139,6 +145,7 @@ def test_imagenet(self, mock_verify): dataset = torchvision.datasets.ImageNet(root, split='val') self.generic_classification_dataset_test(dataset) + @unittest.skipIf(not HAS_PANDAS, "pandas unavailable") def test_vggface2(self): with vggface2_root() as root: dataset = torchvision.datasets.VGGFace2(root, split='train') From 1256a351d57f4b3fe481a9c3bb3f8db6c828a93e Mon Sep 17 00:00:00 2001 From: Joshua Bradley Date: Sat, 31 Oct 2020 14:17:09 -0400 Subject: [PATCH 13/16] add docstring to vggface fakedata generator --- test/fakedata_generation.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/fakedata_generation.py b/test/fakedata_generation.py index 452822d7bbe..36f2e4b898e 100644 --- a/test/fakedata_generation.py +++ b/test/fakedata_generation.py @@ -173,6 +173,18 @@ def _make_devkit_archive(root): @contextlib.contextmanager def vggface2_root(): + """ + Generates a dataset with the following folder structure and returns the path root: + + └── vggface2 + ├── bb_landmark.tar.gz ('bb_landmark' when uncompressed) + ├── vggface2_train.tar.gz ('train' when uncompressed) + ├── vggface2_test.tar.gz ('test' when uncompressed) + ├── train_list.txt + └── test_list.txt + + The dataset consist of 1 image in the train set and 1 image in the test set. + """ class_id = 'n000001' image_id = '0001' From 941682b4bcd3243df65b26b1546ed318a581f80c Mon Sep 17 00:00:00 2001 From: Joshua Bradley Date: Sun, 1 Nov 2020 18:26:31 -0500 Subject: [PATCH 14/16] fix docstring indentation --- torchvision/datasets/vggface2.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/torchvision/datasets/vggface2.py b/torchvision/datasets/vggface2.py index f5a97453a23..63c9416782d 100644 --- a/torchvision/datasets/vggface2.py +++ b/torchvision/datasets/vggface2.py @@ -20,13 +20,13 @@ class VGGFace2(VisionDataset): Args: root (string): Root directory of the VGGFace2 Dataset. Expects the following folder structure if download=False: - . - └── vggface2 - ├── bb_landmark.tar.gz (or 'bb_landmark' if uncompressed) - ├── vggface2_train.tar.gz (or 'train' if uncompressed) - ├── vggface2_test.tar.gz (or 'test' if uncompressed) - ├── train_list.txt - └── test_list.txt + + └── vggface2 + ├── bb_landmark.tar.gz (or 'bb_landmark' if uncompressed) + ├── vggface2_train.tar.gz (or 'train' if uncompressed) + ├── vggface2_test.tar.gz (or 'test' if uncompressed) + ├── train_list.txt + └── test_list.txt split (string): The dataset split to use. One of {``train``, ``test``}. Defaults to ``train``. target_type (string): The type of target to use. One of From a39251410be01d1010cd6a95c15f5ade3aba4b06 Mon Sep 17 00:00:00 2001 From: Joshua Bradley Date: Sun, 1 Nov 2020 20:03:31 -0500 Subject: [PATCH 15/16] use local variable scope and fixed minor docstring formatting --- test/fakedata_generation.py | 20 ++++++++++---------- torchvision/datasets/vggface2.py | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/fakedata_generation.py b/test/fakedata_generation.py index 36f2e4b898e..430452db100 100644 --- a/test/fakedata_generation.py +++ b/test/fakedata_generation.py @@ -176,12 +176,12 @@ def vggface2_root(): """ Generates a dataset with the following folder structure and returns the path root: - └── vggface2 - ├── bb_landmark.tar.gz ('bb_landmark' when uncompressed) - ├── vggface2_train.tar.gz ('train' when uncompressed) - ├── vggface2_test.tar.gz ('test' when uncompressed) - ├── train_list.txt - └── test_list.txt + └── vggface2 + ├── bb_landmark.tar.gz ('bb_landmark' when uncompressed) + ├── vggface2_train.tar.gz ('train' when uncompressed) + ├── vggface2_test.tar.gz ('test' when uncompressed) + ├── train_list.txt + └── test_list.txt The dataset consist of 1 image in the train set and 1 image in the test set. """ @@ -232,8 +232,8 @@ def _make_test_archive(root): _make_tar(test_archive, top_level_dir, arcname='test', compress=True) def _make_bb_landmark_archive(root): - train_bb_contents = 'NAME_ID,X,Y,W,H\n"n000001/0001_01",161,140,224,324' - test_bb_contents = 'NAME_ID,X,Y,W,H\n"n000001/0001_01",161,140,224,324' + train_bbox_contents = 'NAME_ID,X,Y,W,H\n"n000001/0001_01",161,140,224,324' + test_bbox_contents = 'NAME_ID,X,Y,W,H\n"n000001/0001_01",161,140,224,324' train_landmark_contents = ('NAME_ID,P1X,P1Y,P2X,P2Y,P3X,P3Y,P4X,P4Y,P5X,P5Y\n' '"n000001/0001_01",75.81253,110.2077,103.1778,104.6074,' '90.06353,133.3624,85.39182,149.4176,114.9009,144.9259') @@ -248,12 +248,12 @@ def _make_bb_landmark_archive(root): # bbox training file bbox_file = os.path.join(extracted_dir, "loose_bb_train.csv") with open(bbox_file, "w") as csv_file: - csv_file.write(train_bb_contents) + csv_file.write(train_bbox_contents) # bbox testing file bbox_file = os.path.join(extracted_dir, "loose_bb_test.csv") with open(bbox_file, "w") as csv_file: - csv_file.write(test_bb_contents) + csv_file.write(test_bbox_contents) # landmark training file landmark_file = os.path.join(extracted_dir, "loose_landmark_train.csv") diff --git a/torchvision/datasets/vggface2.py b/torchvision/datasets/vggface2.py index 63c9416782d..4613f3bf3dd 100644 --- a/torchvision/datasets/vggface2.py +++ b/torchvision/datasets/vggface2.py @@ -95,7 +95,7 @@ def __init__( raise RuntimeError("target_transform is specified but target_type is empty") image_list_file = "train_list.txt" if self.split == "train" else "test_list.txt" - self.image_list_file = os.path.join(self.root, image_list_file) + image_list_file = os.path.join(self.root, image_list_file) # prepare dataset for (filename, _, extracted_dir) in self.file_list: @@ -113,7 +113,7 @@ def __init__( pandas.read_csv(fn("loose_landmark_test.csv"), index_col=0)] self.landmarks = pandas.concat(landmark_frames) - with open(self.image_list_file, 'r') as f: + with open(image_list_file, 'r') as f: for img_file in f: img_file = img_file.strip() img_filename, ext = os.path.splitext(img_file) # e.g. ["n004332/0317_01", "jpg"] From 70891563de0fb7785c448b84d856064fcc7f14e0 Mon Sep 17 00:00:00 2001 From: Joshua Bradley Date: Thu, 12 Nov 2020 00:04:31 -0500 Subject: [PATCH 16/16] minor style fixes --- torchvision/datasets/vggface2.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/torchvision/datasets/vggface2.py b/torchvision/datasets/vggface2.py index 4613f3bf3dd..c49099f9c15 100644 --- a/torchvision/datasets/vggface2.py +++ b/torchvision/datasets/vggface2.py @@ -3,7 +3,7 @@ import os import torch from typing import Any, Callable, Dict, List, Optional, Tuple, Union -from .utils import check_integrity, extract_archive +from .utils import check_integrity, extract_archive, verify_str_arg from .vision import VisionDataset @@ -49,8 +49,8 @@ class VGGFace2(VisionDataset): downloaded again. """ - base_folder = "vggface2" - file_list = [ + BASE_FOLDER = "vggface2" + FILE_LIST = [ # Filename MD5 Hash Uncompressed filename ("vggface2_train.tar.gz", "88813c6b15de58afc8fa75ea83361d7f", "train"), ("vggface2_test.tar.gz", "bb7a323824d1004e14e00c23974facd3", "test"), @@ -67,7 +67,7 @@ def __init__( download: bool = False, ) -> None: import pandas - super(VGGFace2, self).__init__(root=os.path.join(root, self.base_folder), + super(VGGFace2, self).__init__(root=os.path.join(root, self.BASE_FOLDER), transform=transform, target_transform=target_transform) @@ -79,18 +79,17 @@ def __init__( raise RuntimeError(msg) # check arguments - if split not in ("train", "test"): - raise ValueError('split \"{}\" is not recognized.'.format(split)) - self.split = split + self.split = verify_str_arg(split, "split", ("train", "test")) self.img_info: List[Dict[str, object]] = [] if isinstance(target_type, list): self.target_type = target_type else: self.target_type = [target_type] + self.target_type = [verify_str_arg(t, "target_type", + ("class_id", "image_id", "face_id", "bbox", "landmarks", "")) + for t in self.target_type] - if not (all(x in ["class_id", "image_id", "face_id", "bbox", "landmarks", ""] for x in self.target_type)): - raise ValueError("target_type \"{}\" is not recognized.".format(self.target_type)) if not self.target_type and self.target_transform is not None: raise RuntimeError("target_transform is specified but target_type is empty") @@ -98,14 +97,14 @@ def __init__( image_list_file = os.path.join(self.root, image_list_file) # prepare dataset - for (filename, _, extracted_dir) in self.file_list: + for (filename, _, extracted_dir) in self.FILE_LIST: filename = os.path.join(self.root, filename) extracted_dir_path = os.path.join(self.root, extracted_dir) if not os.path.isdir(extracted_dir_path): extract_archive(filename) # process dataset - fn = partial(os.path.join, self.root, self.file_list[2][2]) + fn = partial(os.path.join, self.root, self.FILE_LIST[2][2]) bbox_frames = [pandas.read_csv(fn("loose_bb_train.csv"), index_col=0), pandas.read_csv(fn("loose_bb_test.csv"), index_col=0)] self.bbox = pandas.concat(bbox_frames) @@ -134,10 +133,8 @@ def __len__(self) -> int: return len(self.img_info) def __getitem__(self, index) -> Tuple[Any, Any]: - img_info = self.img_info[index] - # prepare image - img = Image.open(img_info["img_path"]) + img = Image.open(self.img_info[index]["img_path"]) if self.transform: img = self.transform(img) @@ -147,7 +144,7 @@ def __getitem__(self, index) -> Tuple[Any, Any]: if t == "": target = None break - target.append(img_info[t]) + target.append(self.img_info[index][t]) if target: target = tuple(target) if len(target) > 1 else target[0] if self.target_transform is not None: