BioVision-Path / src /detection.py
BASHARAMEEZ's picture
Upload folder using huggingface_hub
c04bc97 verified
Raw
History Blame Contribute Delete
4.63 kB
import os
import xml.etree.ElementTree as ET
from PIL import Image
import torch
from torch.utils.data import Dataset, DataLoader, Subset
from src.preprocessing import preprocess_detection_sample
class BCCDDataset(Dataset):
"""
PyTorch Dataset for parsing and loading the BCCD Blood Cell smear images and XML annotations.
"""
def __init__(self, root_dir: str, target_size: tuple = (480, 640), label_map: dict = None):
"""
root_dir: path to the directory containing BCCD folder (e.g. bccd_temp/BCCD)
"""
self.root_dir = root_dir
self.target_size = target_size # (height, width)
# Default label mapping (0 is background for Faster R-CNN)
self.label_map = label_map if label_map is not None else {
"WBC": 1,
"RBC": 2,
"Platelets": 3
}
self.annotations_dir = os.path.join(root_dir, "Annotations")
self.images_dir = os.path.join(root_dir, "JPEGImages")
self.xml_files = sorted([f for f in os.listdir(self.annotations_dir) if f.endswith(".xml")])
def __len__(self):
return len(self.xml_files)
def _parse_xml(self, xml_path: str):
"""
Parses Pascal VOC XML annotation file.
"""
tree = ET.parse(xml_path)
root = tree.getroot()
boxes = []
labels = []
for obj in root.findall("object"):
label_name = obj.find("name").text
if label_name not in self.label_map:
continue
bndbox = obj.find("bndbox")
xmin = float(bndbox.find("xmin").text)
ymin = float(bndbox.find("ymin").text)
xmax = float(bndbox.find("xmax").text)
ymax = float(bndbox.find("ymax").text)
boxes.append([xmin, ymin, xmax, ymax])
labels.append(self.label_map[label_name])
return boxes, labels
def __getitem__(self, idx):
xml_name = self.xml_files[idx]
xml_path = os.path.join(self.annotations_dir, xml_name)
# Determine image path (replaces .xml with .jpg)
img_name = xml_name.replace(".xml", ".jpg")
img_path = os.path.join(self.images_dir, img_name)
# Load image
image = Image.open(img_path).convert("RGB")
# Parse XML bounding boxes
boxes, labels = self._parse_xml(xml_path)
target = {
"boxes": boxes,
"labels": labels
}
# Preprocess and resize image & boxes
img_tensor, target_preprocessed = preprocess_detection_sample(
image, target, target_size=self.target_size
)
return img_tensor, target_preprocessed
def detection_collate_fn(batch):
"""
Custom collate function for object detection batching.
Faster R-CNN expects:
images: list of tensors
targets: list of dictionaries
"""
return tuple(zip(*batch))
def get_bccd_dataloaders(root_dir: str, batch_size: int, target_size: tuple = (480, 640), demo_mode: bool = False, subset_size: int = 20):
"""
Loads, splits, and creates DataLoaders for BCCD dataset.
"""
dataset = BCCDDataset(root_dir, target_size=target_size)
total_samples = len(dataset)
if total_samples == 0:
raise ValueError(f"BCCD dataset at {root_dir} contains 0 samples. Verify path.")
# Split: 80% Train, 20% Val (and Test)
g = torch.Generator().manual_seed(42)
indices = torch.randperm(total_samples, generator=g).tolist()
val_split = int(np.floor(0.20 * total_samples))
train_split = total_samples - val_split
train_idx = indices[:train_split]
val_idx = indices[train_split:]
if demo_mode:
train_idx = train_idx[:subset_size]
val_idx = val_idx[:max(2, subset_size // 4)]
print(f"[Dataset] DEMO MODE: Subsetting BCCD dataset. Train: {len(train_idx)}, Val: {len(val_idx)}")
train_sub = Subset(dataset, train_idx)
val_sub = Subset(dataset, val_idx)
# Custom collate_fn is passed to DataLoader
train_loader = DataLoader(
train_sub,
batch_size=batch_size,
shuffle=True,
num_workers=0,
collate_fn=detection_collate_fn
)
val_loader = DataLoader(
val_sub,
batch_size=batch_size,
shuffle=False,
num_workers=0,
collate_fn=detection_collate_fn
)
return train_loader, val_loader
import numpy as np