waterleaf / README.md
hkaraoguz's picture
Finalize hackathon submission
deac482 verified
|
Raw
History Blame Contribute Delete
8.9 kB
---
title: Waterleaf
emoji: 🌿
colorFrom: green
colorTo: red
sdk: docker
app_port: 7860
hf_oauth: true
hf_oauth_expiration_minutes: 43200
license: mit
short_description: Plant ID and weather-aware watering calendars
tags:
- track:backyard
- sponsor:modal
- sponsor:openai
- achievement:llama
- achievement:fieldnotes
- achievement:bestdemo
---
# Waterleaf
Waterleaf identifies an outdoor garden plant from one to three photographs,
grounds the result in a plant taxonomy database, builds an editable
weather-aware watering plan, and exports one 30-day calendar for the garden.
Built by Hakan Karaoguz (`hkaraoguz`) for the 2026 Hugging Face Build Small
Hackathon.
## Workflow
1. Upload or capture one to three photographs of one plant.
2. Gemma 4 extracts visible traits and proposes likely names.
3. GBIF resolves those names to valid plant species.
4. Gemma reranks only the valid records.
5. Confirm or replace the species through autocomplete.
6. Preview weather-adjusted dates and edit them.
7. Save plants and export one whole-garden ICS file.
## Architecture
- **UI and web:** Gradio Blocks mounted in FastAPI
- **Authentication:** Hugging Face OAuth
- **Persistence:** SQLite and normalized JPEGs on an attached HF Storage Bucket
- **Vision model:** `ggml-org/gemma-4-26B-A4B-it-GGUF`
- **Runtime:** llama.cpp `server-cuda13-b9445` on a Modal L4
- **Taxonomy:** GBIF Species API
- **Care data:** Small local baseline catalog with manual interval fallback
- **Weather:** Open-Meteo geocoding and 16-day forecast
- **Calendar:** RFC 5545-compatible ICS with stable UIDs, alarms, profile URLs,
and image attachments
See [docs/architecture.md](docs/architecture.md) for the data flow and privacy
boundaries.
## Local Development
Python 3.11-3.13 and `uv` are supported.
```bash
uv sync
uv run uvicorn app:app --host 0.0.0.0 --port 7860
```
Open `http://localhost:7860`. Without `MODAL_ENDPOINT`, Waterleaf uses a
deterministic lavender demo identifier. Local persistence uses the
`local-gardener` identity and `data/` directory.
Run checks:
```bash
uv sync
uv run pytest
uv run ruff check .
```
## Modal Deployment
### 1. Authenticate the Modal CLI
Install the deploy dependency and connect the local CLI to the Modal workspace:
```bash
uv sync --group deploy
uv run --group deploy modal setup
```
### 2. Deploy llama.cpp
Deploy the protected GPU service:
```bash
uv run --group deploy modal deploy modal_app.py
```
The Modal service:
- uses the pinned `ghcr.io/ggml-org/llama.cpp:server-cuda13-b9445` image;
- starts `Gemma 4 26B-A4B Q4_K_M` with automatic multimodal projector download;
- uses an 8K context, full GPU offload, Flash Attention, Q8 KV cache, and one
parallel slot;
- uses a bounded 256-token thinking pass for database-candidate reranking while
keeping initial visual extraction non-thinking and schema-constrained;
- caches Hugging Face artifacts in a Modal Volume;
- requires Modal proxy-auth headers.
The command prints the `modal.run` URL. Save it as `MODAL_ENDPOINT`.
For the live demo and judging window, keep one container warm:
```bash
MODAL_MIN_CONTAINERS=1 uv run --group deploy modal deploy modal_app.py
```
Return to zero warm containers after judging to stop idle GPU spend:
```bash
MODAL_MIN_CONTAINERS=0 uv run --group deploy modal deploy modal_app.py
```
### 3. Create proxy credentials
In Modal Workspace Settings, create a **Web endpoint proxy auth token**. Save
the token ID as `MODAL_KEY` and token secret as `MODAL_SECRET`. These are not
the same credentials used by `modal setup`.
Test the endpoint before configuring the Space:
```bash
MODAL_ENDPOINT=https://...modal.run \
MODAL_KEY=wk-... \
MODAL_SECRET=ws-... \
uv run python scripts/smoke_modal.py assets/sample-lavender.png
```
If a proxy token has not been created yet, a controlled one-time smoke test can
temporarily publish the endpoint:
```bash
MODAL_PROXY_AUTH=0 MODAL_MIN_CONTAINERS=1 \
uv run --group deploy modal deploy modal_app.py
MODAL_ENDPOINT=https://...modal.run \
uv run python scripts/smoke_modal.py assets/sample-lavender.png
MODAL_PROXY_AUTH=1 MODAL_MIN_CONTAINERS=0 \
uv run --group deploy modal deploy modal_app.py
```
The middle deployment is unauthenticated and should exist only for the smoke
test. Always run the final restore command immediately afterward.
## Space Configuration
### 1. Create the Space
Create `build-small-hackathon/waterleaf` in the Hugging Face UI with:
- **SDK:** Docker
- **Visibility:** Public
- **License:** MIT
If the hackathon organization does not allow direct creation, create
`hkaraoguz/waterleaf` first and transfer or duplicate it into the requested
hackathon namespace.
The root README metadata already enables Docker on port `7860` and HF OAuth.
### 2. Upload this repository
Authenticate the Hugging Face CLI and upload the working tree. The CLI sends
binary assets through Xet storage, which a plain Git push does not:
```bash
hf auth login
hf upload build-small-hackathon/waterleaf . . \
--repo-type space \
--exclude '.git/**' \
--exclude '.venv/**' \
--exclude '.pytest_cache/**' \
--exclude '.ruff_cache/**' \
--exclude '**/__pycache__/**' \
--exclude '*.pyc' \
--exclude 'data/**' \
--exclude '.env'
```
Do not commit or upload local environment files or deployment secrets.
### 3. Attach persistent storage
In **Space Settings → Storage Buckets**:
1. Create or select a bucket for Waterleaf.
2. Attach it read-write.
3. Set the mount path to `/data`.
The Docker image already sets `WATERLEAF_DATA_DIR=/data`. Without this mount,
saved gardens and images disappear when the Space restarts.
### 4. Configure secrets and variables
In **Space Settings → Variables and secrets**, add:
| Name | Type | Required | Purpose |
| --- | --- | --- | --- |
| `MODAL_ENDPOINT` | Secret | Production | Protected llama.cpp base URL |
| `MODAL_KEY` | Secret | Production | Modal proxy token ID |
| `MODAL_SECRET` | Secret | Production | Modal proxy token secret |
| `PUBLIC_BASE_URL` | Variable | Optional | Override the derived Space URL |
| `WATERLEAF_DATA_DIR` | Variable | No | Defaults to `/data` in Docker |
Use only the base Modal URL for `MODAL_ENDPOINT`; do not append
`/v1/chat/completions`.
### 5. Rebuild and verify
Trigger **Factory reboot** after attaching storage or changing secrets. Then
verify:
```bash
curl --fail https://build-small-hackathon-waterleaf.hf.space/health
```
Expected response:
```json
{"status":"ok"}
```
Open the Space directly, not only inside the Hub iframe, and check:
1. **Sign in with Hugging Face** completes successfully.
2. A guest can preview one identification.
3. A signed-in user can save a plant and see it after a factory restart.
4. The generated ICS downloads and its public plant/image links open.
Guests may run one temporary identification preview. Login is required to
save, delete, or export plants.
## Evaluation
Populate `evaluation/manifest.csv` with at least 20 consented real-garden
examples and run:
```bash
MODAL_ENDPOINT=... MODAL_KEY=... MODAL_SECRET=... \
uv run python scripts/evaluate.py evaluation/manifest.csv
```
The report includes species top-1/top-3 accuracy, genus top-1 accuracy,
per-case predictions, and latency. A live one-to-three-photo smoke test is
available at `scripts/smoke_modal.py`.
## Privacy and Limitations
- Images are resized, converted to JPEG, and stripped of EXIF.
- Stored coordinates are rounded and never exposed on public plant pages.
- Public pages use opaque slugs but are intentionally public for calendar use.
- Plant identification and watering dates are suggestions, not horticultural
guarantees.
- If forecast retrieval fails after location resolution, Waterleaf still
generates dates from the care baseline and labels them as seasonal estimates.
- Geocoding failure stops schedule preview until the location is corrected.
- Dates after the 16-day forecast are labeled seasonal estimates.
- ICS `ATTACH` support varies by calendar client; every event also includes a
portable public profile link.
- Users must provide a manual interval when the local catalog has no care
baseline for the selected species.
## Submission Materials
- [30-second demo video](https://www.youtube.com/watch?v=4H5vGVFcaO4)
- [X post](https://x.com/hknkrgz/status/2066605985741807972)
- [Field Notes](docs/field-notes.md)
- [Architecture](docs/architecture.md)
Target categories: Backyard AI, Best Demo, Llama Champion, Modal-powered, and
Field Notes. Waterleaf does not claim Off the Grid because inference, taxonomy,
and weather are cloud-hosted.
## Team
- [Hakan Karaoguz (`hkaraoguz`)](https://huggingface.co/hkaraoguz)
## Credits
- [Gemma 4](https://huggingface.co/google/gemma-4-26B-A4B-it)
- [llama.cpp](https://github.com/ggml-org/llama.cpp)
- [GBIF](https://www.gbif.org/developer/species)
- [Open-Meteo](https://open-meteo.com/)