Spaces:
Running
Running
Serve single-photo head with vetted taxonomy corrections
Browse files- Dockerfile +2 -2
- README.md +6 -5
- inference.py +2 -2
- test_release.py +1 -1
Dockerfile
CHANGED
|
@@ -28,9 +28,9 @@ COPY . .
|
|
| 28 |
# Pinned single-photo head, legacy sex detector and checklist. The reviewed Wings v6
|
| 29 |
# detector is a Space asset and must not be overwritten.
|
| 30 |
ARG WEIGHTS_REPO=fr4nzzch/butterfly-id-classifier
|
| 31 |
-
ARG WEIGHTS_REVISION=
|
| 32 |
RUN mkdir -p /app/assets && python -c "import shutil; from huggingface_hub import hf_hub_download; [shutil.copy(hf_hub_download('${WEIGHTS_REPO}', f, revision='${WEIGHTS_REVISION}'), '/app/assets/'+f) for f in ['head_hier.pt','wing_seg.pt','region_checklist.json']]"
|
| 33 |
-
RUN python -c "import hashlib,pathlib; p=pathlib.Path('/app/assets'); expected={'head_hier.pt':'
|
| 34 |
|
| 35 |
# Bake the 4 GB BioCLIP 2.5-H backbone into the in-image HF cache (HF_HOME) at BUILD
|
| 36 |
# time. This is the big cold-start win: a freshly woken container loads the backbone
|
|
|
|
| 28 |
# Pinned single-photo head, legacy sex detector and checklist. The reviewed Wings v6
|
| 29 |
# detector is a Space asset and must not be overwritten.
|
| 30 |
ARG WEIGHTS_REPO=fr4nzzch/butterfly-id-classifier
|
| 31 |
+
ARG WEIGHTS_REVISION=173f2b75ea834274120b9217055f1e763f7945d7
|
| 32 |
RUN mkdir -p /app/assets && python -c "import shutil; from huggingface_hub import hf_hub_download; [shutil.copy(hf_hub_download('${WEIGHTS_REPO}', f, revision='${WEIGHTS_REVISION}'), '/app/assets/'+f) for f in ['head_hier.pt','wing_seg.pt','region_checklist.json']]"
|
| 33 |
+
RUN python -c "import hashlib,pathlib; p=pathlib.Path('/app/assets'); expected={'head_hier.pt':'fe6f00821dc06cace5238d71c2561cc6eae37bcf1616129f65da8ccba5583eea','wing_seg_v6.pt':'38a0dab20ee607e0a43a72488420ed50e329f07c71c95775ef647b19e0f275de','field_wingtip_override_v1.json':'fb65b55b1f4ae26ce22383f2fcf216d41e104ebc47edc4db6aa82094718a6cb1'}; [(hashlib.sha256((p/n).read_bytes()).hexdigest()==sha or (_ for _ in ()).throw(ValueError('asset hash mismatch '+n))) for n,sha in expected.items()]"
|
| 34 |
|
| 35 |
# Bake the 4 GB BioCLIP 2.5-H backbone into the in-image HF cache (HF_HOME) at BUILD
|
| 36 |
# time. This is the big cold-start win: a freshly woken container loads the backbone
|
README.md
CHANGED
|
@@ -11,9 +11,10 @@ license: mit
|
|
| 11 |
|
| 12 |
# Butterfly ID API
|
| 13 |
|
| 14 |
-
This Space serves the published BioCLIP-2.5-H image encoder and the 7,
|
| 15 |
-
single-photo taxonomy head, retrained on 27 September 2026 with
|
| 16 |
-
|
|
|
|
| 17 |
identifiers for the head and Wings v6 detector. Check those identifiers before using a new response contract.
|
| 18 |
|
| 19 |
For taxonomy, the API runs the released Wings v6 detector on raw image pixels,
|
|
@@ -36,7 +37,7 @@ legacy detector.
|
|
| 36 |
- `POST /predict_raw` accepts multipart `images[]` and optional normalized
|
| 37 |
`box` and `yolo` controls. Each real result retains `filename`, top-64
|
| 38 |
`leaves`, `wing_box`, `boxes`, and sex prediction. It also returns
|
| 39 |
-
`full_leaves`: all 7,
|
| 40 |
These are visual probabilities with **no geographic prior**. Mock results
|
| 41 |
set `full_leaves: null` and `leaf_distribution_complete: false`.
|
| 42 |
- `POST /predict` preserves the server-side geographic-prior response.
|
|
@@ -48,4 +49,4 @@ for full-distribution reranking.
|
|
| 48 |
|
| 49 |
The Dockerfile pins model repository and BioCLIP revisions and verifies the
|
| 50 |
head and detector bytes. The prior Space revision
|
| 51 |
-
`
|
|
|
|
| 11 |
|
| 12 |
# Butterfly ID API
|
| 13 |
|
| 14 |
+
This Space serves the published BioCLIP-2.5-H image encoder and the 7,482-leaf
|
| 15 |
+
single-photo taxonomy head, retrained on 27 September 2026 with added GBIF
|
| 16 |
+
photographs (up to 1,000 observations per species) and 757 vetted taxonomy corrections.
|
| 17 |
+
Leaf names follow the corrected taxonomy; the old-to-new map is `taxon_name_map.json` in the model repo. `/metadata` reports the active pipeline revision and SHA-256
|
| 18 |
identifiers for the head and Wings v6 detector. Check those identifiers before using a new response contract.
|
| 19 |
|
| 20 |
For taxonomy, the API runs the released Wings v6 detector on raw image pixels,
|
|
|
|
| 37 |
- `POST /predict_raw` accepts multipart `images[]` and optional normalized
|
| 38 |
`box` and `yolo` controls. Each real result retains `filename`, top-64
|
| 39 |
`leaves`, `wing_box`, `boxes`, and sex prediction. It also returns
|
| 40 |
+
`full_leaves`: all 7,482 unrounded leaf probabilities in head order, `leaf_distribution_complete: true`, and `pipeline_revision`.
|
| 41 |
These are visual probabilities with **no geographic prior**. Mock results
|
| 42 |
set `full_leaves: null` and `leaf_distribution_complete: false`.
|
| 43 |
- `POST /predict` preserves the server-side geographic-prior response.
|
|
|
|
| 49 |
|
| 50 |
The Dockerfile pins model repository and BioCLIP revisions and verifies the
|
| 51 |
head and detector bytes. The prior Space revision
|
| 52 |
+
`fce1a2421dd87a753333c17b749af7749cec196b` is the rollback target.
|
inference.py
CHANGED
|
@@ -26,8 +26,8 @@ from PIL import Image, ImageOps
|
|
| 26 |
TOP_K = 5
|
| 27 |
RAW_TOP_N = 64 # how many leaves /predict_raw returns (client marginalises these)
|
| 28 |
MAX_DETECTIONS = 32
|
| 29 |
-
PIPELINE_REVISION = "wings-v6-gbif1000-
|
| 30 |
-
HEAD_SHA256 = "
|
| 31 |
|
| 32 |
|
| 33 |
def deduplicate_detections(detections, image_size, max_groups=MAX_DETECTIONS):
|
|
|
|
| 26 |
TOP_K = 5
|
| 27 |
RAW_TOP_N = 64 # how many leaves /predict_raw returns (client marginalises these)
|
| 28 |
MAX_DETECTIONS = 32
|
| 29 |
+
PIPELINE_REVISION = "wings-v6-gbif1000-vetted-taxonomy-20260927-v1"
|
| 30 |
+
HEAD_SHA256 = "fe6f00821dc06cace5238d71c2561cc6eae37bcf1616129f65da8ccba5583eea"
|
| 31 |
|
| 32 |
|
| 33 |
def deduplicate_detections(detections, image_size, max_groups=MAX_DETECTIONS):
|
test_release.py
CHANGED
|
@@ -66,7 +66,7 @@ class ReleaseTests(unittest.TestCase):
|
|
| 66 |
self.assertEqual(hashlib.sha256(path.read_bytes()).hexdigest(), HEAD_SHA256)
|
| 67 |
ck = torch.load(path, map_location="cpu", weights_only=False)
|
| 68 |
n = len(ck["leaves"])
|
| 69 |
-
self.
|
| 70 |
self.assertEqual(ck["head_state_dict"]["weight"].shape[0], n)
|
| 71 |
for rank in ("species", "genus"):
|
| 72 |
self.assertEqual(len(ck["leaf2rank"][rank]), n)
|
|
|
|
| 66 |
self.assertEqual(hashlib.sha256(path.read_bytes()).hexdigest(), HEAD_SHA256)
|
| 67 |
ck = torch.load(path, map_location="cpu", weights_only=False)
|
| 68 |
n = len(ck["leaves"])
|
| 69 |
+
self.assertGreater(n, 7000)
|
| 70 |
self.assertEqual(ck["head_state_dict"]["weight"].shape[0], n)
|
| 71 |
for rank in ("species", "genus"):
|
| 72 |
self.assertEqual(len(ck["leaf2rank"][rank]), n)
|