| import os
|
| import random
|
| import torch
|
| from PIL import Image
|
|
|
|
|
| class DatasetLoader:
|
| def __init__(self, img_dir: str, prompt_path: str, batch_size: int, device: str, train_split: float):
|
| """
|
| A simple dataset loader
|
|
|
| :param img_dir: Path to the image directory
|
| :param prompt_path: Path to the prompt txt file, should be line-separated
|
| :param batch_size: Batch size
|
| :param device: Device to use (e.g. cpu or cuda)
|
| :param train_split: A float, ratio of train to val loss. Usually 0.9 or 0.95
|
| """
|
|
|
| assert 0.5 <= train_split < 1.0, f"Value of {train_split=} is not allowed"
|
|
|
| self.batch_size = batch_size
|
| self.device = device
|
|
|
| prompts = self.load_prompts(prompt_path)
|
| image_paths = [os.path.join(img_dir, p) for p in os.listdir(img_dir)]
|
|
|
|
|
| image_paths = sorted(image_paths, key=lambda x: int(x.split("_")[-1].split(".")[0]))
|
|
|
| assert len(image_paths) == len(prompts), f"Mismatch between {len(image_paths)=} and {len(prompts)=}"
|
|
|
|
|
| prompt_img_pairs = [(p, i) for p, i in zip(prompts, image_paths)]
|
|
|
| random.shuffle(prompt_img_pairs)
|
|
|
| n = int(len(prompt_img_pairs) * train_split)
|
| self.train_pairs = prompt_img_pairs[:n]
|
| self.val_pairs = prompt_img_pairs[n:]
|
|
|
| self.train_idx = 0
|
| self.val_idx = 0
|
|
|
| self.train_epoch = 0
|
| self.val_epoch = 0
|
|
|
| print("\n****************"
|
| "\nImportant Note- MAKE SURE the prompt-image pairs are correctly aligned!"
|
| "\nHere are the first few pairs for verification: "
|
| f"\n{self.train_pairs[:5]=}"
|
| "\n****************\n")
|
|
|
|
|
| @staticmethod
|
| def load_prompts(prompt_path):
|
| with open(prompt_path, "r", encoding="utf-8") as f:
|
| prompts = f.read().split("\n")
|
| prompts = [p for p in prompts if len(p) > 0]
|
|
|
| return prompts
|
|
|
|
|
| def get_batch(self, train: bool):
|
| """
|
| Returns a tensor of shape (b, 3, h, w) normalized to range [-1, 1].
|
| Assumes all images within given dir is of same shape
|
|
|
| :param train: Boolean of whether to return data from train or validation split
|
| :return: A tuple (prompt_batch, images) where prompt_batch is a list of strings, images is a tensor of shape (b, 3, h, w)
|
| """
|
|
|
| if train:
|
| batch = self.train_pairs[self.train_idx: self.train_idx + self.batch_size]
|
| self.train_idx += self.batch_size
|
|
|
| if self.train_idx + self.batch_size >= len(self.train_pairs):
|
| self.train_idx = 0
|
| self.train_epoch += 1
|
| random.shuffle(self.train_pairs)
|
| else:
|
| batch = self.val_pairs[self.val_idx: self.val_idx + self.batch_size]
|
| self.val_idx += self.batch_size
|
|
|
| if self.val_idx + self.batch_size >= len(self.val_pairs):
|
| self.val_idx = 0
|
| self.val_epoch += 1
|
| random.shuffle(self.val_pairs)
|
|
|
|
|
| prompt_batch = [e[0] for e in batch]
|
| img_batch = [e[1] for e in batch]
|
|
|
|
|
| images = [Image.open(i).convert("RGB") for i in img_batch]
|
|
|
|
|
| width, height = images[0].size
|
|
|
|
|
| images = torch.tensor([obj.getdata() for obj in images], dtype=torch.float32, device=self.device)
|
|
|
| images = images.reshape(len(images), width, height, 3)
|
| images = images.permute(0, 3, 2, 1)
|
|
|
| images = ((images / 255) - 0.5) * 2
|
|
|
| return prompt_batch, images
|
|
|
|
|
|
|
|
|