Spaces:
Runtime error
Runtime error
| """Seed ~200 distractor sightings using the Dog CEO public API (dog.ceo). | |
| The API serves images from the Stanford Dogs dataset via CDN — no HF account, | |
| no dataset scripts, no large downloads. Each image is fetched individually. | |
| Run inside the backend container: | |
| python -m scripts.seed_filler | |
| """ | |
| import argparse | |
| import io | |
| import logging | |
| import random | |
| import time | |
| from collections import defaultdict | |
| import requests | |
| from PIL import Image | |
| from sqlalchemy import select | |
| from tqdm import tqdm | |
| from app.config import get_settings | |
| from app.models import Sighting | |
| from scripts._common import insert_sighting, log, open_session | |
| _API_BATCH = 50 # dog.ceo supports up to 50 random images per request | |
| _API_URL = "https://dog.ceo/api/breeds/image/random/{n}" | |
| def _fetch_urls(total: int) -> list[tuple[str, str]]: | |
| """Return list of (image_url, breed) from dog.ceo. Breed extracted from URL path.""" | |
| results: list[tuple[str, str]] = [] | |
| while len(results) < total: | |
| n = min(_API_BATCH, total - len(results)) | |
| resp = requests.get(_API_URL.format(n=n), timeout=15) | |
| resp.raise_for_status() | |
| for url in resp.json().get("message", []): | |
| # URL format: https://images.dog.ceo/breeds/{breed}/{file} | |
| parts = url.rstrip("/").split("/") | |
| breed = parts[-2] if len(parts) >= 2 else "unknown" | |
| results.append((url, breed)) | |
| return results[:total] | |
| def _download_image(url: str, timeout: int = 20) -> Image.Image | None: | |
| try: | |
| resp = requests.get(url, timeout=timeout) | |
| resp.raise_for_status() | |
| return Image.open(io.BytesIO(resp.content)).convert("RGB") | |
| except Exception as exc: # noqa: BLE001 | |
| log.warning("Failed to download %s: %s", url, exc) | |
| return None | |
| def main() -> None: | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument( | |
| "--force", | |
| action="store_true", | |
| help="Re-seed even if filler rows already exist.", | |
| ) | |
| parser.add_argument("--seed", type=int, default=42) | |
| args = parser.parse_args() | |
| logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s") | |
| settings = get_settings() | |
| rng = random.Random(args.seed) | |
| session = open_session() | |
| try: | |
| existing = session.scalar( | |
| select(Sighting).where(Sighting.source == "filler").limit(1) | |
| ) | |
| if existing and not args.force: | |
| log.info("Filler rows already present — skipping (pass --force to re-seed).") | |
| return | |
| if existing and args.force: | |
| deleted = session.query(Sighting).filter( | |
| Sighting.source == "filler" | |
| ).delete() | |
| session.commit() | |
| log.info("Deleted %d existing filler rows.", deleted) | |
| target_count = settings.seed_filler_count | |
| # Fetch extra URLs upfront to account for download failures / detection misses. | |
| fetch_count = int(target_count * 1.5) | |
| log.info("Fetching %d dog image URLs from dog.ceo...", fetch_count) | |
| urls = _fetch_urls(fetch_count) | |
| rng.shuffle(urls) | |
| per_breed: dict[str, int] = defaultdict(int) | |
| max_per_breed = max(1, target_count // settings.seed_filler_breeds * 2) | |
| inserted = 0 | |
| with tqdm(total=target_count, desc="seed:filler") as pbar: | |
| for url, breed in urls: | |
| if inserted >= target_count: | |
| break | |
| if per_breed[breed] >= max_per_breed: | |
| continue | |
| image = _download_image(url) | |
| if image is None: | |
| continue | |
| row = insert_sighting( | |
| session, | |
| image, | |
| source="filler", | |
| identity=None, | |
| rng=rng, | |
| prefix="filler", | |
| ) | |
| if row is None: | |
| time.sleep(0.05) # brief pause after detection miss | |
| continue | |
| per_breed[breed] += 1 | |
| inserted += 1 | |
| pbar.update(1) | |
| if inserted % 25 == 0: | |
| session.commit() | |
| session.commit() | |
| log.info( | |
| "Inserted %d filler sightings across %d breeds.", | |
| inserted, | |
| len(per_breed), | |
| ) | |
| finally: | |
| session.close() | |
| if __name__ == "__main__": | |
| main() | |