--- title: PawTrace emoji: 🐕 colorFrom: yellow colorTo: green sdk: docker app_port: 7860 pinned: false short_description: Find a lost dog by its image — AI re-identification demo --- # PawTrace — image-based dog re-identification Upload a photo of a dog and PawTrace ranks a database of found dogs by how closely each one matches that specific dog, not just its breed. It runs two pipelines over a shared ResNet-101 backbone: an image similarity search fine-tuned to tell individuals apart, and a breed classifier. **Live demo:** https://pawtrace.predx.com/ — read-only, searching 1,000 sample dogs (3,474 photos). ## The model A ResNet-101 model initialized from a dog-breed classifier ([jhoppanne/Dogs-Breed-Image-Classification-V1](https://huggingface.co/jhoppanne/Dogs-Breed-Image-Classification-V1)) and fine-tuned with batch-hard triplet loss, producing a 2,048-dimension L2-normalized embedding per image. Two images of the same dog land close together; two look-alikes do not. Measured on 419 held-out dogs (2,176 photos) under the standard re-ID protocol, where every image queries all the others and is excluded from its own gallery: | Split | Rank-1 | Top-5 | Top-10 | mAP | |----------|--------|-------|--------|-------| | Face | 90.9% | 97.0% | 98.8% | 65.0% | | Body | 93.3% | 97.1% | 98.1% | 66.4% | | Combined | 92.4% | 97.0% | 98.2% | 65.6% | Fine-tuning lifted overall Rank-1 from 71.6% to 92.6%, a gain of 21 points over the pre-trained backbone. The gain is much larger on full-body photos (67.6% → 93.3%) than on faces (84.3% → 91.2%), because a breed-oriented encoder already preserves enough facial detail to distinguish individuals, while it collapses body shots of the same breed together. Training code is in [`backend/scripts/train_reid.py`](./backend/scripts/train_reid.py). > **Caveat worth stating.** The training and test sets draw heavily on YT-BB-Dog, where every image > of a dog comes from a single video, so background is constant within an identity. These numbers > therefore describe retrieval within a session, and the real lost-dog case — a photo taken today > against one taken last week, somewhere else — should be expected to be harder. Measuring that gap > properly needs a dataset following the same dogs across days and locations at usable resolution, > which no public set currently offers. ## Architecture ``` React + Vite + TS (SPA, Tailwind) ──HTTP/JSON──> FastAPI PC + mobile camera capture ├─ Auth (JWT, bcrypt) ├─ Dogs / Cases / Matches (SQLAlchemy → SQLite) ├─ Image pipeline: validate→normalize→store→embed ├─ Matching: ZIP-radius filter → cosine → rank ├─ Notifier (console email default; SMTP optional) └─ Geo (ZIP centroids + haversine) ``` A dog's match score is the maximum cosine similarity across every (query photo × candidate photo) pair, so one weak upload cannot drag down a dog that also has a good photo, and extra angles only help. Embeddings are stored tagged with model name and version, so vectors from different model generations are never compared. Four seams are interfaces with config-selected implementations: `Embedder`, `BreedClassifier`, `StorageBackend` (local→S3), and `VectorIndex` (NumPy brute force→FAISS). Search is exact brute-force cosine, which is correct and fast at this scale. ## Running locally Requires Python 3.11+ (developed on 3.13) and Node 18+ (developed on 24). ```bash cd backend python -m venv .venv # Windows PowerShell: .venv\Scripts\Activate.ps1 # Windows git-bash: source .venv/Scripts/activate # macOS/Linux: source .venv/bin/activate pip install -r requirements.txt cp ../.env.example ../.env # optional; sane defaults apply with no .env python -m scripts.seed # seeds the admin account uvicorn app.main:app --reload --port 8000 # API + docs at /docs ``` ```bash cd frontend npm install npm run dev # http://localhost:5173, proxies /api to :8000 ``` `scripts.seed` creates `admin@example.com` and prints a randomly generated password once. Set `SEED_ADMIN_PASSWORD` beforehand to choose your own. The re-ID model is not used by default locally — see Configuration. To run against the same weights as the demo, place `best.pt` at the repo root and set `EMBEDDER=reid`. ## Configuration Tunables live in [`.env.example`](./.env.example). The two that matter most: | Variable | Values | Deployed as | |---|---|---| | `EMBEDDER` | `mock` \| `hf` \| `reid` | `reid` (the fine-tuned checkpoint) | | `BREED_CLASSIFIER` | `mock` \| `hf` | `hf` | `mock` is a deterministic stand-in that needs no model weights and exists so the test suite runs fast and offline. `hf` downloads the breed model from Hugging Face on first use. `reid` loads the fine-tuned checkpoint from `REID_MODEL_PATH`. Other settings cover match thresholds, `TOP_N`, `RADIUS_LEVELS`, image size limits, and storage/notifier selection. ## Demo mode With `DEMO_MODE=true` (how the public demo runs), middleware rejects every mutating HTTP request, so the database cannot be modified from the UI, a direct API call, or curl. The only exceptions are the two photo-search endpoints, which persist nothing. Safety is enforced on the server, not by hiding buttons in the interface. ## Tests ```bash cd backend && pytest -q # 106 tests cd frontend && npx vitest run # 22 tests ``` Tests use the mock embedder and breed classifier, so they need no weights, no downloads, and no GPU. ## Data & privacy - Uploaded images are auto-oriented and stripped of EXIF/GPS on ingest; only a processed JPEG and thumbnail are stored on disk, with the path in the database rather than the blob. - Contact is mediated: home addresses and exact shelter/vet locations are never shown to the other party, only ZIP-level location. Reports are rate-limited. - `data/zip_centroids.csv` is a representative sample of US ZIP centroids. Drop in a full public-domain dataset with the same `zip,lat,lng` columns for complete coverage. ## Admin and batch loading Bulk data is managed as datasets, each load grouped, inspectable, and purgeable as a unit, under `/admin`. Load from a folder of dog-identity subfolders (one subfolder per dog), then generate embeddings and breed predictions in a single pass per image: ```bash cd backend python -m scripts.load_dataset --folder /path/to/dogs --type unknown \ --name "My dataset" --csv /path/to/dogs/found_dogs.csv python -m scripts.process_dataset --all # or --dataset-id N ``` Admin endpoints cover `GET/DELETE /admin/datasets[/{id}]`, embedding, matching, and async loading via `POST /admin/datasets/load` with `GET /admin/jobs/{id}` for progress. Design notes are in [`DECISIONS.md`](./DECISIONS.md). ## Project layout ``` backend/ FastAPI app, services, ML interfaces, Alembic migrations, tests, scripts frontend/ React + Vite + TS SPA (Tailwind) data/ zip_centroids.csv, media/ (gitignored) ``` ## Credits Built on open models and datasets from the research community: DogFaceNet, the Multi-Pose Dog Dataset, YT-BB-Dog, Stanford Dogs, and the Hugging Face breed classifier above. Full attributions and licenses are on the demo's Credits page.