Biodex
The dex of living things. Photograph whatever you come across, the species gets recognised, the slot fills up. With friends, on your own server.
Website · Download the APK · Host your own server · Français
Biodex is a Pokédex of living things, free and self-hosted. You photograph an animal, a plant or a mushroom; the photo goes to your server, which recognises it among nearly a million species and fills the matching slot in your collection. Your friends do the same, and you compare.
There is no central server: each group hosts its own. No email address, no tracking, no account with anyone. Your photos stay with you.
Two clients, one server:
| Server | Python · FastAPI · SQLite. Recognition (MegaDetector, BioCLIP 2, Pl@ntNet, FungiTastic), accounts, the dex, friends, the map. |
| Web client | Plain HTML/JS, phone-first. Everything the app does. On iPhone, this is the app: add it to the home screen, nothing to install. |
| Android app | Flutter. The camera, and animations a browser cannot do. |
Contents
- Screenshots
- What Biodex does
- How it works
- Host your own server
- The web client
- On iPhone: no app, the website
- The Android app
- Configuration
- Upgrading
- Languages
- Rebuilding the data yourself
- Useful commands
- Privacy and security
- Repository layout
- Contributing
- Special thanks
- Credits and licence
- Résumé en français
Screenshots
The web client on a phone, with demo accounts. The Android app shows the same screens; the iPhone section shows it installed on a home screen.
| The dex | A species | Arranging the dex | The map |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| A place | Friends feed | Leaderboard | A friend's dex |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Profile | Achievements | The 50 ranks | Gallery |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Capture | Settings | In French | Best catches |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
Seventeen themes, thirteen of them with an animated backdrop behind the screens:
| Hacker | Abyss | Sakura | Starry night | Forest |
|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
What Biodex does
Photograph and recognise
- Four kingdoms, each with its own engine: animals, plants, fungi, and an "aquatic" lens for fish.
- Before sending: crop, rotate, adjust the light, cut the animal out with your finger to help the engine. The engine gets the cut-out, the gallery keeps the original.
- A verdict, or several guesses when the engine hesitates: pick one yourself, or search the whole reference catalogue.
- When the engine does not know the species — a cultivar, a breed, a variety — add it to the dex by hand, for everyone. A Wikipedia article can fill it in.
- Fungi flagged as toxic are marked, with the warning that goes with it. No recognition should ever decide whether a mushroom is edible.
- iPhone photos (HEIC) are accepted.
Collect
- 976,000 slots, one per accepted species: subspecies and synonyms land on the same slot. Typing "Bos indicus" leads to the Cow.
- Five rarities — common, basic, uncommon, rare, legendary — worth 5 to 150 points, once per species.
- Fifty ranks, from "Larva" to "Go outside, seriously", each with its illustration.
- 82 achievements, each with its own 3D frog to spin, and a crystal to shatter when earned. The condition of an unearned achievement is hidden; you can reveal it.
- The showcase: your ten finest catches, and the rank they give you.
- Arrange your dex like a desktop: press and hold, move, resize to 2×2, pick the slot shape, hide, place empty slots. The arrangement follows you from one device to another.
- Three ways to read the dex: slots, groups (class → order → family, with progress), recent. Filters and sorts. Slots with the full photo or the cut-out, globally or per slot.
With friends
- Friend requests, activity feed, five-level reactions that give points to the photographer, favourites, captions on photos.
- Tag friends in a photo: it counts in their dex too, without being sent twice.
- Leaderboard of the circle and best-catches ranking. A friend's profile, showcase, achievements, their whole dex.
- The map: photos where they were taken, in bubbles that cluster as you zoom out. Filters by person, new things first — what your friends posted and you have not seen yet — with a strip to jump to them.
- Photos to locate: drop a pin, search an address, in batches.
- In-app notifications, grouped so they never spam: "So-and-so shared 7 finds", one line per friend.
Dress it up
- Seventeen themes. System, light, dark; Hacker with its falling glyphs; Starry night and its shooting stars; Abyss, Neon, Forest, Sakura, Winter, Northern lights, Summer night with fireflies, Savanna at sunset, Watercolour, Lagoon, Nebula; Notebook and Game Boy.
- Backdrops run at 24 frames per second, pause in the background, and can be frozen with one setting.
- A loading animation that is a spinning frog.
Stay in control
- Two dates per photo — taken and published — and the choice of which one to show.
- Batch-delete photos without a species; release a catch (the photo goes, and the species too if it was the only one).
- Password without an email address,
scrypthashing, revocable sessions. - English and French, on the web and in the app, with more languages one JSON file away.
How it works
A photo goes through four stages on the server:
- MegaDetector v5a looks for an animal and crops it. If it only sees a car or a landscape, the photo is rejected right away.
- BioCLIP 2 compares the crop to the catalogue — 411,626 Latin names for animals, separate catalogues for plants and fungi.
- The observation prior corrects the raw result. On its own the model puts the roe deer neck and neck with a Chilean deer seen 404 times in history; we reweight by each species' real frequency in GBIF. On the small calibration set it goes from 3 correct answers out of 10 to 10 out of 10 (
backend/outils/calibre.py); in everyday use, expect roughly 7 photos out of 10 to land on the right species — the rest need a tap on the correct suggestion, or a choice from the list. - Grouping into slots decides. A slot is not a catalogue entry but an accepted species:
Bos taurus,Bos primigenius(the aurochs) andBos indicus(the zebu) are three catalogue entries and a single slot, the Cow. Probabilities are summed per slot before deciding, which removes the hesitation between close relatives.
Plants first go through Pl@ntNet if the player entered their key (free, 500 identifications a day), then BioCLIP. Fungi go through FungiTastic (2,829 species, far more accurate within its scope) with a fallback to BioCLIP outside it.
Domestic animals
Two defects added up, both fixed by outils/domestiques.py. The dog was filed with the wolf: GBIF treats Canis lupus familiaris as a subspecies of Canis lupus, so a labrador photo filled a "Grey wolf" slot rated rare. The dog now has its own slot. Breeds existed nowhere: the classifier is a CLIP, its catalogue does not have to be taxonomy, so 117 breeds are presented to it as plain aliases of their species. Cocker, poodle and cavalier add up on "Dog" instead of splitting.
Silhouettes
Empty slots show the silhouette of their species, from PhyloPic. When the exact species has no drawing, we walk up its lineage: family, order, class. Horse, donkey and zebra each have their own. About a third of the collection is CC BY or CC BY-SA: the Credits screen names every artist and their licence, and it is not decorative.
Once a species is recognised, the animal is cut out (cropped to the MegaDetector box, then background removed) and that cut-out lands in the slot like a sticker.
Rarities and points
| Rarity | Colour | Points | Share of the dex |
|---|---|---|---|
| Common | grey | 5 | 0.1 % |
| Basic | green | 10 | 46.6 % |
| Uncommon | blue | 25 | 32.5 % |
| Rare | purple | 60 | 15.8 % |
| Legendary | gold | 150 | 5.0 % |
Rarity comes from worldwide GBIF observations, corrected for two biases: obscurity is not a feat (a weevil with eight observations is not legendary — a species must be among the best-observed of its own group), and livestock does not get reported (a list of domestic species brings them back to common). Result: pigeon, cow and roe deer common; golden eagle, wolf, lynx, bear rare; tiger legendary.
Two clients, one API
The web client never reads the database: it goes through the API like the app, from the same origin thanks to a small relay (serveur_web.py). Whatever exists in one exists in the other.
Host your own server
Requirements
- A Linux box (Debian, Ubuntu, Arch… anything) with Python 3.12.
- 8 GB of RAM and 20 GB of disk: catalogues and models weigh 2.3 GB, the Python dependencies 2.5 GB, the rest is for photos.
- No GPU needed. An analysis takes about fifteen seconds on an ordinary CPU; half a second on a GPU.
uvto manage the Python environment (python3 -m venvandpipwork too).
1. The code
git clone https://github.com/Raaatus/BioDex.git /opt/biodex
cd /opt/biodex/backend
uv venv .venv
uv pip install -p .venv/bin/python -r requirements-moteur.txt
requirements-moteur.txt pulls torch and the models: that is the heavy part. On a server without a GPU, the CPU wheel is enough and saves 2 GB of CUDA:
uv pip install -p .venv/bin/python torch torchvision --index-url https://download.pytorch.org/whl/cpu
uv pip install -p .venv/bin/python -r requirements-moteur.txt
requirements.txt alone installs the API without the engine: handy for client development, photos then stay "pending".
2. The data
None of this is in the repository, it is too heavy. The data pack is attached to the releases; backend/donnees.manifeste.json lists it with checksums:
| File | Size | Role |
|---|---|---|
biodex-dex.sqlite |
436 MB | the reference: 976,000 slots, 1 million aliases, silhouettes, articles — and no player |
catalogue_v2.pt |
653 MB | animals encoded by BioCLIP 2, plus 117 breeds |
catalogue_plantes.pt |
704 MB | plants |
catalogue_champi.pt |
273 MB | fungi |
catalogue_aquatique.pt |
38 MB | fish, extracted from the animal catalogue |
tete_genres.pt |
2 MB | the fish "genus" head, trained on FishNet |
ft_classes.json, ft_toxiques.json |
— | FungiTastic classes and toxic species |
modeles/md_v5a.0.1.pt |
281 MB | MegaDetector v5a |
silhouettes.tar |
44 MB | PhyloPic silhouettes and rank badges |
sources-dex.tar |
17 MB | names and observations, only useful to rebuild the dex |
One script does it all, from the release address:
.venv/bin/python outils/installe_donnees.py https://github.com/Raaatus/BioDex/releases/download/donnees-v1
It checks every checksum, unpacks the archives, and creates donnees/biodex.sqlite from the dex database only if there is none yet — it never overwrites a database that holds players. BioCLIP 2, FungiTastic and the background-removal model download themselves on first run, from Hugging Face.
3. Run
./run.sh # the API, on all interfaces, port 8077
.venv/bin/python serveur_web.py # the web client, port 8090, relaying /api/ to the API
On first start the server creates donnees/sel_serveur.txt — keep it and back it up with the database: it signs sessions, losing it signs everyone out.
Open http://your-server:8090, create an account, take a photo.
4. As a service
Two systemd units, in /etc/systemd/system/:
# biodex.service
[Unit]
Description=Biodex — API and recognition
After=network.target
[Service]
User=biodex
WorkingDirectory=/opt/biodex/backend
Environment=BIODEX_DONNEES=/opt/biodex/backend/donnees
Environment=BIODEX_JOURS_AVANT_PURGE=0
ExecStart=/opt/biodex/backend/run.sh
Restart=on-failure
[Install]
WantedBy=multi-user.target
# biodex-web.service
[Unit]
Description=Biodex — web client
After=biodex.service
[Service]
User=biodex
WorkingDirectory=/opt/biodex/backend
ExecStart=/opt/biodex/backend/.venv/bin/python serveur_web.py --port 8090
Restart=on-failure
[Install]
WantedBy=multi-user.target
useradd -r -s /usr/sbin/nologin biodex && chown -R biodex: /opt/biodex
systemctl enable --now biodex biodex-web
journalctl -u biodex -f
5. HTTPS
The Android app and the web client need an address reachable from the phone; on the Internet, put a reverse proxy in front. With Caddy, certificates come by themselves:
dex.example.org {
reverse_proxy 127.0.0.1:8090
}
api.example.org {
reverse_proxy 127.0.0.1:8077
}
The web client talks to the API through its own relay: exposing api.example.org is only for the Android app, which gets that address at first sign-in. Photos can weigh 20 MB: make sure the proxy accepts it (Caddy does by default; under nginx, client_max_body_size 32m).
On a local network, without HTTPS, the app accepts an address like http://192.168.x.x:8077 — the "server in the closet" use case.
⚠️ The purge
By default, the server deletes on start-up the photos left without a species for more than BIODEX_JOURS_AVANT_PURGE days (7). To keep them and settle them yourself from "To settle", set the variable to 0, as in the unit above.
The web client
It is served by serveur_web.py and lives in backend/web/: a page, some scripts, no build step. It keeps the session, the language, the theme and the image cache.
The web client offers everything the app does, camera included through the browser's file picker, which offers the camera on phones.
On iPhone: no app, the website
There is no iOS app, and there does not need to be one. The website is built to be put on the home screen:
- Open your server's address in Safari (
https://dex.example.org). - Tap Share, then Add to Home Screen.
- Open Biodex from the icon.
It then runs full screen, without the browser bars, with its own icon and name, and keeps you signed in. The camera, the gallery, the map and the themes all work. The address must be HTTPS for Safari to allow the camera from the home-screen version — see HTTPS above.
| On the home screen | The dex | The map | A species |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Friends feed | Gallery | Starry night theme | Sakura theme |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
The Android app
Install
The APK is attached to every release. Android will ask to allow installs from outside the Play Store. On first launch the app asks for the server address — your API, https://api.example.org or http://192.168.1.20:8077 — then a nickname and a password.
Build it yourself
cd app
./installe_toolchain.sh # Flutter, JDK and Android SDK in app/.toolchain/ (~5.5 GB), nothing system-wide
source env.sh
./build.sh # produces ../apk/biodex.apk
rm -rf app/.toolchain removes everything, caches included. To sign with your own key, add an android/key.properties; without it the APK is signed with the debug key, which is enough to install.
Configuration
Everything is set through environment variables; nothing is hard-coded. The main ones:
| Variable | Default | Role |
|---|---|---|
BIODEX_DONNEES |
backend/donnees |
where database, photos and catalogues live |
BIODEX_BASE |
$DONNEES/biodex.sqlite |
the database |
BIODEX_PHOTOS, BIODEX_VIGNETTES, BIODEX_DECOUPES, BIODEX_AVATARS |
under $DONNEES |
images |
BIODEX_HOTE, BIODEX_PORT |
0.0.0.0, 8077 |
the API listener |
BIODEX_SANS_MOTEUR |
— | 1: the API without recognition |
BIODEX_FILS_TORCH |
8 | CPU threads for inference |
BIODEX_JOURS_AVANT_PURGE |
7 | days before photos without a species are deleted, 0 = never |
BIODEX_DUREE_SESSION_JOURS |
365 | session lifetime |
BIODEX_SEUIL_DETECTION |
0.20 | MegaDetector minimum confidence |
BIODEX_SEUIL_CASE, BIODEX_RATIO_CASE |
0.55, 4.0 | when a slot counts as "sure" |
BIODEX_PRIOR_OBS |
1.0 | weight of the observation prior; lower it to hunt rarities |
BIODEX_CLIP, BIODEX_FUNGI_MODELE |
BioCLIP 2, FungiTastic | the Hugging Face models |
BIODEX_MD_POIDS, BIODEX_CATALOGUE* |
under $DONNEES |
weights and catalogues |
The full list, with the comments explaining each threshold, is in backend/biodex/config.py.
Upgrading
An upgrade touches neither the photos nor the database: migrations only add. The procedure, with the safety copy, the rebuild of the database on the current schema and the rollback if needed, is in backend/MISE_A_JOUR.md (in French).
Languages
Biodex is written in French; every interface text is a key, translated in i18n/<code>.json. The same file serves the web client, the app and the server (for the achievement and rank names it serves). English is complete. Adding a language means copying en.json, translating, declaring the code in two selectors and running python3 i18n/genere.py — all in i18n/README.md.
Species names come from the reference data: the French name when GBIF or Wikipedia gives one, the Latin name otherwise.
Rebuilding the data yourself
The pack is enough to run a server. But everything in it can be rebuilt with the scripts in backend/outils/, for a fresher dex, another language of names, or simply to understand:
| Script | What it does |
|---|---|
construit_dex.py |
reads the GBIF backbone (simple.txt.gz) and writes one slot per accepted species, plus the aliases |
domestiques.py |
takes dog and pig out of wolf and boar, adds 117 breeds |
observations.py |
collects GBIF observation counts, for the prior and the rarities |
qualites.py |
assigns the five rarities |
noms_francais.py, noms_wikidata.py, noms_wikipedia.py |
vernacular names |
phylopic.py, silhouettes.py, silhouettes_flore.py |
silhouettes, by lineage |
catalogue_poissons.py, embeddings_poissons.py, tete_poissons.py |
the aquatic catalogue and its "genus" head |
succes_especes.py |
precomputes which slots satisfy each achievement |
badges_paliers.py, modeles_succes.py, couleurs_succes.py |
rank and achievement illustrations |
toxiques.py |
the list of species flagged as toxic |
base_neuve.py |
rebuilds the database on the current schema, keeping everything |
calibre.py, mesure_analyse.py, essai_moteur.py |
measure the engine on a set of known photos |
Each script says at the top what it expects and what it produces. Encoding the catalogues takes a GPU or a lot of patience: 411,626 names to push through BioCLIP.
Useful commands
Accounts, from the server:
sqlite3 donnees/biodex.sqlite "SELECT id, pseudo, cree_le, vu_le FROM utilisateurs ORDER BY id;"
Count what the purge would delete:
SELECT COUNT(*) FROM captures
WHERE case_id IS NULL AND etat NOT IN ('attente','analyse')
AND cree_le < datetime('now', '-7 days');
Back up: the database, sel_serveur.txt, and the photos/, decoupes/, avatars/ folders. The rest downloads or recomputes.
Photos live in photos/<player id>/<YYYY-MM>/, with the thumbnails and the WebP cut-outs mirrored under vignettes/ and decoupes/: one player's pictures are one folder to copy or remove. An installation older than this layout is reorganised with outils/range_images.py (services stopped, journaled, reversible).
Test:
cd backend && .venv/bin/python -m pytest -q tests # server
for f in backend/web/*.js; do node --check "$f"; done # web client
cd app && source env.sh && flutter analyze && flutter test
Make a code archive for a server that already runs: cd backend && ./paquet.sh --code-seul.
Privacy and security
- No email address. A nickname, a password. In return, a lost password is lost: there is no recovery channel. Hashing is
scryptfrom the standard library. - Sessions are random tokens of which only the fingerprint, salted with
sel_serveur.txt, is stored: the database alone cannot forge a session. - What others see: a friend's photo is visible only if shared, and only to accepted friends. A stranger sees no dex, no photos, no map.
- Nothing leaves the server except what it is asked to fetch: Wikipedia articles (once per species, cached), Nominatim geocoding when searching an address, Pl@ntNet if you gave your key.
- SQLite in WAL mode, a single file to back up.
Repository layout
backend/
biodex/ the server: routes/, moteur.py (recognition), db.py (migrations), schema.sql
web/ the web client: index.html, app.js, style.css, backdrops (fond.js), dictionaries (lang/)
outils/ building and measuring the data
tests/ pytest
run.sh starts the API
serveur_web.py serves the web client and relays /api/
paquet.sh builds a deployment archive
MISE_A_JOUR.md upgrading without losing anything
app/
lib/ the Flutter app: ecrans/, widgets/, themes.dart, i18n.dart
assets/ 3D achievement models, loading animation, dictionaries
build.sh, env.sh, installe_toolchain.sh
i18n/ translations, and the generator
docs/ the website (GitHub Pages) and the screenshots
Code, comments and identifiers are in French. Contributions in English are welcome; see below.
Contributing
Issues and pull requests are welcome; CONTRIBUTING.md says how. Tests run in CI, and a v* tag builds the APK and the server archive as a release.
Special thanks
Biodex is a thin layer on top of work that other people chose to give away. None of it would exist without them — a species sheet is GBIF's taxonomy, Wikidata's names, Wikipedia's article, a PhyloPic artist's silhouette and a model somebody trained and published. Thank you.
The species data
- GBIF — the backbone taxonomy (every slot, every synonym) and the observation records that give the rarities and the prior. gbif.org/occurrence
- Wikidata — the vernacular names in every language. query.wikidata.org
- Wikipedia and Wikimedia Commons — the article and its illustration on each sheet, by their volunteers.
- FishBase — the fish references behind the aquatic catalogue.
The silhouettes
- PhyloPic — every silhouette in the dex is an SVG drawn by one of its contributors and released under CC0, CC BY or CC BY-SA. The Credits screen of the app and the website lists each artist by name, with the licence of each image. phylopic.org/contributors
The models
- MegaDetector — Microsoft AI for Earth and the camera-trap community; finds the animal in the photo.
- BioCLIP 2 — the Imageomics Institute, trained on TreeOfLife-200M; names the species.
- Pl@ntNet — the plants, through their public API. my.plantnet.org
- FungiTastic — the BVRA group, the fungi.
- FishNet — the fish catalogue and its images.
The map
- OpenStreetMap and its contributors — the tiles and, through Nominatim, the names of places.
- OpenTopoMap — the relief layer.
- Esri — the satellite imagery.
- Leaflet — the map itself.
And
- Meshy — the 3D frogs of the achievements.
- Python, FastAPI, SQLite, PyTorch, Flutter, and the people who maintain them.
If you reuse Biodex, keep these credits with it: the licences of PhyloPic, OpenStreetMap and Wikipedia ask for attribution, and the rest deserve it.
Credits and licence
Biodex is released under the MIT licence. The data and models it relies on keep their own licences, listed above in Special thanks.
Résumé en français
Biodex est un Pokédex du vivant, libre et auto-hébergé. On photographie un animal, une plante ou un champignon ; ton propre serveur le reconnaît parmi près d'un million d'espèces et remplit la case correspondante dans ta collection. Tes amis font pareil sur le même serveur, et vous comparez : fil d'activité, réactions, classement, carte des trouvailles avec les nouveautés d'abord, cinquante rangs, 82 succès avec leur grenouille en 3D, une vitrine, dix-sept thèmes à décor animé.
Pas de serveur central, pas de compte chez qui que ce soit : chaque bande héberge le sien. Pas d'adresse mail, pas de pistage. Les photos restent chez toi.
- Serveur : Python 3.12, FastAPI, SQLite. Reconnaissance par MegaDetector, BioCLIP 2, Pl@ntNet et FungiTastic. Tourne sur processeur (une quinzaine de secondes par photo).
- Site web : HTML/JS sans framework, pensé pour le téléphone. Sur iPhone, pas d'application, pas besoin : Safari → Partager → Sur l'écran d'accueil, et le site devient l'application.
- Application Android : Flutter, avec l'appareil photo. APK sur la page des releases.
- Langues : français (source) et anglais, au choix dans les réglages ; une langue de plus, c'est un fichier JSON (voir
i18n/README.md).
Démarrage rapide :
git clone https://github.com/Raaatus/BioDex.git && cd BioDex/backend
uv venv .venv && uv pip install -p .venv/bin/python -r requirements-moteur.txt
.venv/bin/python outils/installe_donnees.py https://github.com/Raaatus/BioDex/releases/download/donnees-v1
./run.sh & # l'API sur :8077
.venv/bin/python serveur_web.py # le site sur :8090
Le pack de données (base des espèces sans aucun joueur, catalogues encodés, poids du détecteur, silhouettes — 2,3 Go) est attaché aux releases ; backend/donnees.manifeste.json le liste avec ses empreintes. Tout ce qu'il contient se reconstruit avec les scripts de backend/outils/.
Mettre BIODEX_JOURS_AVANT_PURGE=0 pour garder les photos sans espèce au lieu de les voir effacées après sept jours. Sauvegarder biodex.sqlite, sel_serveur.txt et les dossiers photos/, decoupes/, avatars/. La mise à jour sans rien perdre est décrite dans backend/MISE_A_JOUR.md.
Licence MIT. Les données et modèles gardent leurs licences — GBIF, PhyloPic (CC0 / CC BY / CC BY-SA, crédités dans l'application), Wikipédia, MegaDetector, BioCLIP 2, Pl@ntNet, FungiTastic.
























