Spaces:
Runtime error
Runtime error
File size: 6,207 Bytes
d32533a 706f0cb d32533a 706f0cb d32533a 706f0cb d32533a 706f0cb d32533a 706f0cb d32533a 706f0cb d32533a 706f0cb d32533a 706f0cb d32533a 706f0cb d32533a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | """Seed target identity sightings from `seed_data/targets/{identity}/*.jpg`.
For each identity folder, the script holds out one photo into
`seed_data/holdout/{identity}.{ext}` (so you can use it as a query) and
inserts the rest into the database, marked source='target' identity='{name}'.
The identity column is for evaluation only — the search API never reads it.
Run inside the backend container:
python -m scripts.seed_targets
"""
import argparse
import logging
import random
import shutil
from pathlib import Path
from PIL import Image
from sqlalchemy import select
from app.config import get_settings
from app.models import LostDog, Sighting
from scripts._common import (
insert_sighting,
log,
offset_within_meters,
open_session,
random_city_point,
random_recent_timestamp,
)
VALID_EXTS = {".jpg", ".jpeg", ".png", ".webp"}
def _is_image(p: Path) -> bool:
return p.is_file() and p.suffix.lower() in VALID_EXTS
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"--force",
action="store_true",
help="Re-seed even if target rows already exist.",
)
parser.add_argument("--seed", type=int, default=7)
parser.add_argument(
"--root",
type=Path,
default=Path("/seed_data"),
help="Root containing targets/ and holdout/ subdirs.",
)
args = parser.parse_args()
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
rng = random.Random(args.seed)
targets_root: Path = args.root / "targets"
holdout_root: Path = args.root / "holdout"
holdout_root.mkdir(parents=True, exist_ok=True)
if not targets_root.exists():
log.info(
"No targets directory at %s — drop dogs into seed_data/targets/{name}/ "
"and re-run. Skipping.",
targets_root,
)
return
identity_dirs = [d for d in sorted(targets_root.iterdir()) if d.is_dir()]
if not identity_dirs:
log.info(
"targets/ is empty — drop ~5 dogs with 2-3 photos each into "
"seed_data/targets/{name}/ and re-run. Skipping."
)
return
session = open_session()
try:
existing = session.scalar(
select(Sighting).where(Sighting.source == "target").limit(1)
)
if existing and not args.force:
log.info("Target rows already present — skipping (pass --force to re-seed).")
return
if existing and args.force:
# Cascade-delete the LostDog rows; FK ondelete=CASCADE will remove
# the linked sightings too.
session.query(LostDog).filter(
LostDog.id.in_(
select(Sighting.lost_dog_id)
.where(Sighting.source == "target")
.where(Sighting.lost_dog_id.is_not(None))
)
).delete(synchronize_session=False)
# Catch any orphan target sightings (no lost_dog_id) too.
session.query(Sighting).filter(Sighting.source == "target").delete()
session.commit()
settings = get_settings()
total_dogs = 0
total_photos = 0
for ident_dir in identity_dirs:
identity = ident_dir.name
photos = sorted([p for p in ident_dir.iterdir() if _is_image(p)])
if len(photos) < 2:
log.warning(
"Identity '%s' has %d photo(s); need at least 2 (one for index, "
"one as holdout query). Skipping.",
identity,
len(photos),
)
continue
# One "home turf" point per identity — every photo of this dog is placed
# within seed_target_radius_m (default 100 m) of this center, so the
# matched sightings cluster tightly on the map.
home_lat, home_lng = random_city_point(rng)
# Hold out one photo deterministically (last one alphabetically).
holdout_src = photos[-1]
index_photos = photos[:-1]
holdout_dst = holdout_root / f"{identity}{holdout_src.suffix.lower()}"
shutil.copyfile(holdout_src, holdout_dst)
log.info(
"Identity '%s': home=(%.5f, %.5f), indexing %d photo(s), holdout=%s",
identity,
home_lat,
home_lng,
len(index_photos),
holdout_dst.name,
)
# Create the LostDog cluster row first so FKs are valid.
lost_dog = LostDog(
name=identity.title(),
last_seen_at=random_recent_timestamp(rng),
last_seen_lat=home_lat,
last_seen_lng=home_lng,
contact_name=f"{identity.title()}'s Owner",
contact_email="koenig.andres@gmail.com",
contact_phone=None,
)
session.add(lost_dog)
session.flush() # populate lost_dog.id
total_dogs += 1
for photo in index_photos:
try:
image = Image.open(photo)
image.load()
except Exception as exc: # noqa: BLE001
log.warning("Could not open %s: %s", photo, exc)
continue
coords = offset_within_meters(
rng, home_lat, home_lng, settings.seed_target_radius_m
)
row = insert_sighting(
session,
image,
source="target",
identity=identity,
coords=coords,
rng=rng,
prefix="target",
lost_dog_id=lost_dog.id,
)
if row is not None:
total_photos += 1
session.commit()
log.info(
"Seeded %d lost-dog clusters with %d total reference photos.",
total_dogs,
total_photos,
)
finally:
session.close()
if __name__ == "__main__":
main()
|