| """ |
| Stanford Dogs dataset loader. |
| |
| 120 breeds, ~12K train, ~8.5K test images. |
| Uses torchvision ImageFolder with backbone-aware preprocessing. |
| """ |
|
|
| import os |
| import re |
|
|
| from torch.utils.data import DataLoader |
| from torchvision import datasets, transforms |
|
|
|
|
| |
| def clean_breed_name(folder_name: str) -> str: |
| """Strip synset ID prefix from Stanford Dogs folder names.""" |
| match = re.match(r"n\d+-(.+)", folder_name) |
| if match: |
| return match.group(1).replace("_", " ") |
| return folder_name.replace("_", " ") |
|
|
|
|
| def get_breed_names(data_dir: str) -> list[str]: |
| """Return sorted list of 120 breed names from dataset directory.""" |
| images_dir = os.path.join(data_dir, "Images") |
| if not os.path.isdir(images_dir): |
| raise FileNotFoundError(f"Dataset not found at {images_dir}. Run scripts/download_dataset.py first.") |
| folders = sorted(os.listdir(images_dir)) |
| return [clean_breed_name(f) for f in folders if os.path.isdir(os.path.join(images_dir, f))] |
|
|
|
|
| def get_transforms(preprocess_config: dict, is_train: bool = True) -> transforms.Compose: |
| """Build transforms from backbone's preprocess config. |
| |
| Args: |
| preprocess_config: dict with 'mean', 'std', 'input_size' from backbone |
| is_train: whether to apply training augmentations |
| """ |
| img_size = preprocess_config.get("input_size", 224) |
| mean = preprocess_config.get("mean", [0.485, 0.456, 0.406]) |
| std = preprocess_config.get("std", [0.229, 0.224, 0.225]) |
|
|
| if is_train: |
| return transforms.Compose([ |
| transforms.RandomResizedCrop(img_size, scale=(0.7, 1.0), ratio=(0.8, 1.2)), |
| transforms.RandomHorizontalFlip(p=0.5), |
| transforms.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.2, hue=0.05), |
| transforms.RandomRotation(15), |
| transforms.RandomAffine(degrees=0, translate=(0.05, 0.05)), |
| transforms.ToTensor(), |
| transforms.Normalize(mean=mean, std=std), |
| transforms.RandomErasing(p=0.1, scale=(0.02, 0.15)), |
| ]) |
| else: |
| return transforms.Compose([ |
| transforms.Resize(int(img_size * 1.14)), |
| transforms.CenterCrop(img_size), |
| transforms.ToTensor(), |
| transforms.Normalize(mean=mean, std=std), |
| ]) |
|
|
|
|
| def get_dataloaders( |
| data_dir: str, |
| preprocess_config: dict, |
| batch_size: int = 64, |
| num_workers: int = 4, |
| ) -> tuple[DataLoader, DataLoader, list[str]]: |
| """Create train and test dataloaders for Stanford Dogs. |
| |
| Returns: |
| (train_loader, test_loader, breed_names) |
| """ |
| images_dir = os.path.join(data_dir, "Images") |
|
|
| train_transform = get_transforms(preprocess_config, is_train=True) |
| test_transform = get_transforms(preprocess_config, is_train=False) |
|
|
| train_ds = datasets.ImageFolder(images_dir, transform=train_transform) |
| test_ds = datasets.ImageFolder(images_dir, transform=test_transform) |
|
|
| |
| |
| |
| |
|
|
| breed_names = [clean_breed_name(c) for c in train_ds.classes] |
|
|
| train_loader = DataLoader( |
| train_ds, |
| batch_size=batch_size, |
| shuffle=True, |
| num_workers=num_workers, |
| pin_memory=True, |
| drop_last=True, |
| persistent_workers=num_workers > 0, |
| ) |
| test_loader = DataLoader( |
| test_ds, |
| batch_size=batch_size, |
| shuffle=False, |
| num_workers=num_workers, |
| pin_memory=True, |
| persistent_workers=num_workers > 0, |
| ) |
|
|
| print(f" Train: {len(train_ds)} images, {len(breed_names)} breeds") |
| print(f" Test: {len(test_ds)} images") |
|
|
| return train_loader, test_loader, breed_names |
|
|