File size: 7,420 Bytes
d16b6da
4b98524
 
d16b6da
4b98524
d16b6da
4b98524
 
 
d16b6da
4b98524
 
d16b6da
 
4b98524
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9d36f13
 
 
 
4b98524
 
 
 
 
 
9d36f13
4b98524
 
 
 
 
 
 
 
9d36f13
4b98524
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
---
title: Animap Inference
emoji: πŸ„
colorFrom: green
colorTo: gray
sdk: gradio
sdk_version: 5.49.1
app_file: space_app.py
python_version: "3.12.12"
pinned: false
license: apache-2.0
short_description: Livestock models that refuse to invent a result.
---

# Animap inference

This runs validated livestock models against farm photographs and returns what
the model actually produced. Its most important property is what it **refuses**:
a capability with no checksummed artefact behind it answers `unavailable`, not a
placeholder and not a plausible-looking number.

Twenty-eight capabilities are registered. **Two run.** `cattle_detection` and
`poultry_count` execute a YOLOX-m ONNX artefact whose sha256 is verified against
its model card at start-up. The other twenty-six say so plainly, which is the
honest answer rather than a gap.

The source is `services/inference` in the Animap repository. `app.py` mounts
`app.main:app` β€” the same FastAPI service the Azure Container App runs β€” under a
Gradio page, so `/health`, `/capabilities` and `/jobs` behave exactly as they do
in production and the page is a demonstration of them.

**It was a Docker Space until 2026-08-24 and is a Gradio one now**, for one
reason: ZeroGPU is Gradio-SDK only. The Docker image ran the identical Azure
bytes, which was the better provenance story, and it could not be given a GPU at
any price β€” which left CountGD, the one capability a GPU actually unblocks,
unmeasurable. See **What this Space is not** for what the change cost.

## What is open and what is not

| Endpoint | Auth | Why |
|---|---|---|
| `GET /animap/health` | none | Carries no farm data, and a platform probe has to reach it |
| `GET /animap/capabilities` | none | The published contract: what may be claimed, and what may not |
| `POST /animap/jobs` | **bearer token** | Runs a model against a farm's photographs |
| `GET /animap/jobs/{id}` | **bearer token** | Returns a farm's result |

`space/publish.py --set-secret` mints `ANIMAP_INFERENCE_TOKEN` and sets it as a
Space secret. **Check it rather than assuming it**: `GET /health` reports
`"authenticated": false` when no token is configured, so a deployment that
reached the internet without one says so to anyone who asks.

    curl -s https://bluman1-animap-inference.hf.space/animap/health

## Running a model

Two public-domain captures are baked in, so a real detection can be obtained
without an Azure account. `space/FIXTURES.md` in the repository lists their ids,
their sources and the human count on record for each.

```bash
curl -s -X POST https://bluman1-animap-inference.hf.space/animap/jobs \
  -H "authorization: Bearer $ANIMAP_INFERENCE_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"capability_key":"cattle_detection",
       "subject_type":"herd",
       "subject_id":"00000000-0000-0000-0000-000000000001",
       "farm_id":"00000000-0000-0000-0000-000000000002",
       "media_ids":["aa5e8481-8be6-509d-b1fa-f1a178c7cda0"],
       "captured_at":"2026-08-22T10:00:00Z"}'
```

A frame that settles at the first grid answers in well under a second. A dense
one runs all three grids and takes a few seconds; there is no queue, because no
capability yet takes tens of seconds.

Read `warnings` before you read the number. A count is of the animals **visible
in one frame** β€” never the herd size, never a flock population, and never a
house reconciliation. When the count keeps rising as the frame is read more
finely, or exceeds twenty, the service publishes `count_withheld` and no number
at all. That refusal is a feature and it is measured: see the `known_limits` and
`validation_notes` on each model card.

## What the SDK change cost, and what it did not

**Lost: the image is no longer byte-identical to Azure's.** A Gradio Space has
no Dockerfile, so the claim *"this Space builds from the same Dockerfile"* is
gone and cannot be got back while ZeroGPU is Gradio-only. What runs is the same
`app/` tree with the same `requirements.txt`, which is close and is not the same
thing, and this file says so rather than letting the old sentence stand.

**Lost: a build-time licence gate.** The Docker build failed if an AGPL runtime
arrived. There is no build to fail now.

**Kept: every gate that actually protects a result.** `app.py` runs
`scripts/install_models.py --check` before it imports the service, so an
artefact that disagrees with its model card stops the Space at start-up rather
than being found by a farm's job. `providers.discover()` still refuses to serve
a capability whose artefact fingerprints as a copyleft runtime, and `/health`
still publishes `artefact_licenses` so a deployment in breach is visible from
outside.

**Gained: the ability to be given a GPU.** Nothing here reaches for CUDA yet β€”
YOLOX-m and DINOv3 are both ONNX on CPU β€” so this buys no speed-up today. It is
the prerequisite for CountGD, which gets MAE 14.84 on broiler houses against the
deployed detector's 156.80 and has never been runnable anywhere in this project.

## What this Space is not

**It is not the production media path, and it must not be read as evidence for
one.** This was true of the Docker Space and the SDK change did nothing to it. Production reads captures out of an Azure Blob container using the
Container App's managed identity β€” no key, no SAS, nothing stored. A Space is
not inside Azure and has no managed identity, so that credential is unavailable
to it. The alternatives a Space *could* use are a storage account key or a SAS
token in a secret, and neither is the production posture: one hands a public
Space full access to every farm's evidence, and the other expires.

So this Space serves `ANIMAP_MEDIA_PROVIDER=local` against the two baked-in
frames. Everything downstream of the pixels β€” the quality gate, the detection
pyramid, the counting guard, the observation vocabulary β€” is the production code
path exactly. Everything upstream of them is not.

**No farm data reaches this Space.** It cannot read `animapmedia`, and the only
captures it holds are two public-domain photographs from Wikimedia Commons.

## Weights

YOLOX-m, Apache-2.0, from the Megvii `0.1.1rc0` release, **vendored into this
repository under Git LFS rather than fetched at build time**. The service
verifies its sha256 against `models/cattle_detection/model_card.json` at
start-up and refuses to load an artefact that does not match. Vendoring is what
lets the build step stay `install_models.py --check` β€” verify, never fetch β€”
which is the posture ADR 0005 asks for and the same command the Azure build
runs.

`models/cattle_identity` is deliberately absent. Its artefact is DINOv3 under a
bespoke Meta licence whose two published texts disagree about an attribution
obligation, and publishing a copy into a public Space is redistribution. That
capability answers `unavailable` here, and correctly.

No AGPL-3.0 software is installed and none may be. `requirements.txt` omits
`ultralytics`, `.dockerignore` excludes `*.pt`, the build fails if one arrives
anyway, and `providers.discover()` refuses to serve a capability whose artefact
fingerprints as a copyleft runtime. `GET /health` publishes
`artefact_licenses`, so a deployment in breach is visible from outside.

## Attribution

Third-party notices travel with the image in `THIRD_PARTY_NOTICES.md`. The two
demonstration captures are CC0 and public domain; their sources are in
`space/FIXTURES.md`.