Elliott Duke commited on
Commit ·
b5442b0
1
Parent(s): fb71747
PawTrace read-only demo: reid model + 1000-dog haystack
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +3 -0
- .gitignore +11 -1
- DEPLOY.md +0 -113
- Dockerfile +23 -17
- README.md +11 -0
- backend/app/api/geo.py +17 -1
- backend/app/config.py +19 -4
- backend/app/main.py +20 -0
- backend/app/ml/breed.py +1 -1
- backend/app/ml/detector.py +0 -55
- backend/app/ml/embedder.py +53 -47
- backend/demo_data/app.db +2 -2
- backend/demo_data/media/known/1/1e6abffb49aa43f1822fb9ed29356967.jpg +0 -3
- backend/demo_data/media/known/1/41bc76f5b36340b8b59e95f25a406113.jpg +0 -3
- backend/demo_data/media/known/1/ca4f3fabe3034731bfb7e47e98244bc4.jpg +0 -3
- backend/demo_data/media/known/11/4b97be269fac4e63953eba8b0e41c43e.jpg +0 -3
- backend/demo_data/media/known/12/7f83295941134d89b033d8fec8dffd2f.jpg +0 -3
- backend/demo_data/media/known/12/a64dc67d5085476f881f971e73f52b2e.jpg +0 -3
- backend/demo_data/media/known/12/f93f2f92e16e43a79c0e1497ded8410a.jpg +0 -3
- backend/demo_data/media/known/13/450ea8a48cad492a9c49a11393eb8495.jpg +0 -3
- backend/demo_data/media/known/13/b1d596d553fe4e3cae4c67fe46b379c3.jpg +0 -3
- backend/demo_data/media/known/13/ee2e8e1152d740e6abfb0af8ef3c3f19.jpg +0 -3
- backend/demo_data/media/known/14/42294b0c875541fa878d6ab8225fb4de.jpg +0 -3
- backend/demo_data/media/known/14/c6f1598561454e65a522127bd8f01c6f.jpg +0 -3
- backend/demo_data/media/known/14/d86a962b033f466295244ea36f1789a9.jpg +0 -3
- backend/demo_data/media/known/15/101251303df44d1bac54e58b7b5e10d0.jpg +0 -3
- backend/demo_data/media/known/15/3e1114b46fa64b98bb50d1f14bc38bda.jpg +0 -3
- backend/demo_data/media/known/15/596faa34b27d411c9d7928bf41606f7b.jpg +0 -3
- backend/demo_data/media/known/16/16023f39dfdf40ff8b10f46f9718ab2b.jpg +0 -3
- backend/demo_data/media/known/16/2f7b765e96bf4a9b834574b55403c196.jpg +0 -3
- backend/demo_data/media/known/16/955a2d571eed41909ecf81bbbf34fd13.jpg +0 -3
- backend/demo_data/media/known/18/630f9c5a28604f14a5c50d2b1fca5996.jpg +0 -3
- backend/demo_data/media/known/18/a3694f54285e4aedb13b1502c5fd41b7.jpg +0 -3
- backend/demo_data/media/known/18/fef796259d264e43a3efa784d59b789f.jpg +0 -3
- backend/demo_data/media/known/19/2668ad9747c64280b49442b408892a00.jpg +0 -3
- backend/demo_data/media/known/19/3a8fcb16eea8449191a72b1e1f66b418.jpg +0 -3
- backend/demo_data/media/known/19/e3e4286484ff4295a3c2151d25ff8c1d.jpg +0 -3
- backend/demo_data/media/known/2/229eef530f064b99b72a4cf1a04f3eab.jpg +0 -3
- backend/demo_data/media/known/2/97bfa63313074cd888736d131f82bc6c.jpg +0 -3
- backend/demo_data/media/known/2/d745cafed42f43478a4f37fec4981352.jpg +0 -3
- backend/demo_data/media/known/2/dd63a482808b4ba1884d42c7e99b116c.jpg +0 -3
- backend/demo_data/media/known/21/0620a5b6c9794547925baeb807b1ecef.jpg +0 -3
- backend/demo_data/media/known/21/5c0acc4e4c964f6d813093cf62d22ded.jpg +0 -3
- backend/demo_data/media/known/21/6d9c1ee992d44a26a6350f6eeb1b3de7.jpg +0 -3
- backend/demo_data/media/known/22/68e56438501c460abc3aaf05e91b8346.jpg +0 -3
- backend/demo_data/media/known/22/a1a82403e1d0442e96ac9be580db1f35.jpg +0 -3
- backend/demo_data/media/known/22/d7101488750c4478802eb72c0d6fa8ce.jpg +0 -3
- backend/demo_data/media/known/23/091b968a546e47278d5d22054e73ab33.jpg +0 -3
- backend/demo_data/media/known/23/954a1c7a56ea46e5ae34b8b1fa2b5d24.jpg +0 -3
- backend/demo_data/media/known/23/a42d8b7dec254893a8b4fbaf8ef34979.jpg +0 -3
.gitattributes
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
backend/demo_data/app.db filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 3 |
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
+
# Large model weights go through git-LFS (needed for the Hugging Face Space repo).
|
| 2 |
+
best.pt filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 4 |
backend/demo_data/app.db filter=lfs diff=lfs merge=lfs -text
|
| 5 |
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 6 |
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
.gitignore
CHANGED
|
@@ -17,8 +17,11 @@ venv/
|
|
| 17 |
backend/data/
|
| 18 |
*.db
|
| 19 |
*.sqlite3
|
|
|
|
|
|
|
|
|
|
| 20 |
# NOTE: keep data/zip_centroids.csv tracked — the app needs it.
|
| 21 |
-
# EXCEPTION: the demo snapshot
|
| 22 |
!backend/demo_data/
|
| 23 |
!backend/demo_data/app.db
|
| 24 |
!backend/demo_data/media/
|
|
@@ -50,3 +53,10 @@ Thumbs.db
|
|
| 50 |
|
| 51 |
# Analysis notebooks — kept locally, excluded from the repo
|
| 52 |
*.ipynb
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
backend/data/
|
| 18 |
*.db
|
| 19 |
*.sqlite3
|
| 20 |
+
*.db-wal
|
| 21 |
+
*.db-shm
|
| 22 |
+
app.db.pre-*
|
| 23 |
# NOTE: keep data/zip_centroids.csv tracked — the app needs it.
|
| 24 |
+
# EXCEPTION: the shipped demo snapshot (the 1,000-dog haystack DB + its media) IS tracked.
|
| 25 |
!backend/demo_data/
|
| 26 |
!backend/demo_data/app.db
|
| 27 |
!backend/demo_data/media/
|
|
|
|
| 53 |
|
| 54 |
# Analysis notebooks — kept locally, excluded from the repo
|
| 55 |
*.ipynb
|
| 56 |
+
|
| 57 |
+
# Local re-ID training data (YT-BB-Dog + Sibetan, ~2GB) — never commit
|
| 58 |
+
reid_data/
|
| 59 |
+
|
| 60 |
+
# Local-only: personal test photos + one-off data-prep scripts (kept on disk, not deployed)
|
| 61 |
+
DobbyTesting/
|
| 62 |
+
reid_research/
|
DEPLOY.md
DELETED
|
@@ -1,113 +0,0 @@
|
|
| 1 |
-
# Deploying PawTrace to Render
|
| 2 |
-
|
| 3 |
-
This gets the whole app (frontend + backend) online as **one service** on Render, linkable from your
|
| 4 |
-
personal site. It starts in **mock mode** (free, fake match scores) so your first deploy costs nothing
|
| 5 |
-
and is low-risk; flipping to the real ML model is a small change at the end.
|
| 6 |
-
|
| 7 |
-
The repo already contains everything you need: a `Dockerfile` that builds the React app and runs the
|
| 8 |
-
FastAPI API that serves it, plus `render.yaml`.
|
| 9 |
-
|
| 10 |
-
---
|
| 11 |
-
|
| 12 |
-
## Step 1 — Put the code on GitHub
|
| 13 |
-
|
| 14 |
-
You've pushed frontends before, so this is familiar. One repo-specific thing first: **don't push the
|
| 15 |
-
giant dataset folders** — they're already in `.gitignore`, so just make sure you don't force-add them.
|
| 16 |
-
|
| 17 |
-
```bash
|
| 18 |
-
# from the project root
|
| 19 |
-
git init # if it isn't a git repo yet
|
| 20 |
-
git add .
|
| 21 |
-
git commit -m "PawTrace: add Render deploy (Dockerfile, render.yaml)"
|
| 22 |
-
```
|
| 23 |
-
|
| 24 |
-
Create a new **empty** repo on GitHub, then:
|
| 25 |
-
|
| 26 |
-
```bash
|
| 27 |
-
git branch -M main
|
| 28 |
-
git remote add origin https://github.com/<you>/<repo>.git
|
| 29 |
-
git push -u origin main
|
| 30 |
-
```
|
| 31 |
-
|
| 32 |
-
Sanity check on GitHub: the repo should be small (a few MB). If it's hundreds of MB, a dataset folder
|
| 33 |
-
slipped in — remove it (`git rm -r --cached "Multi-pose dog dataset"`), commit, and push again.
|
| 34 |
-
|
| 35 |
-
## Step 2 — Create the Render service
|
| 36 |
-
|
| 37 |
-
1. Sign up at **render.com** with your GitHub account.
|
| 38 |
-
2. **New + → Web Service →** pick your repo (authorize Render if asked).
|
| 39 |
-
3. Render detects the `Dockerfile`. Set:
|
| 40 |
-
- **Name:** `pawtrace` (this becomes `pawtrace.onrender.com`)
|
| 41 |
-
- **Branch:** `main`
|
| 42 |
-
- **Instance type:** **Free**
|
| 43 |
-
4. **Environment variables** (Advanced → Add):
|
| 44 |
-
- `JWT_SECRET` → click **Generate** (or paste a long random string)
|
| 45 |
-
- `EMBEDDER` → `mock`
|
| 46 |
-
- `BREED_CLASSIFIER` → `mock`
|
| 47 |
-
*(`DATABASE_URL` and `MEDIA_DIR` are already set inside the Dockerfile.)*
|
| 48 |
-
5. **Create Web Service.** First build takes a few minutes (it builds the React app, then the API).
|
| 49 |
-
|
| 50 |
-
> Shortcut: instead of steps 2–4 you can use **New + → Blueprint**, pick the repo, and Render reads
|
| 51 |
-
> `render.yaml` for you.
|
| 52 |
-
|
| 53 |
-
## Step 3 — Try it
|
| 54 |
-
|
| 55 |
-
Open your `https://pawtrace.onrender.com` URL. You should be able to register, add a dog, upload
|
| 56 |
-
photos, create cases, and click through everything. **Breed estimation works for real even in mock
|
| 57 |
-
mode** — it's the best thing to show off. (Match *scores* are placeholder until Step 5.)
|
| 58 |
-
|
| 59 |
-
Note: the **free** instance sleeps after ~15 min idle, so the first visit after a nap takes ~30s to
|
| 60 |
-
wake. That's normal for free hosting and fine for a demo.
|
| 61 |
-
|
| 62 |
-
## Step 4 — Link it from your personal site
|
| 63 |
-
|
| 64 |
-
Just a link:
|
| 65 |
-
|
| 66 |
-
```html
|
| 67 |
-
<a href="https://pawtrace.onrender.com">Try PawTrace</a>
|
| 68 |
-
```
|
| 69 |
-
|
| 70 |
-
Optional nicer URL: in Render → your service → **Settings → Custom Domains**, add e.g.
|
| 71 |
-
`pawtrace.yourdomain.com`. Render shows you a **CNAME** to create in your DigitalSpace domain/DNS
|
| 72 |
-
settings. Once it propagates, that address serves your app over HTTPS (Render issues the certificate).
|
| 73 |
-
|
| 74 |
-
---
|
| 75 |
-
|
| 76 |
-
## Step 5 — Turn on real matching (when you're ready)
|
| 77 |
-
|
| 78 |
-
Real matching loads a PyTorch model (~1–2 GB RAM), so it needs a paid instance.
|
| 79 |
-
|
| 80 |
-
1. In the root **`Dockerfile`**, uncomment the `REAL MATCHING` block (the two `pip install` lines for
|
| 81 |
-
`torch` / `transformers`), commit, and push.
|
| 82 |
-
2. In Render → **Environment**, change:
|
| 83 |
-
- `EMBEDDER` → `hf`
|
| 84 |
-
- `BREED_CLASSIFIER` → `hf`
|
| 85 |
-
- add `HF_HOME` → `/app/data/hf-cache` (so the model download is cached on the disk)
|
| 86 |
-
3. In Render → **Settings**, bump the **instance type** to one with **≥ 1 GB RAM**.
|
| 87 |
-
4. **Add persistence** so uploads/DB survive redeploys: uncomment the `disk:` block in `render.yaml`
|
| 88 |
-
(or in the dashboard add a **Disk** mounted at **`/app/data`**, ~1 GB). *Do this when you go paid —
|
| 89 |
-
free instances can't have a disk.*
|
| 90 |
-
5. Redeploy. The first request downloads the ~43 MB model once (cached afterward).
|
| 91 |
-
|
| 92 |
-
## Demo data (so the site isn't empty)
|
| 93 |
-
|
| 94 |
-
`render.yaml` sets **`SEED_DEMO=true`**, so on startup an empty database is auto-filled with a small
|
| 95 |
-
Houston-area set: **4 registered dogs (2 reported lost) + 5 found dogs**, a couple of which match a
|
| 96 |
-
lost dog. On the free tier the DB resets on each cold start, so this repopulates every time.
|
| 97 |
-
|
| 98 |
-
Demo logins (all password **`password123`**):
|
| 99 |
-
- **admin@example.com** — the admin dashboard (all dogs/cases/owners)
|
| 100 |
-
- **owner0@example.com** … **owner3@example.com** — owner views; owner0 ("Rex") and owner1 ("Bella")
|
| 101 |
-
have a lost case with a candidate match already waiting.
|
| 102 |
-
|
| 103 |
-
When you move to a **real** deployment with actual users, set `SEED_DEMO=false` (or remove it) so the
|
| 104 |
-
demo dogs don't get added.
|
| 105 |
-
|
| 106 |
-
---
|
| 107 |
-
|
| 108 |
-
## Troubleshooting
|
| 109 |
-
- **Build fails on `npm run build`** → run `npm run build` in `frontend/` locally to see the real
|
| 110 |
-
error (usually a TypeScript issue).
|
| 111 |
-
- **App loads but API calls fail** → check the service **Logs** in Render; confirm `JWT_SECRET` is set.
|
| 112 |
-
- **Everything resets after a deploy** → expected without a Disk (Step 5.4). Add one on a paid plan.
|
| 113 |
-
- **Repo too big to push** → a dataset folder got added; `git rm -r --cached <folder>` and re-commit.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dockerfile
CHANGED
|
@@ -1,9 +1,8 @@
|
|
| 1 |
-
# PawTrace — single-service image
|
| 2 |
-
#
|
| 3 |
-
# the PyTorch re-ID
|
| 4 |
-
#
|
| 5 |
-
#
|
| 6 |
-
# EMBEDDER/BREED_CLASSIFIER=mock + SEED_DEMO=true.
|
| 7 |
|
| 8 |
# ---- Stage 1: build the React frontend -> /web/dist ----
|
| 9 |
FROM node:20-slim AS frontend
|
|
@@ -31,13 +30,17 @@ RUN pip install torch torchvision --index-url https://download.pytorch.org/whl/c
|
|
| 31 |
# Pre-download the model into the image (into HF_HOME) so the first live match doesn't stall on a
|
| 32 |
# runtime download. This is a big, stable layer — kept cached across code changes below.
|
| 33 |
ENV HF_HOME=/app/hf_cache
|
| 34 |
-
RUN python -c "from transformers import AutoImageProcessor, AutoModelForImageClassification as M; \
|
| 35 |
-
k='jhoppanne/Dogs-Breed-Image-Classification-V1'; AutoImageProcessor.from_pretrained(k);
|
|
|
|
| 36 |
# ------------------------------------------------------------------------------------------------
|
| 37 |
|
| 38 |
# Backend source (includes backend/demo_data/ — the shipped snapshot).
|
| 39 |
COPY backend/ ./
|
| 40 |
|
|
|
|
|
|
|
|
|
|
| 41 |
# Geo centroid CSV lives at the repo root; copy it in and point the app at it.
|
| 42 |
COPY data/zip_centroids.csv /app/geo/zip_centroids.csv
|
| 43 |
ENV ZIP_CENTROID_FILE=/app/geo/zip_centroids.csv
|
|
@@ -45,24 +48,27 @@ ENV ZIP_CENTROID_FILE=/app/geo/zip_centroids.csv
|
|
| 45 |
# Built frontend from stage 1 (the API serves this at "/").
|
| 46 |
COPY --from=frontend /web/dist ./frontend_dist
|
| 47 |
|
| 48 |
-
# Bake the
|
| 49 |
-
# the source copy.
|
| 50 |
-
#
|
| 51 |
-
# persistence across restarts — see render.yaml.)
|
| 52 |
RUN mkdir -p /app/data \
|
| 53 |
&& cp /app/demo_data/app.db /app/data/app.db \
|
| 54 |
&& cp -r /app/demo_data/media /app/data/media \
|
| 55 |
&& rm -rf /app/demo_data
|
| 56 |
ENV DATABASE_URL=sqlite:////app/data/app.db \
|
| 57 |
MEDIA_DIR=/app/data/media \
|
| 58 |
-
EMBEDDER=
|
|
|
|
|
|
|
| 59 |
BREED_CLASSIFIER=hf \
|
|
|
|
|
|
|
| 60 |
HF_HUB_OFFLINE=1 \
|
| 61 |
TRANSFORMERS_OFFLINE=1
|
| 62 |
# HF_HUB_OFFLINE/TRANSFORMERS_OFFLINE: use the model baked into HF_HOME above; never call
|
| 63 |
# huggingface.co at runtime (faster cold start, no external dependency during a demo).
|
| 64 |
|
| 65 |
-
#
|
| 66 |
-
#
|
| 67 |
-
|
| 68 |
-
CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-
|
|
|
|
| 1 |
+
# PawTrace — single-service image for the READ-ONLY demo (Hugging Face Spaces / any Docker host).
|
| 2 |
+
# Builds the React frontend, then runs the FastAPI API which also serves that frontend (one origin,
|
| 3 |
+
# no CORS). Bundles the fine-tuned PyTorch re-ID model (best.pt) + the HF breed classifier + the
|
| 4 |
+
# 1,000-dog demo database, and runs with DEMO_MODE=true so every write is blocked server-side.
|
| 5 |
+
# Needs ~1 GB RAM with both models loaded — fine on a Spaces CPU-basic (16 GB) box.
|
|
|
|
| 6 |
|
| 7 |
# ---- Stage 1: build the React frontend -> /web/dist ----
|
| 8 |
FROM node:20-slim AS frontend
|
|
|
|
| 30 |
# Pre-download the model into the image (into HF_HOME) so the first live match doesn't stall on a
|
| 31 |
# runtime download. This is a big, stable layer — kept cached across code changes below.
|
| 32 |
ENV HF_HOME=/app/hf_cache
|
| 33 |
+
RUN python -c "from transformers import AutoImageProcessor, AutoModel, AutoModelForImageClassification as M; \
|
| 34 |
+
k='jhoppanne/Dogs-Breed-Image-Classification-V1'; AutoImageProcessor.from_pretrained(k); \
|
| 35 |
+
M.from_pretrained(k); AutoModel.from_pretrained(k)"
|
| 36 |
# ------------------------------------------------------------------------------------------------
|
| 37 |
|
| 38 |
# Backend source (includes backend/demo_data/ — the shipped snapshot).
|
| 39 |
COPY backend/ ./
|
| 40 |
|
| 41 |
+
# Fine-tuned re-ID model weights (git-LFS in the Space repo) -> loaded when EMBEDDER=reid.
|
| 42 |
+
COPY best.pt /app/best.pt
|
| 43 |
+
|
| 44 |
# Geo centroid CSV lives at the repo root; copy it in and point the app at it.
|
| 45 |
COPY data/zip_centroids.csv /app/geo/zip_centroids.csv
|
| 46 |
ENV ZIP_CENTROID_FILE=/app/geo/zip_centroids.csv
|
|
|
|
| 48 |
# Built frontend from stage 1 (the API serves this at "/").
|
| 49 |
COPY --from=frontend /web/dist ./frontend_dist
|
| 50 |
|
| 51 |
+
# Bake the 1,000-dog demo snapshot (SQLite DB + processed photos) into the image's data dir, then
|
| 52 |
+
# drop the source copy. DEMO_MODE blocks all writes, so the DB never changes; a redeploy just
|
| 53 |
+
# reloads this same read-only snapshot.
|
|
|
|
| 54 |
RUN mkdir -p /app/data \
|
| 55 |
&& cp /app/demo_data/app.db /app/data/app.db \
|
| 56 |
&& cp -r /app/demo_data/media /app/data/media \
|
| 57 |
&& rm -rf /app/demo_data
|
| 58 |
ENV DATABASE_URL=sqlite:////app/data/app.db \
|
| 59 |
MEDIA_DIR=/app/data/media \
|
| 60 |
+
EMBEDDER=reid \
|
| 61 |
+
REID_MODEL_PATH=/app/best.pt \
|
| 62 |
+
REID_MODEL_VERSION=v4 \
|
| 63 |
BREED_CLASSIFIER=hf \
|
| 64 |
+
BREED_TOP_K=10 \
|
| 65 |
+
DEMO_MODE=true \
|
| 66 |
HF_HUB_OFFLINE=1 \
|
| 67 |
TRANSFORMERS_OFFLINE=1
|
| 68 |
# HF_HUB_OFFLINE/TRANSFORMERS_OFFLINE: use the model baked into HF_HOME above; never call
|
| 69 |
# huggingface.co at runtime (faster cold start, no external dependency during a demo).
|
| 70 |
|
| 71 |
+
# HF Spaces routes to the port declared as `app_port` in README.md (7860). Bind there; ${PORT} keeps
|
| 72 |
+
# it portable to hosts that inject a port (Render, etc.).
|
| 73 |
+
EXPOSE 7860
|
| 74 |
+
CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-7860}"]
|
README.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# PawTrace — Lost-Dog Reunification Platform
|
| 2 |
|
| 3 |
A responsive web app that helps reunite lost dogs with their owners by matching photos with
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: PawTrace
|
| 3 |
+
emoji: 🐕
|
| 4 |
+
colorFrom: yellow
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
pinned: false
|
| 9 |
+
short_description: Find a lost dog by its image — an AI dog re-identification demo.
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
# PawTrace — Lost-Dog Reunification Platform
|
| 13 |
|
| 14 |
A responsive web app that helps reunite lost dogs with their owners by matching photos with
|
backend/app/api/geo.py
CHANGED
|
@@ -1,7 +1,13 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
-
from fastapi import APIRouter
|
|
|
|
|
|
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
from .shelters import nearby_shelters
|
| 6 |
|
| 7 |
router = APIRouter(tags=["geo"])
|
|
@@ -15,3 +21,13 @@ def get_nearby_shelters(zip: str | None = None) -> dict:
|
|
| 15 |
@router.get("/healthz")
|
| 16 |
def healthz() -> dict:
|
| 17 |
return {"status": "ok"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
+
from fastapi import APIRouter, Depends
|
| 4 |
+
from sqlalchemy import func, select
|
| 5 |
+
from sqlalchemy.orm import Session
|
| 6 |
|
| 7 |
+
from ..config import settings
|
| 8 |
+
from ..db import get_db
|
| 9 |
+
from ..models import UnknownDog
|
| 10 |
+
from ..models.base import UnknownDogStatus
|
| 11 |
from .shelters import nearby_shelters
|
| 12 |
|
| 13 |
router = APIRouter(tags=["geo"])
|
|
|
|
| 21 |
@router.get("/healthz")
|
| 22 |
def healthz() -> dict:
|
| 23 |
return {"status": "ok"}
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@router.get("/config")
|
| 27 |
+
def public_config(db: Session = Depends(get_db)) -> dict:
|
| 28 |
+
"""Public runtime config the frontend reads on load: which UI to render, and how many
|
| 29 |
+
found/unclaimed dogs a photo search is compared against (the searchable haystack size)."""
|
| 30 |
+
haystack_size = db.execute(
|
| 31 |
+
select(func.count(UnknownDog.id)).where(UnknownDog.status == UnknownDogStatus.pending)
|
| 32 |
+
).scalar()
|
| 33 |
+
return {"demo_mode": settings.demo_mode, "haystack_size": haystack_size}
|
backend/app/config.py
CHANGED
|
@@ -25,17 +25,32 @@ class Settings(BaseSettings):
|
|
| 25 |
media_dir: str = "./data/media"
|
| 26 |
storage_backend: str = "local" # local | s3
|
| 27 |
|
| 28 |
-
# Embedder / ML
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
# HF re-ID embedder: uses the penultimate (pre-classifier) pooled features as the vector.
|
| 32 |
embedder_hf_model: str = "jhoppanne/Dogs-Breed-Image-Classification-V1"
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
# Populate a small demo dataset on startup when the DB is empty (SEED_DEMO=1). For free/mock
|
| 36 |
# deploys so the app isn't blank; safe to leave on (only ever seeds an empty database).
|
| 37 |
seed_demo: bool = False
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
# Breed classifier — cheap estimated-breed candidate gate (spec §9.3, extends metadata gate).
|
| 40 |
# Predicts breed *labels only*; never used for similarity. mock = deterministic, no downloads.
|
| 41 |
breed_classifier: str = "mock" # mock | hf
|
|
|
|
| 25 |
media_dir: str = "./data/media"
|
| 26 |
storage_backend: str = "local" # local | s3
|
| 27 |
|
| 28 |
+
# Embedder / ML. THE SINGLE SWITCH between one-model and two-model modes:
|
| 29 |
+
# EMBEDDER=hf -> SINGLE model: the breed model runs ONCE per image and yields BOTH the
|
| 30 |
+
# matching vector (penultimate features) and the top-K breed labels.
|
| 31 |
+
# EMBEDDER=reid -> TWO models: the fine-tuned re-ID model (reid_model_path) produces the
|
| 32 |
+
# matching vector, and the separate breed classifier produces the top-K
|
| 33 |
+
# breeds. Better matching, at the cost of a second forward pass + model.
|
| 34 |
+
# (Routing is automatic: with EMBEDDER=reid the embedder != breed model, so images.py takes its
|
| 35 |
+
# existing two-model path. Set BREED_CLASSIFIER=hf in reid mode to keep real breed labels.)
|
| 36 |
+
embedder: str = "mock" # mock | hf | reid
|
| 37 |
# HF re-ID embedder: uses the penultimate (pre-classifier) pooled features as the vector.
|
| 38 |
embedder_hf_model: str = "jhoppanne/Dogs-Breed-Image-Classification-V1"
|
| 39 |
+
# Fine-tuned re-ID checkpoint (EMBEDDER=reid): a train_reid.py best.pt loaded into the breed
|
| 40 |
+
# backbone. reid_model_version tags stored vectors so matching only compares same-model vectors
|
| 41 |
+
# (switching modes needs a re-embed — the tag keeps old/new vectors from being mixed).
|
| 42 |
+
reid_model_path: str = "./best.pt"
|
| 43 |
+
reid_model_version: str = "v4"
|
| 44 |
|
| 45 |
# Populate a small demo dataset on startup when the DB is empty (SEED_DEMO=1). For free/mock
|
| 46 |
# deploys so the app isn't blank; safe to leave on (only ever seeds an empty database).
|
| 47 |
seed_demo: bool = False
|
| 48 |
|
| 49 |
+
# Public read-only showcase (DEMO_MODE=1): the frontend renders the 3-page demo shell, and the
|
| 50 |
+
# backend HARD-BLOCKS every write — only the two transient, no-persist photo-search endpoints are
|
| 51 |
+
# allowed. Nothing can modify the database (not the demo UI, a direct API call, or curl).
|
| 52 |
+
demo_mode: bool = False
|
| 53 |
+
|
| 54 |
# Breed classifier — cheap estimated-breed candidate gate (spec §9.3, extends metadata gate).
|
| 55 |
# Predicts breed *labels only*; never used for similarity. mock = deterministic, no downloads.
|
| 56 |
breed_classifier: str = "mock" # mock | hf
|
backend/app/main.py
CHANGED
|
@@ -74,6 +74,26 @@ async def _validation_exc_handler(_: Request, exc: RequestValidationError) -> JS
|
|
| 74 |
)
|
| 75 |
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
# ---- Routers ----
|
| 78 |
app.include_router(auth.router)
|
| 79 |
app.include_router(dogs.router)
|
|
|
|
| 74 |
)
|
| 75 |
|
| 76 |
|
| 77 |
+
# ---- Demo mode: hard, server-side read-only guard ----
|
| 78 |
+
# When DEMO_MODE=1 the app is a public showcase. The ONLY writes allowed are the two transient
|
| 79 |
+
# photo-search endpoints (which persist nothing). Every other mutating request is refused here — so
|
| 80 |
+
# the database can never be modified from the demo UI, a direct API call, or curl. Safety is enforced
|
| 81 |
+
# on the server, not by hiding buttons in the frontend.
|
| 82 |
+
if settings.demo_mode:
|
| 83 |
+
_DEMO_WRITE_ALLOWLIST = {"/search/by-photo", "/search/breed"}
|
| 84 |
+
_MUTATING_METHODS = {"POST", "PUT", "PATCH", "DELETE"}
|
| 85 |
+
|
| 86 |
+
@app.middleware("http")
|
| 87 |
+
async def _demo_readonly_guard(request: Request, call_next):
|
| 88 |
+
if request.method in _MUTATING_METHODS and request.url.path not in _DEMO_WRITE_ALLOWLIST:
|
| 89 |
+
return JSONResponse(
|
| 90 |
+
status_code=403,
|
| 91 |
+
content={"error": {"code": 403,
|
| 92 |
+
"message": "This is a read-only demo — changes are disabled."}},
|
| 93 |
+
)
|
| 94 |
+
return await call_next(request)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
# ---- Routers ----
|
| 98 |
app.include_router(auth.router)
|
| 99 |
app.include_router(dogs.router)
|
backend/app/ml/breed.py
CHANGED
|
@@ -7,7 +7,7 @@ similarity; that matcher is selected separately.
|
|
| 7 |
|
| 8 |
Mock-first: the default ``MockBreedClassifier`` is deterministic and weight-free, so tests and the
|
| 9 |
seed demo run offline. The real ``HFBreedClassifier`` is opt-in via config and lazily imports
|
| 10 |
-
torch/transformers, exactly like ``
|
| 11 |
"""
|
| 12 |
from __future__ import annotations
|
| 13 |
|
|
|
|
| 7 |
|
| 8 |
Mock-first: the default ``MockBreedClassifier`` is deterministic and weight-free, so tests and the
|
| 9 |
seed demo run offline. The real ``HFBreedClassifier`` is opt-in via config and lazily imports
|
| 10 |
+
torch/transformers, exactly like ``HFEmbedder``.
|
| 11 |
"""
|
| 12 |
from __future__ import annotations
|
| 13 |
|
backend/app/ml/detector.py
DELETED
|
@@ -1,55 +0,0 @@
|
|
| 1 |
-
"""Optional dog-detector crop (spec §8 step 3, milestone 8).
|
| 2 |
-
|
| 3 |
-
Disabled by default (DETECTOR_ENABLED=false) and never used on the mock path. When enabled with
|
| 4 |
-
torch/torchvision installed, crops the most confident dog box (with padding) before embedding.
|
| 5 |
-
"""
|
| 6 |
-
from __future__ import annotations
|
| 7 |
-
|
| 8 |
-
from PIL import Image
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
class DogDetector: # pragma: no cover - only used when DETECTOR_ENABLED + torch installed
|
| 12 |
-
# COCO class id 18 == "dog".
|
| 13 |
-
DOG_CLASS_ID = 18
|
| 14 |
-
|
| 15 |
-
def __init__(self, score_threshold: float = 0.5, padding: float = 0.1):
|
| 16 |
-
import torchvision
|
| 17 |
-
from torchvision.models.detection import (
|
| 18 |
-
FasterRCNN_ResNet50_FPN_Weights as W,
|
| 19 |
-
)
|
| 20 |
-
|
| 21 |
-
self.score_threshold = score_threshold
|
| 22 |
-
self.padding = padding
|
| 23 |
-
weights = W.DEFAULT
|
| 24 |
-
self._model = torchvision.models.detection.fasterrcnn_resnet50_fpn(
|
| 25 |
-
weights=weights
|
| 26 |
-
).eval()
|
| 27 |
-
self._preprocess = weights.transforms()
|
| 28 |
-
|
| 29 |
-
def detect_box(self, img: Image.Image) -> tuple[int, int, int, int] | None:
|
| 30 |
-
import torch
|
| 31 |
-
|
| 32 |
-
with torch.no_grad():
|
| 33 |
-
tensor = self._preprocess(img)
|
| 34 |
-
pred = self._model([tensor])[0]
|
| 35 |
-
best = None
|
| 36 |
-
best_score = self.score_threshold
|
| 37 |
-
for box, label, score in zip(pred["boxes"], pred["labels"], pred["scores"]):
|
| 38 |
-
if int(label) == self.DOG_CLASS_ID and float(score) >= best_score:
|
| 39 |
-
best_score = float(score)
|
| 40 |
-
best = box.tolist()
|
| 41 |
-
if best is None:
|
| 42 |
-
return None
|
| 43 |
-
x1, y1, x2, y2 = best
|
| 44 |
-
w, h = img.size
|
| 45 |
-
px, py = (x2 - x1) * self.padding, (y2 - y1) * self.padding
|
| 46 |
-
return (
|
| 47 |
-
max(0, int(x1 - px)),
|
| 48 |
-
max(0, int(y1 - py)),
|
| 49 |
-
min(w, int(x2 + px)),
|
| 50 |
-
min(h, int(y2 + py)),
|
| 51 |
-
)
|
| 52 |
-
|
| 53 |
-
def crop(self, img: Image.Image) -> Image.Image:
|
| 54 |
-
box = self.detect_box(img)
|
| 55 |
-
return img.crop(box) if box else img
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/app/ml/embedder.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""Embedder interface + Mock (default) and
|
| 2 |
|
| 3 |
The Embedder is one of the four swap points. The matcher only depends on this Protocol, so a
|
| 4 |
better model can be dropped in without schema or API changes (A6).
|
|
@@ -70,48 +70,6 @@ class MockEmbedder:
|
|
| 70 |
return [self._vector_for(p) for p in image_paths]
|
| 71 |
|
| 72 |
|
| 73 |
-
class CNNEmbedder: # pragma: no cover - exercised only when torch is installed (milestone 8)
|
| 74 |
-
"""Pretrained CNN backbone, classifier head removed, global-pooled + L2-normalized (spec §9.1).
|
| 75 |
-
|
| 76 |
-
Lazily imports torch/torchvision so the mock path never requires them. Optional dog-crop
|
| 77 |
-
preprocess is gated behind DETECTOR_ENABLED (added in milestone 8).
|
| 78 |
-
"""
|
| 79 |
-
|
| 80 |
-
def __init__(self, model_name: str | None = None):
|
| 81 |
-
import torch # noqa: F401
|
| 82 |
-
import torchvision
|
| 83 |
-
from torchvision.models import ResNet50_Weights
|
| 84 |
-
|
| 85 |
-
self.name = model_name or settings.embedder_model
|
| 86 |
-
self.version = "imagenet-v1"
|
| 87 |
-
weights = ResNet50_Weights.DEFAULT
|
| 88 |
-
backbone = torchvision.models.resnet50(weights=weights)
|
| 89 |
-
import torch.nn as nn
|
| 90 |
-
|
| 91 |
-
self._model = nn.Sequential(*list(backbone.children())[:-1]).eval()
|
| 92 |
-
self._preprocess = weights.transforms()
|
| 93 |
-
self.dim = 2048
|
| 94 |
-
self._detector = None
|
| 95 |
-
if settings.detector_enabled:
|
| 96 |
-
from .detector import DogDetector
|
| 97 |
-
|
| 98 |
-
self._detector = DogDetector()
|
| 99 |
-
|
| 100 |
-
def embed(self, image_paths: list[str]) -> list[np.ndarray]:
|
| 101 |
-
import torch
|
| 102 |
-
|
| 103 |
-
out: list[np.ndarray] = []
|
| 104 |
-
with torch.no_grad():
|
| 105 |
-
for p in image_paths:
|
| 106 |
-
img = Image.open(p).convert("RGB")
|
| 107 |
-
if self._detector is not None:
|
| 108 |
-
img = self._detector.crop(img)
|
| 109 |
-
tensor = self._preprocess(img).unsqueeze(0)
|
| 110 |
-
feat = self._model(tensor).flatten().cpu().numpy()
|
| 111 |
-
out.append(_l2_normalize(feat))
|
| 112 |
-
return out
|
| 113 |
-
|
| 114 |
-
|
| 115 |
class HFEmbedder: # pragma: no cover - exercised only when transformers is installed
|
| 116 |
"""HuggingFace image model used as a re-ID embedder via its penultimate pooled features.
|
| 117 |
|
|
@@ -119,7 +77,7 @@ class HFEmbedder: # pragma: no cover - exercised only when transformers is inst
|
|
| 119 |
individual-dog re-identification (per the project owner). We run the image through the model,
|
| 120 |
take the last hidden state, pool it (global average for CNN feature maps, mean-over-tokens for
|
| 121 |
transformer sequences), and L2-normalize. Breed *labels* are NOT used here — that is the
|
| 122 |
-
separate BreedClassifier swap point. Lazily imports torch/transformers
|
| 123 |
"""
|
| 124 |
|
| 125 |
def __init__(self, model_id: str | None = None):
|
|
@@ -195,16 +153,64 @@ class HFEmbedder: # pragma: no cover - exercised only when transformers is inst
|
|
| 195 |
return results
|
| 196 |
|
| 197 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
_embedder: Embedder | None = None
|
| 199 |
|
| 200 |
|
| 201 |
def get_embedder() -> Embedder:
|
| 202 |
global _embedder
|
| 203 |
if _embedder is None:
|
| 204 |
-
if settings.embedder == "
|
| 205 |
-
_embedder = CNNEmbedder()
|
| 206 |
-
elif settings.embedder == "hf":
|
| 207 |
_embedder = HFEmbedder()
|
|
|
|
|
|
|
| 208 |
else:
|
| 209 |
_embedder = MockEmbedder()
|
| 210 |
return _embedder
|
|
|
|
| 1 |
+
"""Embedder interface + Mock (default), HF, and fine-tuned re-ID implementations (spec §9.1).
|
| 2 |
|
| 3 |
The Embedder is one of the four swap points. The matcher only depends on this Protocol, so a
|
| 4 |
better model can be dropped in without schema or API changes (A6).
|
|
|
|
| 70 |
return [self._vector_for(p) for p in image_paths]
|
| 71 |
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
class HFEmbedder: # pragma: no cover - exercised only when transformers is installed
|
| 74 |
"""HuggingFace image model used as a re-ID embedder via its penultimate pooled features.
|
| 75 |
|
|
|
|
| 77 |
individual-dog re-identification (per the project owner). We run the image through the model,
|
| 78 |
take the last hidden state, pool it (global average for CNN feature maps, mean-over-tokens for
|
| 79 |
transformer sequences), and L2-normalize. Breed *labels* are NOT used here — that is the
|
| 80 |
+
separate BreedClassifier swap point. Lazily imports torch/transformers only when selected.
|
| 81 |
"""
|
| 82 |
|
| 83 |
def __init__(self, model_id: str | None = None):
|
|
|
|
| 153 |
return results
|
| 154 |
|
| 155 |
|
| 156 |
+
_MEAN, _STD = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225] # ImageNet stats (re-ID preprocessing)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
class ReIDEmbedder: # pragma: no cover - exercised only when torch is installed
|
| 160 |
+
"""Fine-tuned re-ID embedder (EMBEDDER=reid): the breed backbone further trained with triplet
|
| 161 |
+
loss for individual-dog re-identification.
|
| 162 |
+
|
| 163 |
+
Loads a local checkpoint (a state_dict saved by scripts/train_reid.py's ``ReIDModel`` — keys are
|
| 164 |
+
prefixed ``backbone.``) into the same ResNet base as the breed model, and emits the L2-normalized
|
| 165 |
+
penultimate pooled features. Preprocessing matches TRAINING (Resize 224 + ImageNet norm), NOT the
|
| 166 |
+
HF image processor. This model produces NO breed labels — when it is the active embedder, breed
|
| 167 |
+
labels come from the separate breed classifier via images.py's two-model path.
|
| 168 |
+
"""
|
| 169 |
+
|
| 170 |
+
def __init__(self, ckpt_path: str | None = None, base_model: str | None = None):
|
| 171 |
+
import torch # noqa: F401
|
| 172 |
+
import torchvision.transforms as T
|
| 173 |
+
from transformers import AutoModel
|
| 174 |
+
|
| 175 |
+
self.name = "reid"
|
| 176 |
+
self.version = settings.reid_model_version
|
| 177 |
+
base = base_model or settings.embedder_hf_model
|
| 178 |
+
self.model = AutoModel.from_pretrained(base)
|
| 179 |
+
raw = torch.load(ckpt_path or settings.reid_model_path, map_location="cpu")
|
| 180 |
+
# accept the ReIDModel wrapper's 'backbone.'-prefixed keys OR a bare backbone state_dict.
|
| 181 |
+
state = {k.removeprefix("backbone."): v for k, v in raw.items()}
|
| 182 |
+
self.model.load_state_dict(state)
|
| 183 |
+
self.model.eval()
|
| 184 |
+
self._device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 185 |
+
self.model.to(self._device)
|
| 186 |
+
self._prep = T.Compose(
|
| 187 |
+
[T.Resize((224, 224)), T.ToTensor(), T.Normalize(_MEAN, _STD)]
|
| 188 |
+
)
|
| 189 |
+
self.dim = 2048
|
| 190 |
+
|
| 191 |
+
def embed(self, image_paths: list[str]) -> list[np.ndarray]:
|
| 192 |
+
import torch
|
| 193 |
+
|
| 194 |
+
out: list[np.ndarray] = []
|
| 195 |
+
with torch.no_grad():
|
| 196 |
+
for p in image_paths:
|
| 197 |
+
with Image.open(p) as img:
|
| 198 |
+
x = self._prep(img.convert("RGB")).unsqueeze(0).to(self._device)
|
| 199 |
+
feat = self.model(x).pooler_output.flatten(1)[0]
|
| 200 |
+
out.append(_l2_normalize(feat.cpu().numpy()))
|
| 201 |
+
return out
|
| 202 |
+
|
| 203 |
+
|
| 204 |
_embedder: Embedder | None = None
|
| 205 |
|
| 206 |
|
| 207 |
def get_embedder() -> Embedder:
|
| 208 |
global _embedder
|
| 209 |
if _embedder is None:
|
| 210 |
+
if settings.embedder == "hf":
|
|
|
|
|
|
|
| 211 |
_embedder = HFEmbedder()
|
| 212 |
+
elif settings.embedder == "reid":
|
| 213 |
+
_embedder = ReIDEmbedder()
|
| 214 |
else:
|
| 215 |
_embedder = MockEmbedder()
|
| 216 |
return _embedder
|
backend/demo_data/app.db
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3708ba8aa5761b5f6afe855f2c29b55b959eaf987d32018fad4dcb4a41ad4e5c
|
| 3 |
+
size 39477248
|
backend/demo_data/media/known/1/1e6abffb49aa43f1822fb9ed29356967.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/1/41bc76f5b36340b8b59e95f25a406113.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/1/ca4f3fabe3034731bfb7e47e98244bc4.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/11/4b97be269fac4e63953eba8b0e41c43e.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/12/7f83295941134d89b033d8fec8dffd2f.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/12/a64dc67d5085476f881f971e73f52b2e.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/12/f93f2f92e16e43a79c0e1497ded8410a.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/13/450ea8a48cad492a9c49a11393eb8495.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/13/b1d596d553fe4e3cae4c67fe46b379c3.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/13/ee2e8e1152d740e6abfb0af8ef3c3f19.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/14/42294b0c875541fa878d6ab8225fb4de.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/14/c6f1598561454e65a522127bd8f01c6f.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/14/d86a962b033f466295244ea36f1789a9.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/15/101251303df44d1bac54e58b7b5e10d0.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/15/3e1114b46fa64b98bb50d1f14bc38bda.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/15/596faa34b27d411c9d7928bf41606f7b.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/16/16023f39dfdf40ff8b10f46f9718ab2b.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/16/2f7b765e96bf4a9b834574b55403c196.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/16/955a2d571eed41909ecf81bbbf34fd13.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/18/630f9c5a28604f14a5c50d2b1fca5996.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/18/a3694f54285e4aedb13b1502c5fd41b7.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/18/fef796259d264e43a3efa784d59b789f.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/19/2668ad9747c64280b49442b408892a00.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/19/3a8fcb16eea8449191a72b1e1f66b418.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/19/e3e4286484ff4295a3c2151d25ff8c1d.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/2/229eef530f064b99b72a4cf1a04f3eab.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/2/97bfa63313074cd888736d131f82bc6c.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/2/d745cafed42f43478a4f37fec4981352.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/2/dd63a482808b4ba1884d42c7e99b116c.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/21/0620a5b6c9794547925baeb807b1ecef.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/21/5c0acc4e4c964f6d813093cf62d22ded.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/21/6d9c1ee992d44a26a6350f6eeb1b3de7.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/22/68e56438501c460abc3aaf05e91b8346.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/22/a1a82403e1d0442e96ac9be580db1f35.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/22/d7101488750c4478802eb72c0d6fa8ce.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/23/091b968a546e47278d5d22054e73ab33.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/23/954a1c7a56ea46e5ae34b8b1fa2b5d24.jpg
DELETED
Git LFS Details
|
backend/demo_data/media/known/23/a42d8b7dec254893a8b4fbaf8ef34979.jpg
DELETED
Git LFS Details
|