s1ngledoge's picture
upd
1c8d1ba
Raw
History Blame Contribute Delete
3.17 kB
from __future__ import annotations
import logging
import re
from pathlib import Path
import pandas as pd
from src.config import ALL_CATEGORIES_LABEL, PROJECT_ROOT
logger = logging.getLogger(__name__)
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".bmp", ".webp"}
METADATA_COLUMNS = ["id", "path", "filename", "category"]
class DatasetError(RuntimeError):
"""Raised when local dataset metadata is missing or invalid."""
def _slug(value: str) -> str:
slug = re.sub(r"[^A-Za-z0-9]+", "-", value.strip()).strip("-").lower()
return slug or "image"
def _relative_to_project(path: Path) -> str:
try:
return path.resolve().relative_to(PROJECT_ROOT).as_posix()
except ValueError:
return path.resolve().as_posix()
def scan_images(image_dir: Path) -> list[dict[str, str]]:
image_dir = Path(image_dir)
if not image_dir.exists():
logger.warning("Image directory does not exist: %s", image_dir)
return []
rows: list[dict[str, str]] = []
used_ids: dict[str, int] = {}
files = sorted(
path
for path in image_dir.rglob("*")
if path.is_file() and path.suffix.lower() in IMAGE_EXTENSIONS
)
for image_path in files:
if image_path.parent == image_dir:
category = "Uncategorized"
else:
category = image_path.parent.name
base_id = f"{_slug(category)}-{_slug(image_path.stem)}"
occurrence = used_ids.get(base_id, 0)
used_ids[base_id] = occurrence + 1
image_id = base_id if occurrence == 0 else f"{base_id}-{occurrence + 1}"
rows.append(
{
"id": image_id,
"path": _relative_to_project(image_path),
"filename": image_path.name,
"category": category,
}
)
return rows
def build_metadata(image_dir: Path, output_csv: Path) -> pd.DataFrame:
rows = scan_images(image_dir)
output_csv = Path(output_csv)
output_csv.parent.mkdir(parents=True, exist_ok=True)
metadata = pd.DataFrame(rows, columns=METADATA_COLUMNS)
metadata.to_csv(output_csv, index=False)
logger.info("Wrote metadata for %d image(s) to %s", len(metadata), output_csv)
return metadata
def load_metadata(metadata_csv: Path) -> pd.DataFrame:
metadata_csv = Path(metadata_csv)
if not metadata_csv.exists():
raise DatasetError(
f"metadata.csv was not found at {metadata_csv}. "
"Run `python scripts/build_metadata.py` after adding images."
)
metadata = pd.read_csv(metadata_csv, dtype=str).fillna("")
missing = [column for column in METADATA_COLUMNS if column not in metadata.columns]
if missing:
raise DatasetError(f"metadata.csv is missing required column(s): {', '.join(missing)}")
return metadata[METADATA_COLUMNS]
def get_categories(metadata_csv: Path) -> list[str]:
try:
metadata = load_metadata(metadata_csv)
except DatasetError:
return [ALL_CATEGORIES_LABEL]
categories = sorted(category for category in metadata["category"].dropna().unique() if category)
return [ALL_CATEGORIES_LABEL, *categories]