← PortfolioThe Build

Floradex

Point your phone at a plant; get a live ID and a Pokédex-style card.

A curiosity about the living world, made tactile — point your phone at a plant and watch a box find it, name it, and remember it. Underneath the toy is a clean engineering idea: real-time detection and accurate identification are two different problems, and the app is fast and accurate precisely because it refuses to conflate them.

SwiftSwiftUIVisionPl@ntNetClaude

In Motion

The app running on a phone — the full capture flow end to end, then a closer look at the live detection loop.

The whole loop end to end — a live box finds the plant, a tap names the species, and Claude writes the card it's filed under.
Flora collected so far!

What it is

Floradex is a native iOS (Swift/SwiftUI) computer-vision app. Aim it at a plant and a bounding box tracks it live at ~30 fps; tap the shutter and it identifies the species; save it, and Claude writes a small structured card — family, Latin name, relatives, a fact or two. Every catch lands in a Pokédex-style collection you can browse later. Native was a deliberate choice: real-time camera CV is dramatically better with Vision, AVFoundation, and Core ML than anything cross-platform.

Architecture — a three-stage pipeline

The pipeline deliberately splits “real-time” from “accurate.” The latency-critical loop stays on the device; the accuracy-critical work lives in the cloud, off the hot path.

01on-device

Detect

On-device · ~30 fps

Apple Vision + AVFoundation

A live bounding box tracks the plant right on the viewfinder. Latency-critical — no network round-trip survives a 30 fps loop, so it must run locally.

02cloud

Classify

Cloud · on capture

Pl@ntNet API

A still frame goes to a fine-grained classifier for global species ID + confidence. Off the real-time path, so ~1–2 s of latency is fine.

03cloud

Enrich

Cloud · on save

Claude Haiku 4.5

The species name becomes a tight, schema-valid info card — family, Latin name, notable relatives, fun facts — via structured outputs.

The key insight

“Identify a plant with a YOLO-style box” secretly bundles two different CV problems: object detection (real-time, few classes, on-device) and fine-grained classification (thousands of species, needs a large model or an API). Separating them per stage is the whole reason the app can be both fast and accurate.

What it took

Debug the invisible with an on-screen HUD

Vision returns normalized, bottom-left-origin rects; SwiftUI is top-left; the preview crops aspect-fill. The box was mis-scaled, offset, and its axes transposed. Rather than guess, I put the raw numbers on screen and plotted them against the object's true position across five field screenshots — which revealed a clean X↔Y transpose, then hand-wrote the scale-to-cover transform against all five data points before shipping.

A phone screenshot of the app: a green bounding box snapped tightly around a yellow CAUTION wet-floor sign, with the debug HUD overlaid at the top showing the raw normalized rect, the converted screen-pixel rect, and the screen size.
The HUD after the fix — raw normalized rect, the converted screen-pixel rect, and screen size, printed live over a snapped-on box. A high-contrast sign stood in for a plant while I nailed the coordinate transform.

One source of truth for orientation

The bounding box double-rotated because orientation was applied twice — once on the output connection and again as a Vision hint. Two places defining the same fact fought each other. Fixed by making the Vision hint the single source of truth.

A failure mode that reshaped the roadmap

Field-testing showed Apple's saliency only isolates high-contrast subjects — it detects contrast, not “plant-ness,” and structurally struggles with the core case: a green plant against green foliage. That empirically-discovered limit is exactly why Phase 2 is a domain-specific trained detector.

Where it's going

Done

Phase 1 — the vertical slice. Real-time on-device detection → capture → global species ID (Pl@ntNet) → AI enrichment (Claude) → a durable local collection (SwiftData). A complete path, working end to end on real plants.

Next

Phase 2 — the ML deep-dive. Train a custom plant detector (fine-tune YOLO on a plant dataset, evaluate, export to Core ML) and swap it in for Apple's saliency — directly fixing the low-contrast-foliage failure found in the field.