{"slug": "naturdex-a-pokedex-for-real-nature-that-runs-offline-on-a-recycled-phone", "title": "NaturDex: a Pokédex for real nature that runs offline on a recycled phone", "summary": "A developer built NaturDex, an offline, on-device AI nature identification app that turns a recycled Android phone into a Pokédex-style field device for school trips. The app runs an image classification model locally, adds species to a collectible regional dex, and prioritizes safety warnings for dangerous or protected species, currently limited to Asturias, Spain. Field testing on a mid-range Android phone produced direct identifications, multiple-choice prompts, and honest \"not sure\" results, prompting a camera hint to photograph a single leaf, flower or bark rather than a whole tree.", "body_md": "*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass](https://dev.to/challenges/hacktoberfest-week1-2026-10-05)*\n\nWe live surrounded by plants and animals we never look at. The robin on the fence, the heather on the hillside, the butterfly crossing the path. Kids walk past all of them with their eyes on a screen.\n\nSo I flipped it around and used the screen to send them outside. **NaturDex** is a Pokédex for real nature. You photograph a plant or an animal, and an AI model running **on the phone** tells you what it is. If it's new, it goes into your NaturDex with its number, its photo and the date you found it. The goal is to fill it, and the only way to do that is to go out and look.\n\nThe scenario I designed it for is a **school field trip to the forest**. Schools could hand out **recycled phones** (old phones everyone has in a drawer) with NaturDex installed, and the activity is to fill the NaturDex. That scenario set three hard rules:\n\nAnd, since it's for children outdoors, a fourth one: **safety first.** NaturDex warns before it entertains.\n\nHow a trip works:\n\n**Why only Asturias?** Because it's where I live, and where I could actually use it. The AI model knows living things from all over the world, but the species list and, above all, the safety rules have to be right for the place where the kids are. Which vipers live here, which plants are poisonous, which species are protected by law: I can only vouch for that in a region I know. So I chose to do one region well instead of the whole world badly. The last section explains how other regions could join.\n\nIf you don't live in Asturias, you can still try it: on a computer, the camera button opens a file picker, so you can give it any photo of a common European species, like a robin, a fly agaric or a peacock butterfly.\n\n**Try it:** [virien84.github.io/naturdex](https://virien84.github.io/naturdex/). Open it on a phone, tap *Prepare trip* on wifi, then go outside. You can also install it from the browser menu (*Install app* / *Add to Home screen*).\n\nThe design is a handheld field device: a red shell, a big blue lens that glows while the AI is thinking, a leaf-green LCD screen and chunky buttons that press down.\n\n| Ready to go | A confident answer | Added to the NaturDex | \n|---|---|---|\n\nThat robin is a small bird far away, between branches, and NaturDex is still 100% sure. The shield means it's a protected species: look, but don't disturb it.\n\n**Safety comes first.** Dangerous species get a black-and-yellow hazard warning above everything else. On the right, the model isn't sure which snake it is, but one of the candidates is a snake, and that's enough to warn.\n\n| Deadly mushroom | \"Could it be…?\" | \n|---|---|\n\n**When the model hesitates, the kid decides.** It's a learning moment: compare your photo with the options. And when NaturDex really doesn't know, it says so instead of guessing.\n\n| Which one is it? | Not sure | My NaturDex | \n|---|---|---|\n\nAt night the screen switches to a backlit LCD:\n\n*The photos in these screenshots are openly licensed iNaturalist observations from Spain (CC BY / CC0). Credits are in [screenshots/CREDITS.md](https://github.com/Virien84/naturdex/blob/main/screenshots/CREDITS.md).*\n\nI installed it on my phone, a mid-range Android (vivo V50 Lite), and went out to photograph trees and plants. All three answers showed up: direct identifications, \"Which one is it?\" with the right species among the options, and \"not sure\". It worked with no errors.\n\nThe \"not sure\" one taught me something. I had photographed **a whole tree standing among other trees**. The model looks at a square crop of 224×224 pixels, and in that square there were several crowns and a lot of sky, so it couldn't tell which tree I meant. It was right not to guess. So now the camera says *\"For trees, photograph one leaf, flower or the bark\"*, and the \"not sure\" screen repeats it.\n\nThe second lesson was about patience. After taking a photo, **the app looked frozen**. The message \"Looking closely…\" existed, but the screen never got the chance to show it (more on that below). Kids would tap again, or give up. Now the lens glows and a spinner turns from the very first instant.\n\nA nature \"Pokémon Snap\" for school field trips: photograph plants and animals and fill your NaturDex, **fully offline** on recycled phones.\n\n**Try it:** [https://virien84.github.io/naturdex/](https://virien84.github.io/naturdex/) (open it on a phone, tap *Prepare trip* on wifi, then go outside).\n\n`app/` — the PWA (React + Vite + vite-plugin-pwa + onnxruntime-web).`embeddings/` — Python scripts (uv) that prepare the model and the species list on a laptop.`SAFETY.md`\n| What | Where | \n|---|---|\n| The app | GitHub Pages, rebuilt on every push to `main` by[`.github/workflows/deploy.yml`](https://github.com/Virien84/naturdex/.github/workflows/deploy.yml) | \n| The BioCLIP model (87 MB, too big for GitHub) | Hugging Face: [Virien/naturdex-bioclip-onnx](https://huggingface.co/Virien/naturdex-bioclip-onnx) | \n| Species list + embeddings (~5 MB) | In this repo, `app/public/species/` | \n\nEverything runs on the phone: photos and locations never leave it.\n\nMIT licensed. The model is on Hugging Face: [Virien/naturdex-bioclip-onnx](https://huggingface.co/Virien/naturdex-bioclip-onnx).\n\nThe trick that makes it fit on a phone is splitting the AI work in two: the heavy, one-off part runs **once, on a laptop**, and the phone only does what it has to do for each photo.\n\n```\nLAPTOP, once                                 PHONE, every photo (offline)\n\nGBIF ─► 1,944 species of Asturias            Photo ─► BioCLIP image encoder\n          │                                          (ONNX int8, 87 MB, WASM)\n          ▼                                              │\nBioCLIP text encoder ─► 1 \"fingerprint\"                 ▼\nper species (512 numbers)  ──────────────────►  compare with the 1,944\n                                                fingerprints ─► top 5 + %\n```\n\n**1. The model: BioCLIP.** [BioCLIP](https://huggingface.co/imageomics/bioclip) is an open CLIP model (MIT) trained by the Imageomics Institute on the Tree of Life: millions of images of organisms with their full taxonomy. Like any CLIP model, it puts images and texts in the same space, so it can do **zero-shot** classification: no training on my side, just compare the photo with a text description of each species.\n\n**2. The species list, on the laptop.** A script downloads from [GBIF](https://www.gbif.org/) every species with at least 20 human observations in Asturias since 2000 (1,933), and a safety script adds 11 dangerous species that are too rare to make the cut, because the app can only warn about species it knows. Then BioCLIP's text encoder turns each species into a \"fingerprint\": its full taxonomy and common name, averaged over 15 photo prompts (\"a photo of a…\", \"a blurry photo of a…\"). That's a 4 MB file.\n\n**3. The model, on the phone.** I exported only BioCLIP's **image** encoder to ONNX and quantized its weights to int8: from 345 MB to **87 MB**. It's too big for GitHub Pages, so it lives on Hugging Face, which serves it with CORS. The phone runs it with `onnxruntime-web` (WebAssembly), compares the photo's embedding with the 1,944 fingerprints, and picks a verdict:\n\n``` js\nconst top = candidates[0].probability\nconst verdict = top >= CONFIDENT ? 'confident' : top >= UNSURE ? 'maybe' : 'unsure'\nreturn { candidates, verdict, warnings: precautionWarnings(candidates) }\n```\n\nThose thresholds (70% and 40%) aren't guesses. I evaluated the int8 model, running exactly as the phone does, on **244 research-grade iNaturalist photos from Spain** (82 species, including every dangerous one):\n\n| top-1 | top-3 | top-5 | \n|---|---|---|\n| 66% | 81% | 86% | \n\nIn the evaluation (an earlier run, with 232 of those photos), above 70% the top answer was right 84% of the time, so NaturDex says \"It's a…!\". Between 40% and 70% it was right only ~45%, but the right species was usually in the top 3, so NaturDex shows three and lets the kid choose. Below 40% it was right 7% of the time, so it says \"not sure\".\n\n**4. Safety rules ([SAFETY.md](https://github.com/Virien84/naturdex/blob/main/SAFETY.md)).** This is the part that matters most, because a wrong answer here isn't a bug, it's a child touching something.\n\n**5. Offline for real.** It's a PWA: a service worker caches the app shell, the fonts and onnxruntime's WebAssembly; the model and species list go to Cache Storage when you tap *Prepare trip*; the album lives in IndexedDB. Once prepared, it works with no connection: I tested it by preparing the trip, cutting the network, reloading the page and identifying a butterfly. If you're building something similar, [this post on offline PWA pitfalls](https://dev.to/ohugonnot/offline-pwa-the-pitfalls-of-an-app-that-never-asks-for-the-network-fc7) by ohugonnot covers the Vite PWA and Workbox traps well.\n\n**The app looked frozen, because the browser never got to paint.** Decoding a full-size phone photo, checking whether it's blurry and running the model all happen on the page's single thread. React had already switched to the \"thinking\" screen, but the browser had no free moment to draw it. The fix is to wait for a paint before the heavy work:\n\n```\n// Waits until the browser has drawn the screen. Decoding the photo and running\n// the model block the page, so without this the \"thinking\" screen never shows.\nfunction nextPaint() {\n  return new Promise((resolve) => requestAnimationFrame(() => setTimeout(resolve, 0)))\n}\n```\n\nThe spinner and the glowing lens are CSS `transform`/` opacity` animations, which the GPU keeps running even while the model has the page busy. Measured inside the page with the CPU throttled 4×: the spinner is painted 0.05 s after the photo arrives, and the result comes ~13 s later.\n\n**Firefox got stuck at 99%.** After downloading, the app asks the browser not to delete its data (`navigator.storage.persist()`). Chrome, Brave and Safari answer at once. Firefox **asks the user first**, and the app waited for that answer. If the kid didn't notice the prompt, the download looked stuck forever. Now it waits at most 3 seconds and moves on. I found it by running the full flow (download + identify) in Chromium, Brave, Firefox and WebKit with Playwright, and all four pass now.\n\n**\"It's an European Robin!\"** In English it's *a* European. Small, but the app talks to kids.\n\nI built it with Claude Code as my coding agent: it did the heavy lifting on the ONNX export, the evaluation script, the PWA plumbing and the design, while I made the decisions and tested it outside.\n\nFor this project, open isn't a nice-to-have. A closed API would have broken every one of the rules:\n\nThe data is open too: species from GBIF, test photos from iNaturalist, protected species from public Spanish catalogues. Open models plus open data made a tool that a school can own.\n\nAsturias is a starting point, not the limit. The final idea, and the reason it had to be open source, is that **anyone could add their own region**:\n\n`safety.py` is built around Spanish and Asturian catalogues. The plan is to turn it into one folder per region (`dangerous.csv`, `sensitive.csv`, the local protected-species list), so That's the kind of collaboration a closed app can't have: the AI is open, the data is open, and the knowledge that keeps kids safe comes from the community itself.\n\nThis is the session from the last day: testing it outside on my phone, fixing the \"frozen\" screen, the Pokédex-style redesign, the install icons, the cross-browser test that caught the Firefox bug, and preparing this post (including swapping out a photo of a run-over fox for a living one). It's in Spanish, my native language.\n\nNone. NaturDex doesn't use any partner technology, so I'm entering only the overall prize.", "url": "https://wpnews.pro/news/naturdex-a-pokedex-for-real-nature-that-runs-offline-on-a-recycled-phone", "canonical_source": "https://dev.to/virien84/naturdex-a-pokedex-for-real-nature-that-runs-offline-on-a-recycled-phone-2amo", "published_at": "2026-10-11 01:05:05+00:00", "updated_at": "2026-10-11 01:19:46.817603+00:00", "lang": "en", "topics": ["artificial-intelligence", "computer-vision", "ai-products", "ai-tools", "machine-learning"], "entities": ["NaturDex", "Asturias", "iNaturalist", "Hacktoberfest", "vivo V50 Lite", "Virien84"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/naturdex-a-pokedex-for-real-nature-that-runs-offline-on-a-recycled-phone", "markdown": "https://wpnews.pro/news/naturdex-a-pokedex-for-real-nature-that-runs-offline-on-a-recycled-phone.md", "text": "https://wpnews.pro/news/naturdex-a-pokedex-for-real-nature-that-runs-offline-on-a-recycled-phone.txt", "jsonld": "https://wpnews.pro/news/naturdex-a-pokedex-for-real-nature-that-runs-offline-on-a-recycled-phone.jsonld"}}