| import random |
| import torch |
| import numpy as np |
| from PIL import Image |
| from torch.utils.data import Dataset, Sampler |
| from torchvision.transforms import v2 |
| import cv2 |
|
|
| class Makeset(Dataset): |
| """ |
| Standard SOCRATE dataset for prediction and validation. |
| If we only want inference (no labels), `labels` can be None. |
| If we want complex training transformations, we can pass them via `transform`. |
| """ |
| def __init__(self, images, labels=None, transform=None, tokenizer=None, pad_id=None, bos_id=None, eos_id=None, height=32): |
| self.images = images |
| self.labels = labels |
| self.tokenizer = tokenizer |
| self.height = height |
| |
| self.pad_id = pad_id |
| self.bos_id = bos_id |
| self.eos_id = eos_id |
|
|
| if transform is None: |
| self.transform = v2.Compose([ |
| v2.ToImage(), |
| v2.ToDtype(torch.float32, scale=True), |
| v2.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) |
| ]) |
| else: |
| self.transform = transform |
|
|
| def __getitem__(self, idx): |
| image_data = self.images[idx] |
| |
| |
| if isinstance(image_data, str): |
| image = Image.open(image_data).convert("RGB") |
| else: |
| |
| image = Image.fromarray(cv2.cvtColor(image_data, cv2.COLOR_BGR2RGB)) |
| |
| w, h = image.size |
| new_h = self.height |
| new_w = max(1, int(w * new_h / h)) |
| image = v2.Resize((new_h, new_w))(image) |
| image = self.transform(image) |
|
|
| |
| if self.labels is not None and self.tokenizer is not None: |
| label = self.labels[idx] |
| label = self.tokenizer.encode(label).ids |
| label = [self.bos_id] + label + [self.eos_id] |
| label = torch.tensor(label, dtype=torch.long) |
| return image, label[:-1], label[1:] |
| |
| return image |
|
|
| def __len__(self): |
| return len(self.images) |
|
|
| def collate_fn(self, batch): |
| from torch.nn.utils.rnn import pad_sequence |
| |
| if self.labels is None: |
| images = batch |
| batch_size = len(images) |
| c = images[0].shape[0] |
| h = images[0].shape[1] |
| max_w = max(img.shape[2] for img in images) |
| new_images = images[0].new_zeros((batch_size, c, h, max_w)) |
| for i, img in enumerate(images): |
| w = img.shape[2] |
| new_images[i, :, :, :w] = img |
| return new_images |
| else: |
| images, label1, label2 = zip(*batch) |
| |
| |
| pad_val = self.pad_id if self.pad_id is not None else 0 |
| |
| label1 = pad_sequence(label1, batch_first=True, padding_value=pad_val) |
| label2 = pad_sequence(label2, batch_first=True, padding_value=pad_val) |
|
|
| batch_size = len(images) |
| c = images[0].shape[0] |
| h = images[0].shape[1] |
| max_w = max(img.shape[2] for img in images) |
|
|
| new_images = images[0].new_zeros((batch_size, c, h, max_w)) |
|
|
| for i, img in enumerate(images): |
| w = img.shape[2] |
| new_images[i, :, :, :w] = img |
|
|
| return new_images, label1, label2 |
|
|
| class SmartBatchSampler(Sampler): |
| """ |
| Custom Batch Sampler that groups labels by length |
| to minimize the padding required within each batch. |
| """ |
| def __init__(self, labels, batch_size): |
| self.batch_size = batch_size |
| labels_list = list(labels) |
| lengths = [len(str(lbl)) for lbl in labels_list] |
| sorted_indices = sorted(range(len(lengths)), key=lambda i: lengths[i]) |
| self.batches = [sorted_indices[i:i + batch_size] for i in range(0, len(sorted_indices), batch_size)] |
| |
| def __iter__(self): |
| random.shuffle(self.batches) |
| for batch in self.batches: |
| yield batch |
|
|
| def __len__(self): |
| return len(self.batches) |
|
|
| def load_dataset(path): |
| """ |
| Automatically loads images and labels from a supported file (.csv, .txt, .json, .yaml). |
| Returns (images, labels) as lists. |
| """ |
| import csv |
| import json |
| |
| images = [] |
| labels = [] |
| |
| ext = str(path).lower().split('.')[-1] |
| |
| if ext == 'csv': |
| with open(path, 'r', encoding='utf-8') as f: |
| reader = csv.DictReader(f) |
| if not reader.fieldnames: |
| raise ValueError("CSV is empty or missing headers.") |
| |
| img_col = next((col for col in reader.fieldnames if 'img' in col.lower() or 'image' in col.lower() or 'path' in col.lower()), reader.fieldnames[0]) |
| lbl_col = next((col for col in reader.fieldnames if 'lbl' in col.lower() or 'label' in col.lower() or 'text' in col.lower() or 'word' in col.lower()), reader.fieldnames[1] if len(reader.fieldnames) > 1 else None) |
| |
| for row in reader: |
| images.append(row[img_col]) |
| if lbl_col: |
| labels.append(row[lbl_col]) |
| |
| elif ext == 'txt': |
| with open(path, 'r', encoding='utf-8') as f: |
| for line in f: |
| parts = line.strip().split('\t') |
| if len(parts) >= 2: |
| images.append(parts[0]) |
| labels.append(parts[1]) |
| |
| elif ext in ['json']: |
| with open(path, 'r', encoding='utf-8') as f: |
| data = json.load(f) |
| for item in data: |
| img_key = next((k for k in item.keys() if 'img' in k.lower() or 'path' in k.lower()), None) |
| lbl_key = next((k for k in item.keys() if 'lbl' in k.lower() or 'text' in k.lower() or 'word' in k.lower()), None) |
| if img_key and lbl_key: |
| images.append(item[img_key]) |
| labels.append(item[lbl_key]) |
| |
| elif ext in ['yaml', 'yml']: |
| import yaml |
| with open(path, 'r', encoding='utf-8') as f: |
| data = yaml.safe_load(f) |
| for item in data: |
| img_key = next((k for k in item.keys() if 'img' in k.lower() or 'path' in k.lower()), None) |
| lbl_key = next((k for k in item.keys() if 'lbl' in k.lower() or 'text' in k.lower() or 'word' in k.lower()), None) |
| if img_key and lbl_key: |
| images.append(item[img_key]) |
| labels.append(item[lbl_key]) |
| else: |
| raise ValueError(f"Unsupported format: {ext}") |
| |
| return images, labels |
|
|