| """ |
| Dataset loading and splitting for cross-modal retrieval. |
| |
| Handles: |
| - Loading CrisisLandMark dataset |
| - Per-modality preprocessing |
| - Query/gallery splitting (80/20) |
| - Ground-truth label preparation |
| """ |
|
|
| import torch |
| from torch.utils.data import Dataset, DataLoader |
| from pathlib import Path |
| from typing import Tuple, List, Dict, Optional |
| import numpy as np |
| from PIL import Image |
| from sklearn.model_selection import train_test_split |
|
|
| from .preprocessing import preprocess_image, handle_channels |
|
|
|
|
| class CrisisLandMarkDataset(Dataset): |
| """ |
| Dataset class for CrisisLandMark satellite imagery. |
| |
| Supports: |
| - Optical (Sentinel-2 RGB) |
| - SAR (Sentinel-1 VV/VH) |
| - Multispectral (Sentinel-2 all bands) |
| """ |
| |
| def __init__( |
| self, |
| data_dir: str = "data/raw/crisislandmark", |
| modality: str = "optical", |
| split: str = "train", |
| transform=None, |
| size: int = 224 |
| ): |
| """ |
| Initialize dataset. |
| |
| Args: |
| data_dir: Path to dataset |
| modality: "optical", "sar", or "multispectral" |
| split: "train", "validation", or "test" |
| transform: Optional custom transform |
| size: Image resize size |
| """ |
| self.data_dir = Path(data_dir) |
| self.modality = modality |
| self.split = split |
| self.size = size |
| self.transform = transform |
| |
| |
| |
| self.samples = self._load_samples() |
| self.labels = self._load_labels() |
| |
| def _load_samples(self) -> List[Dict]: |
| """Load sample metadata.""" |
| |
| return [{"id": i, "path": f"sample_{i}.png"} for i in range(100)] |
| |
| def _load_labels(self) -> Dict[int, int]: |
| """Load ground-truth labels.""" |
| |
| return {i: i % 10 for i in range(100)} |
| |
| def __len__(self) -> int: |
| return len(self.samples) |
| |
| def __getitem__(self, idx: int) -> Tuple[torch.Tensor, int, int]: |
| """ |
| Get sample. |
| |
| Returns: |
| (image_tensor, modality_label, class_label) |
| """ |
| sample = self.samples[idx] |
| |
| |
| image = Image.fromarray(np.random.randint(0, 255, (256, 256, 3), dtype=np.uint8)) |
| |
| |
| if self.transform: |
| image_tensor = self.transform(image) |
| else: |
| image_tensor = preprocess_image(image, self.modality, self.size) |
| |
| |
| modality_label = {"optical": 0, "sar": 1, "multispectral": 2}[self.modality] |
| |
| |
| class_label = self.labels.get(idx, 0) |
| |
| return image_tensor, modality_label, class_label |
|
|
|
|
| def create_splits( |
| dataset: CrisisLandMarkDataset, |
| query_ratio: float = 0.2, |
| seed: int = 42 |
| ) -> Tuple[List[int], List[int]]: |
| """ |
| Create query/gallery split with no overlap. |
| |
| Args: |
| dataset: Full dataset |
| query_ratio: Fraction for query set |
| seed: Random seed for reproducibility |
| |
| Returns: |
| (query_indices, gallery_indices) |
| """ |
| indices = list(range(len(dataset))) |
| |
| |
| labels = [dataset.labels.get(i, 0) for i in indices] |
| |
| query_idx, gallery_idx = train_test_split( |
| indices, |
| test_size=1 - query_ratio, |
| random_state=seed, |
| stratify=labels |
| ) |
| |
| |
| assert len(set(query_idx) & set(gallery_idx)) == 0, "Query and gallery sets overlap!" |
| |
| return query_idx, gallery_idx |
|
|
|
|
| def get_dataloaders( |
| data_dir: str = "data/raw/crisislandmark", |
| modality: str = "optical", |
| batch_size: int = 32, |
| size: int = 224, |
| num_workers: int = 4 |
| ) -> Tuple[DataLoader, DataLoader]: |
| """ |
| Get train/test dataloaders. |
| |
| Args: |
| data_dir: Path to dataset |
| modality: Modality type |
| batch_size: Batch size |
| size: Image size |
| num_workers: Number of workers |
| |
| Returns: |
| (train_loader, test_loader) |
| """ |
| train_dataset = CrisisLandMarkDataset(data_dir, modality, "train", size=size) |
| test_dataset = CrisisLandMarkDataset(data_dir, modality, "test", size=size) |
| |
| train_loader = DataLoader( |
| train_dataset, |
| batch_size=batch_size, |
| shuffle=True, |
| num_workers=num_workers |
| ) |
| |
| test_loader = DataLoader( |
| test_dataset, |
| batch_size=batch_size, |
| shuffle=False, |
| num_workers=num_workers |
| ) |
| |
| return train_loader, test_loader |
|
|
|
|
| |
| if __name__ == "__main__": |
| |
| dataset = CrisisLandMarkDataset(modality="optical") |
| |
| |
| query_idx, gallery_idx = create_splits(dataset, query_ratio=0.2) |
| |
| print(f"Total samples: {len(dataset)}") |
| print(f"Query set: {len(query_idx)} samples") |
| print(f"Gallery set: {len(gallery_idx)} samples") |
| print(f"Overlap: {len(set(query_idx) & set(gallery_idx))} (should be 0)") |
| |
| |
| train_loader, test_loader = get_dataloaders(modality="optical", batch_size=4) |
| batch = next(iter(train_loader)) |
| |
| print(f"\nBatch shapes:") |
| print(f" Images: {batch[0].shape}") |
| print(f" Modality labels: {batch[1]}") |
| print(f" Class labels: {batch[2]}") |
| |
| print("\nDataset test passed!") |