torchsight.datasets.instre
module
A dataset interface for the INSTRE dataset.
See official webpage: http://isia.ict.ac.cn/dataset/instre.html
Source code
"""A dataset interface for the INSTRE dataset.
See official webpage:
http://isia.ict.ac.cn/dataset/instre.html
"""
import json
import os
import random
import torch
from PIL import Image
from torch.utils.data import Dataset
from .mixins import VisualizeMixin
class InstreDataset(Dataset, VisualizeMixin):
"""INSTRE dataset class to get the images with their bounding boxes.
Instructions:
- Download the dataset from the original web page.
- Unzip the dataset to any directory.
- The root directory is the one that has the 3 directories of the 3 datasets inside.
"""
def __init__(self, root, name='S2', dataset='training', transform=None):
"""Initialize the dataset.
Arguments:
root (str): The path to the root of the directory.
name (str, optional): The name of the dataset to load.
dataset (str, optional): The portion of the dataset to load.
transform (callable, optional): The transformation to apply to the image and bounding boxes.
"""
self.name = self.validate_name(name)
self.dataset = self.validate_dataset(dataset)
self.root = self.validate_root(root)
self.split = self.get_split()
self.class_to_label, self.label_to_class = self.generate_labels()
self.paths = self.generate_paths()
self.transform = transform
def __len__(self):
"""Return the length of the dataset."""
return len(self.paths)
def __getitem__(self, i):
"""Get the image, bounding boxes and an info dict for the item at the given index.
Returns:
any: The transformed image.
torch.Tensor: The bounding boxes for the image.
Shape: `(num of boxes, 4)` with x1,y1,x2,y2.
dict: A dict with additional info.
"""
image, boxes, name = self.paths[i]
info = {'image': image, 'class': name}
image = Image.open(image)
boxes = self.get_boxes(boxes, name)
if self.transform is not None:
image, boxes, info = self.transform((image, boxes, info))
return image, boxes, info
def get_boxes(self, file, class_name):
"""Read the file and parse the bounding boxes.
Return:
torch.Tensor: The bounding boxes loaded from the file.
Shape: `(num of boxes, 4)` with x1,y1,x2,y2.
"""
boxes = []
label = self.class_to_label[class_name]
with open(file, 'r') as file:
for line in file.readlines():
x, y, w, h = [int(val) for val in line.split()]
x2, y2 = x + w, y + h
boxes.append(torch.Tensor([x, y, x2, y2, label]))
return torch.stack(boxes)
@staticmethod
def validate_name(name):
if name not in ['S1', 'S2', 'M']:
raise ValueError('You must indicate a valid name. Options: S1, S2 and M. Value: {}'.format(name))
return 'INSTRE-' + name
@staticmethod
def validate_dataset(dataset):
if dataset not in ['training', 'validation', 'trainval']:
raise ValueError('Please provide a valid dataset. Options: "training", "validation" and "trainval".')
return dataset
def validate_root(self, root):
if not os.path.exists(root):
raise ValueError('"{}" does not exists.'.format(root))
if self.name not in os.listdir(root):
raise ValueError('The dataset "{}" is not the root directory. '
'Are you sure that is the correct directory?'.format(self.name))
return os.path.join(root, self.name)
def get_split(self):
"""Get the split file or generate a new one.
Returns:
dict: A dict with all the class names with a 'training' and a 'validation'
list of of images paths.
"""
file = os.path.join(self.root, 'split.json')
if not os.path.exists(file):
print('There is no split for the dataset, generating a new random one.')
return self.split()
with open(file, 'r') as file:
return json.loads(file.read())
def generate_labels(self):
"""Generate the dicts to map the labels with the classes.
Returns:
dict: A dict that maps the class (str) -> label (int).
dict: A dict that maps the label (int) -> class (str).
"""
classes = [name.replace('_', ' ') for name in self.split.keys()]
classes.sort()
return (
{name: i for i, name in enumerate(classes)},
{i: name for i, name in enumerate(classes)}
)
def generate_paths(self):
"""Generate tuples with (image file, boxes file, class name) for each image in the dataset.
Returns:
list of tuples: A list with tuples like (image file, boxes file, class name).
"""
paths = []
for name in self.split.keys():
images = []
if self.dataset in ['training', 'trainval']:
images += self.split[name]['training']
if self.dataset in ['validation', 'trainval']:
images += self.split[name]['validation']
for image in images:
boxes = image[:-3] + 'txt'
paths.append((image, boxes, name.replace('_', ' ')))
return paths
def make_split(self, proportion=0.8):
"""Make a random split of the given dataset.
It will look for all the images inside each class and will generate a JSON file with
the training images and the validation images for each class.
Arguments:
proportion (float, optional): The proportion of training images to select from the total
number of images.
"""
classes = [directory for directory in os.listdir(self.root)
if os.path.isdir(os.path.join(self.root, directory))]
split = {}
for class_name in classes:
images = [os.path.join(self.root, class_name, file)
for file in os.listdir(os.path.join(self.root, class_name))
if file[-4:] == '.jpg']
training = random.sample(images, int(len(images) * proportion))
validation = list(set(images) - set(training))
split[class_name] = {
'training': training,
'validation': validation
}
split_file = os.path.join(self.root, 'split.json')
with open(split_file, 'w') as file:
file.write(json.dumps(split))
self.split = split
self.print_stats()
return split
def print_stats(self):
"""Print the distribution of the classes in the given split json file."""
classes = [class_name for class_name in self.split]
print('Classes: {}'.format(len(classes)))
for name in classes:
print('{} {} {}'.format(
name.ljust(30, '.'),
str(len(self.split[name]['training'])).rjust(3),
str(len(self.split[name]['validation'])).rjust(3)))
Classes
class InstreDataset (ancestors: torch.utils.data.dataset.Dataset, VisualizeMixin)
-
INSTRE dataset class to get the images with their bounding boxes.
Instructions: - Download the dataset from the original web page. - Unzip the dataset to any directory. - The root directory is the one that has the 3 directories of the 3 datasets inside.
Source code
class InstreDataset(Dataset, VisualizeMixin): """INSTRE dataset class to get the images with their bounding boxes. Instructions: - Download the dataset from the original web page. - Unzip the dataset to any directory. - The root directory is the one that has the 3 directories of the 3 datasets inside. """ def __init__(self, root, name='S2', dataset='training', transform=None): """Initialize the dataset. Arguments: root (str): The path to the root of the directory. name (str, optional): The name of the dataset to load. dataset (str, optional): The portion of the dataset to load. transform (callable, optional): The transformation to apply to the image and bounding boxes. """ self.name = self.validate_name(name) self.dataset = self.validate_dataset(dataset) self.root = self.validate_root(root) self.split = self.get_split() self.class_to_label, self.label_to_class = self.generate_labels() self.paths = self.generate_paths() self.transform = transform def __len__(self): """Return the length of the dataset.""" return len(self.paths) def __getitem__(self, i): """Get the image, bounding boxes and an info dict for the item at the given index. Returns: any: The transformed image. torch.Tensor: The bounding boxes for the image. Shape: `(num of boxes, 4)` with x1,y1,x2,y2. dict: A dict with additional info. """ image, boxes, name = self.paths[i] info = {'image': image, 'class': name} image = Image.open(image) boxes = self.get_boxes(boxes, name) if self.transform is not None: image, boxes, info = self.transform((image, boxes, info)) return image, boxes, info def get_boxes(self, file, class_name): """Read the file and parse the bounding boxes. Return: torch.Tensor: The bounding boxes loaded from the file. Shape: `(num of boxes, 4)` with x1,y1,x2,y2. """ boxes = [] label = self.class_to_label[class_name] with open(file, 'r') as file: for line in file.readlines(): x, y, w, h = [int(val) for val in line.split()] x2, y2 = x + w, y + h boxes.append(torch.Tensor([x, y, x2, y2, label])) return torch.stack(boxes) @staticmethod def validate_name(name): if name not in ['S1', 'S2', 'M']: raise ValueError('You must indicate a valid name. Options: S1, S2 and M. Value: {}'.format(name)) return 'INSTRE-' + name @staticmethod def validate_dataset(dataset): if dataset not in ['training', 'validation', 'trainval']: raise ValueError('Please provide a valid dataset. Options: "training", "validation" and "trainval".') return dataset def validate_root(self, root): if not os.path.exists(root): raise ValueError('"{}" does not exists.'.format(root)) if self.name not in os.listdir(root): raise ValueError('The dataset "{}" is not the root directory. ' 'Are you sure that is the correct directory?'.format(self.name)) return os.path.join(root, self.name) def get_split(self): """Get the split file or generate a new one. Returns: dict: A dict with all the class names with a 'training' and a 'validation' list of of images paths. """ file = os.path.join(self.root, 'split.json') if not os.path.exists(file): print('There is no split for the dataset, generating a new random one.') return self.split() with open(file, 'r') as file: return json.loads(file.read()) def generate_labels(self): """Generate the dicts to map the labels with the classes. Returns: dict: A dict that maps the class (str) -> label (int). dict: A dict that maps the label (int) -> class (str). """ classes = [name.replace('_', ' ') for name in self.split.keys()] classes.sort() return ( {name: i for i, name in enumerate(classes)}, {i: name for i, name in enumerate(classes)} ) def generate_paths(self): """Generate tuples with (image file, boxes file, class name) for each image in the dataset. Returns: list of tuples: A list with tuples like (image file, boxes file, class name). """ paths = [] for name in self.split.keys(): images = [] if self.dataset in ['training', 'trainval']: images += self.split[name]['training'] if self.dataset in ['validation', 'trainval']: images += self.split[name]['validation'] for image in images: boxes = image[:-3] + 'txt' paths.append((image, boxes, name.replace('_', ' '))) return paths def make_split(self, proportion=0.8): """Make a random split of the given dataset. It will look for all the images inside each class and will generate a JSON file with the training images and the validation images for each class. Arguments: proportion (float, optional): The proportion of training images to select from the total number of images. """ classes = [directory for directory in os.listdir(self.root) if os.path.isdir(os.path.join(self.root, directory))] split = {} for class_name in classes: images = [os.path.join(self.root, class_name, file) for file in os.listdir(os.path.join(self.root, class_name)) if file[-4:] == '.jpg'] training = random.sample(images, int(len(images) * proportion)) validation = list(set(images) - set(training)) split[class_name] = { 'training': training, 'validation': validation } split_file = os.path.join(self.root, 'split.json') with open(split_file, 'w') as file: file.write(json.dumps(split)) self.split = split self.print_stats() return split def print_stats(self): """Print the distribution of the classes in the given split json file.""" classes = [class_name for class_name in self.split] print('Classes: {}'.format(len(classes))) for name in classes: print('{} {} {}'.format( name.ljust(30, '.'), str(len(self.split[name]['training'])).rjust(3), str(len(self.split[name]['validation'])).rjust(3)))
Static methods
def validate_dataset(dataset)
-
Source code
@staticmethod def validate_dataset(dataset): if dataset not in ['training', 'validation', 'trainval']: raise ValueError('Please provide a valid dataset. Options: "training", "validation" and "trainval".') return dataset
def validate_name(name)
-
Source code
@staticmethod def validate_name(name): if name not in ['S1', 'S2', 'M']: raise ValueError('You must indicate a valid name. Options: S1, S2 and M. Value: {}'.format(name)) return 'INSTRE-' + name
Methods
def __init__(self, root, name='S2', dataset='training', transform=None)
-
Initialize the dataset.
Arguments
root
:str
- The path to the root of the directory.
name
:str
, optional- The name of the dataset to load.
dataset
:str
, optional- The portion of the dataset to load.
transform
:callable
, optional- The transformation to apply to the image and bounding boxes.
Source code
def __init__(self, root, name='S2', dataset='training', transform=None): """Initialize the dataset. Arguments: root (str): The path to the root of the directory. name (str, optional): The name of the dataset to load. dataset (str, optional): The portion of the dataset to load. transform (callable, optional): The transformation to apply to the image and bounding boxes. """ self.name = self.validate_name(name) self.dataset = self.validate_dataset(dataset) self.root = self.validate_root(root) self.split = self.get_split() self.class_to_label, self.label_to_class = self.generate_labels() self.paths = self.generate_paths() self.transform = transform
def generate_labels(self)
-
Generate the dicts to map the labels with the classes.
Returns
dict
- A dict that maps the class (str) -> label (int).
dict
- A dict that maps the label (int) -> class (str).
Source code
def generate_labels(self): """Generate the dicts to map the labels with the classes. Returns: dict: A dict that maps the class (str) -> label (int). dict: A dict that maps the label (int) -> class (str). """ classes = [name.replace('_', ' ') for name in self.split.keys()] classes.sort() return ( {name: i for i, name in enumerate(classes)}, {i: name for i, name in enumerate(classes)} )
def generate_paths(self)
-
Generate tuples with (image file, boxes file, class name) for each image in the dataset.
Returns
list of tuples: A list with tuples like (image file, boxes file, class name).
Source code
def generate_paths(self): """Generate tuples with (image file, boxes file, class name) for each image in the dataset. Returns: list of tuples: A list with tuples like (image file, boxes file, class name). """ paths = [] for name in self.split.keys(): images = [] if self.dataset in ['training', 'trainval']: images += self.split[name]['training'] if self.dataset in ['validation', 'trainval']: images += self.split[name]['validation'] for image in images: boxes = image[:-3] + 'txt' paths.append((image, boxes, name.replace('_', ' '))) return paths
def get_boxes(self, file, class_name)
-
Read the file and parse the bounding boxes.
Return
torch.Tensor: The bounding boxes loaded from the file. Shape:
(num of boxes, 4)
with x1,y1,x2,y2.Source code
def get_boxes(self, file, class_name): """Read the file and parse the bounding boxes. Return: torch.Tensor: The bounding boxes loaded from the file. Shape: `(num of boxes, 4)` with x1,y1,x2,y2. """ boxes = [] label = self.class_to_label[class_name] with open(file, 'r') as file: for line in file.readlines(): x, y, w, h = [int(val) for val in line.split()] x2, y2 = x + w, y + h boxes.append(torch.Tensor([x, y, x2, y2, label])) return torch.stack(boxes)
def get_split(self)
-
Get the split file or generate a new one.
Returns
dict
- A dict with all the class names with a 'training' and a 'validation' list of of images paths.
Source code
def get_split(self): """Get the split file or generate a new one. Returns: dict: A dict with all the class names with a 'training' and a 'validation' list of of images paths. """ file = os.path.join(self.root, 'split.json') if not os.path.exists(file): print('There is no split for the dataset, generating a new random one.') return self.split() with open(file, 'r') as file: return json.loads(file.read())
def make_split(self, proportion=0.8)
-
Make a random split of the given dataset.
It will look for all the images inside each class and will generate a JSON file with the training images and the validation images for each class.
Arguments
proportion
:float
, optional- The proportion of training images to select from the total number of images.
Source code
def make_split(self, proportion=0.8): """Make a random split of the given dataset. It will look for all the images inside each class and will generate a JSON file with the training images and the validation images for each class. Arguments: proportion (float, optional): The proportion of training images to select from the total number of images. """ classes = [directory for directory in os.listdir(self.root) if os.path.isdir(os.path.join(self.root, directory))] split = {} for class_name in classes: images = [os.path.join(self.root, class_name, file) for file in os.listdir(os.path.join(self.root, class_name)) if file[-4:] == '.jpg'] training = random.sample(images, int(len(images) * proportion)) validation = list(set(images) - set(training)) split[class_name] = { 'training': training, 'validation': validation } split_file = os.path.join(self.root, 'split.json') with open(split_file, 'w') as file: file.write(json.dumps(split)) self.split = split self.print_stats() return split
def print_stats(self)
-
Print the distribution of the classes in the given split json file.
Source code
def print_stats(self): """Print the distribution of the classes in the given split json file.""" classes = [class_name for class_name in self.split] print('Classes: {}'.format(len(classes))) for name in classes: print('{} {} {}'.format( name.ljust(30, '.'), str(len(self.split[name]['training'])).rjust(3), str(len(self.split[name]['validation'])).rjust(3)))
def validate_root(self, root)
-
Source code
def validate_root(self, root): if not os.path.exists(root): raise ValueError('"{}" does not exists.'.format(root)) if self.name not in os.listdir(root): raise ValueError('The dataset "{}" is not the root directory. ' 'Are you sure that is the correct directory?'.format(self.name)) return os.path.join(root, self.name)
Inherited members