import logging
import numpy as np
import math
from sklearn.model_selection import train_test_split
from skmultilearn.model_selection import iterative_train_test_split
from torch.utils.data import random_split
from ..base import BaseModel, BaseTask
from ..utils import (
load_aitlas_format_dataset,
load_folder_per_class_dataset,
load_voc_format_dataset,
)
from .schemas import SplitTaskSchema
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
[docs]class BaseSplitTask(BaseTask):
"""Base task meant to split dataset"""
schema = SplitTaskSchema
is_multilabel = False # specify it's a multilabel dataset or not
extensions = [
".jpg",
".jpeg",
".png",
".ppm",
".bmp",
".pgm",
".tif",
".tiff",
".webp",
]
def __init__(self, model: BaseModel, config):
super().__init__(model, config)
self.data_dir = self.config.data_dir
self.csv_file = self.config.csv_file
[docs] def run(self):
logging.info("Loading data...")
self.images = self.load_images(self.data_dir, self.csv_file)
logging.info("Making splits...")
# load the images and labels
self.X = np.array([x[0] for x in self.images])
self.y = np.array([y[1] for y in self.images])
self.split()
logging.info("And that's it!")
[docs] def has_val(self):
return self.config.split.val and self.config.split.val.ratio > 0
[docs] def is_split_valid(self):
res = self.config.split.train.ratio + self.config.split.test.ratio
if self.has_val():
res += self.config.split.val.ratio
return res == 100
[docs] def split(self):
if not self.is_split_valid():
raise ValueError(
"The defined split is invalid. The sum should be equal to 100."
)
# split the dataset
self.make_splits()
[docs] def save_split(self, X, y, file):
with open(file, "w") as f:
if self.is_multilabel:
row = "\t".join(self.header)
f.write(f"{row}\n")
for xx, yy in zip(X, y):
if self.is_multilabel:
# save in VOC format again
img = xx[0] if isinstance(xx, np.ndarray) else xx
img = img[img.rfind("images") + 7 : img.rfind(".")]
row = "\t".join([str(int(i)) for i in yy])
f.write(f"{img}\t{row}\n")
else:
f.write(f"{xx},{yy}\n")
f.close()
[docs] def load_images(self, data_dir, csv_file, extensions=None):
"""Attempts to read in VOC format, then in internal format, then in folder per class format"""
images = []
try:
images = load_voc_format_dataset(data_dir, csv_file)
# if this format is load, it's a multilabel dataset
self.is_multilabel = True
# read the header again. TODO: Maybe this can be a bit better implemented.
with open(csv_file, "rb") as f:
self.header = f.readline().decode("utf-8").strip().split("\t")
except TypeError: # it's not in VOC format, then let's try aitlas (CSV) internal one
if csv_file is not None:
images = load_aitlas_format_dataset(csv_file)
else:
if not extensions:
extensions = self.extensions
images = load_folder_per_class_dataset(data_dir, extensions)
if not images:
raise ValueError("No images were found!")
return images
[docs] def make_splits(self):
# load paths and labels
test_size = float(self.config.split.test.ratio / 100)
X_train, y_train, X_test, y_test = self.perform_split(self.X, self.y, test_size)
# if there is a validation split, perform that as well
if self.has_val():
val_size = float(
self.config.split.val.ratio
/ (self.config.split.val.ratio + self.config.split.train.ratio)
)
X_train, y_train, X_val, y_val = self.perform_split(
X_train, y_train, val_size
)
# save split
self.save_split(X_val, y_val, self.config.split.val.file)
# save the other splits
self.save_split(X_train, y_train, self.config.split.train.file)
self.save_split(X_test, y_test, self.config.split.test.file)
[docs]class RandomSplitTask(BaseSplitTask):
"""Randomly split a folder containing images"""
[docs]class StratifiedSplitTask(BaseSplitTask):
"""Meant for multilabel stratified slit"""