import json import os from pathlib import Path from PIL import Image # --------------------------------------------------------------------------- # Paths # --------------------------------------------------------------------------- BASE_DIR = Path(__file__).resolve().parents[1] RAW_DIR = BASE_DIR / "data" / "raw" CROPS_DIR = BASE_DIR / "data" / "crops" OCCUPIED_DIR = CROPS_DIR / "occupied" EMPTY_DIR = CROPS_DIR / "empty" SPLITS = ["train", "valid", "test"] # Max crops per class — more than enough for MobileNetV2 MAX_PER_CLASS = 4000 # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def load_coco(path: Path) -> dict: with open(path, "r") as f: return json.load(f) def get_category_map(coco: dict) -> dict: cat_map = {} for c in coco["categories"]: if c["name"] == "space-occupied": cat_map[c["id"]] = "occupied" elif c["name"] == "space-empty": cat_map[c["id"]] = "empty" return cat_map def get_image_map(coco: dict, split_dir: Path) -> dict: return { img["id"]: split_dir / img["file_name"] for img in coco["images"] } # --------------------------------------------------------------------------- # Core # --------------------------------------------------------------------------- def process_split(split: str, counters: dict): split_dir = RAW_DIR / split coco_file = split_dir / "_annotations.coco.json" if not coco_file.exists(): print(f" Skipping {split} — not found.") return print(f"\nProcessing: {split}") coco = load_coco(coco_file) cat_map = get_category_map(coco) image_map = get_image_map(coco, split_dir) for ann in coco["annotations"]: # Stop early if both classes are full if counters["occupied"] >= MAX_PER_CLASS and counters["empty"] >= MAX_PER_CLASS: print(f" Cap reached — stopping {split} early.") break category_id = ann["category_id"] if category_id not in cat_map: continue label = cat_map[category_id] # Skip if this class is already full if counters[label] >= MAX_PER_CLASS: continue img_path = image_map.get(ann["image_id"]) if img_path is None or not img_path.exists(): continue x, y, w, h = [int(v) for v in ann["bbox"]] if w <= 0 or h <= 0: continue try: img = Image.open(img_path).convert("RGB") crop = img.crop((x, y, x + w, y + h)) except Exception: continue filename = f"{split}_{ann['image_id']}_{ann['id']}.jpg" if label == "occupied": crop.save(OCCUPIED_DIR / filename) else: crop.save(EMPTY_DIR / filename) counters[label] += 1 print(f" occupied so far : {counters['occupied']}") print(f" empty so far : {counters['empty']}") # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- def main(): OCCUPIED_DIR.mkdir(parents=True, exist_ok=True) EMPTY_DIR.mkdir(parents=True, exist_ok=True) counters = {"occupied": 0, "empty": 0} for split in SPLITS: if counters["occupied"] >= MAX_PER_CLASS and counters["empty"] >= MAX_PER_CLASS: break process_split(split, counters) print(f"\nDone.") print(f"Total occupied : {counters['occupied']}") print(f"Total empty : {counters['empty']}") if __name__ == "__main__": main()