dr-severity-grading / src /data /aptos_dataset.py
mshaiel2004's picture
Upload folder using huggingface_hub
78856c0 verified
Raw History Blame Contribute Delete
4.34 kB
"""PyTorch Dataset class for the APTOS 2019 Blindness Detection challenge."""
import os
from typing import Callable, Dict, Optional, Tuple, Union
import cv2
import numpy as np
import pandas as pd
import torch
from torch.utils.data import Dataset
from src.data.preprocessing import ben_graham_pipeline
class APTOSDataset(Dataset):
"""APTOS 2019 Diabetic Retinopathy dataset."""
def __init__(
self,
df_or_csv: Union[pd.DataFrame, str],
images_dir: str,
transform: Optional[Callable] = None,
image_size: int = 380,
apply_ben_graham: bool = True,
is_preprocessed: bool = False,
) -> None:
"""Initialize APTOS dataset.
Args:
df_or_csv: DataFrame or path to CSV with columns ['id_code', 'diagnosis'].
images_dir: Path to image directory containing PNG images.
transform: Optional Albumentations transform pipeline.
image_size: Target image dimension for resizing / preprocessing.
apply_ben_graham: Whether to apply Ben Graham preprocessing on the fly.
is_preprocessed: If True, images in images_dir are already preprocessed.
"""
if isinstance(df_or_csv, str):
self.df = pd.read_csv(df_or_csv)
else:
self.df = df_or_csv.reset_index(drop=True)
self.images_dir = images_dir
self.transform = transform
self.image_size = image_size
self.apply_ben_graham = apply_ben_graham and not is_preprocessed
self.is_preprocessed = is_preprocessed
# Standardize column names
if "id_code" in self.df.columns:
self.image_ids = self.df["id_code"].values
elif "image_id" in self.df.columns:
self.image_ids = self.df["image_id"].values
else:
self.image_ids = self.df.iloc[:, 0].values
if "diagnosis" in self.df.columns:
self.labels = self.df["diagnosis"].values.astype(np.int64)
else:
self.labels = self.df.iloc[:, 1].values.astype(np.int64)
def __len__(self) -> int:
return len(self.df)
def __getitem__(self, idx: int) -> Tuple[torch.Tensor, int, str]:
"""Fetch image tensor, integer label, and image path.
Returns:
Tuple of (image_tensor, label, image_path).
"""
img_id = str(self.image_ids[idx])
# Support both raw id and id with extension
if not img_id.endswith((".png", ".jpg", ".jpeg")):
img_filename = f"{img_id}.png"
else:
img_filename = img_id
img_path = os.path.join(self.images_dir, img_filename)
# Fallback to jpg if png not found
if not os.path.exists(img_path):
alt_path = os.path.join(self.images_dir, f"{img_id}.jpg")
if os.path.exists(alt_path):
img_path = alt_path
image = cv2.imread(img_path)
if image is None:
raise FileNotFoundError(f"Failed to load image at {img_path}")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
if self.apply_ben_graham:
image = ben_graham_pipeline(image, image_size=self.image_size)
if self.transform is not None:
augmented = self.transform(image=image)
image_tensor = augmented["image"]
else:
# Fallback simple tensor conversion if no transform provided
image_tensor = torch.from_numpy(image.transpose(2, 0, 1)).float() / 255.0
label = int(self.labels[idx])
return image_tensor, label, img_path
def get_class_counts(self) -> Dict[int, int]:
"""Compute frequency per severity grade."""
unique, counts = np.unique(self.labels, return_counts=True)
return dict(zip(unique.tolist(), counts.tolist()))
def get_class_weights(self) -> torch.Tensor:
"""Compute balanced inverse-frequency class weights for loss functions.
weight_c = total_samples / (num_classes * count_c)
"""
counts = self.get_class_counts()
num_classes = 5
total = len(self.labels)
weights = []
for c in range(num_classes):
c_count = counts.get(c, 1)
weights.append(total / (num_classes * c_count))
return torch.tensor(weights, dtype=torch.float32)