Apiarist Dev commited on
Commit ·
34483f7
1
Parent(s): 0fecff4
polish: hero header + detection legend + footer (Off-Brand); document Modal training pipeline + published artifacts
Browse files
README.md
CHANGED
|
@@ -32,33 +32,69 @@ in 10 days for the [Build Small Hackathon](https://huggingface.co/build-small-ha
|
|
| 32 |
|
| 33 |
Point a phone at any honeycomb frame, get back:
|
| 34 |
|
| 35 |
-
-
|
| 36 |
- a narrative inspection report
|
| 37 |
- auto-saved to a per-hive registry
|
| 38 |
- weekly PDF report on demand
|
| 39 |
|
| 40 |
All local. No cloud APIs at inference.
|
| 41 |
|
| 42 |
-
## Architecture
|
| 43 |
|
| 44 |
-
| Layer | Model | Job |
|
| 45 |
-
|---|---|---|
|
| 46 |
-
|
|
| 47 |
-
|
|
| 48 |
-
|
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
## Stack
|
| 52 |
|
| 53 |
-
- [Hugging Face Spaces](https://huggingface.co/spaces) on ZeroGPU
|
| 54 |
- [Gradio](https://gradio.app) with a custom field-tool theme
|
| 55 |
-
- [Modal](https://modal.com) for fine-tuning
|
| 56 |
-
- Training data: [Hendricks Ricky bee-project](https://universe.roboflow.com/hendricks_ricky-hotmail-de/bee-project) (3,308 imgs, 892 queens)
|
| 57 |
- Context imagery: [Apiarist iNaturalist bees dataset](https://huggingface.co/datasets/maryammeda/apiarist-inaturalist-bees)
|
| 58 |
|
| 59 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
## License
|
| 64 |
|
|
|
|
| 32 |
|
| 33 |
Point a phone at any honeycomb frame, get back:
|
| 34 |
|
| 35 |
+
- queen / worker / drone / varroa-mite detections with bounding boxes
|
| 36 |
- a narrative inspection report
|
| 37 |
- auto-saved to a per-hive registry
|
| 38 |
- weekly PDF report on demand
|
| 39 |
|
| 40 |
All local. No cloud APIs at inference.
|
| 41 |
|
| 42 |
+
## Architecture: two tiny specialists + one small generalist
|
| 43 |
|
| 44 |
+
| Layer | Model | Params | Job |
|
| 45 |
+
|---|---|---|---|
|
| 46 |
+
| Detector | [Custom YOLOv8s](https://huggingface.co/maryammeda/apiarist-honey-bee-detector) | ~11M | Locates + counts bees, drones, queens, varroa mites |
|
| 47 |
+
| Queen verifier | [Custom EfficientNet-B0](https://huggingface.co/maryammeda/apiarist-queen-classifier) | ~5M | Confirms the queen on cropped bees (F1 0.96) |
|
| 48 |
+
| Narrator | [Qwen2.5-VL-3B-Instruct](https://huggingface.co/Qwen/Qwen2.5-VL-3B-Instruct) | 3B | Writes the narrative inspection report |
|
| 49 |
+
| Persistence | SQLite | - | Hive registry + inspection history |
|
| 50 |
+
| Reports | ReportLab | - | Weekly PDF generation |
|
| 51 |
+
|
| 52 |
+
The two custom models do the precise work (localization + queen ID); the
|
| 53 |
+
3B VLM only writes prose grounded in their findings. That's why a tiny
|
| 54 |
+
specialist beats a giant generalist at counting bees.
|
| 55 |
+
|
| 56 |
+
## Built with Modal
|
| 57 |
+
|
| 58 |
+
Both custom models were fine-tuned on [Modal](https://modal.com) using
|
| 59 |
+
the hackathon's free GPU credits. Reproducible scripts live in `scripts/`:
|
| 60 |
+
|
| 61 |
+
| Script | What it does | GPU | Time | Cost |
|
| 62 |
+
|---|---|---|---|---|
|
| 63 |
+
| `train_yolo_on_modal.py` | Fine-tunes YOLOv8s on 3,308 labeled bee images (60 epochs) | 1x T4 | ~50 min | ~$0.40 |
|
| 64 |
+
| `train_queen_classifier.py` | Trains EfficientNet-B0 queen-vs-worker on ~31k bee crops | 1x T4 | ~12 min | ~$0.15 |
|
| 65 |
+
|
| 66 |
+
Each script is a self-contained Modal app: it pulls the dataset from
|
| 67 |
+
Roboflow inside the container, trains, writes weights to a Modal Volume,
|
| 68 |
+
and exits. No local GPU, no notebook babysitting. Run with:
|
| 69 |
+
|
| 70 |
+
```bash
|
| 71 |
+
modal run scripts/train_yolo_on_modal.py
|
| 72 |
+
modal run scripts/train_queen_classifier.py
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
Across the build we ran four training jobs on Modal (dataset iterations
|
| 76 |
+
+ the final two shipped models) for well under $1 of credit.
|
| 77 |
|
| 78 |
## Stack
|
| 79 |
|
| 80 |
+
- [Hugging Face Spaces](https://huggingface.co/spaces) on ZeroGPU for inference
|
| 81 |
- [Gradio](https://gradio.app) with a custom field-tool theme
|
| 82 |
+
- [Modal](https://modal.com) for all model fine-tuning
|
| 83 |
+
- Training data: [Hendricks Ricky bee-project](https://universe.roboflow.com/hendricks_ricky-hotmail-de/bee-project) (3,308 imgs, 892 queens) on Roboflow Universe
|
| 84 |
- Context imagery: [Apiarist iNaturalist bees dataset](https://huggingface.co/datasets/maryammeda/apiarist-inaturalist-bees)
|
| 85 |
|
| 86 |
+
## Published artifacts (all open, CC/Apache licensed)
|
| 87 |
+
|
| 88 |
+
- Model: [apiarist-honey-bee-detector](https://huggingface.co/maryammeda/apiarist-honey-bee-detector)
|
| 89 |
+
- Model: [apiarist-queen-classifier](https://huggingface.co/maryammeda/apiarist-queen-classifier)
|
| 90 |
+
- Dataset: [apiarist-inaturalist-bees](https://huggingface.co/datasets/maryammeda/apiarist-inaturalist-bees)
|
| 91 |
+
|
| 92 |
+
## Operating note
|
| 93 |
|
| 94 |
+
Queen detection is strongest on close-up macro shots of frames (a bee or
|
| 95 |
+
small cluster filling the frame). Very wide shots with hands and
|
| 96 |
+
background are harder. The app shows the specialist's confidence so you
|
| 97 |
+
always know how much to trust a given call.
|
| 98 |
|
| 99 |
## License
|
| 100 |
|
app.py
CHANGED
|
@@ -461,46 +461,106 @@ def view_hive_history(hive_name):
|
|
| 461 |
|
| 462 |
|
| 463 |
custom_css = """
|
|
|
|
|
|
|
| 464 |
.gradio-container {
|
| 465 |
-
background:
|
|
|
|
|
|
|
| 466 |
color: #f4e4bc !important;
|
| 467 |
-
font-family: 'JetBrains Mono', 'Courier New', monospace !important;
|
| 468 |
-
max-width:
|
| 469 |
margin: 0 auto !important;
|
| 470 |
}
|
| 471 |
-
|
| 472 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 473 |
h4 { color: #ffd066 !important; }
|
| 474 |
-
|
| 475 |
-
button[variant="primary"] {
|
| 476 |
-
background: linear-gradient(180deg, #
|
| 477 |
color: #1a1410 !important;
|
| 478 |
-
font-weight:
|
| 479 |
border: none !important;
|
| 480 |
-
|
|
|
|
| 481 |
transition: transform 80ms ease, box-shadow 200ms ease !important;
|
| 482 |
}
|
| 483 |
button.primary:hover {
|
| 484 |
transform: translateY(-1px);
|
| 485 |
-
box-shadow: 0
|
| 486 |
}
|
|
|
|
| 487 |
.gr-box, .block, .gradio-container .block {
|
| 488 |
-
border-color: rgba(244,
|
| 489 |
-
background: rgba(
|
|
|
|
| 490 |
}
|
| 491 |
.tabs > .tab-nav > button.selected {
|
| 492 |
-
color: #
|
| 493 |
border-bottom: 2px solid #f4a300 !important;
|
| 494 |
}
|
| 495 |
-
.gradio-container a {
|
| 496 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 497 |
}
|
| 498 |
-
.
|
| 499 |
-
|
| 500 |
-
|
|
|
|
| 501 |
}
|
| 502 |
-
|
| 503 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
}
|
| 505 |
"""
|
| 506 |
|
|
@@ -530,15 +590,27 @@ def build_ui() -> gr.Blocks:
|
|
| 530 |
_seed_sample_data()
|
| 531 |
|
| 532 |
with gr.Blocks(title="Apiarist - Hive Frame Inspector") as app:
|
| 533 |
-
gr.
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 537 |
)
|
| 538 |
|
| 539 |
with gr.Tabs():
|
| 540 |
# ------- INSPECT TAB -------
|
| 541 |
-
with gr.Tab("
|
| 542 |
with gr.Row():
|
| 543 |
with gr.Column():
|
| 544 |
hive_input = gr.Dropdown(
|
|
@@ -595,12 +667,22 @@ def build_ui() -> gr.Blocks:
|
|
| 595 |
)
|
| 596 |
with gr.Column():
|
| 597 |
annotated_output = gr.Image(label="Annotated Frame")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 598 |
narrative_output = gr.Markdown()
|
| 599 |
with gr.Accordion("Raw JSON", open=False):
|
| 600 |
json_output = gr.Code(language="json")
|
| 601 |
|
| 602 |
# ------- HIVES TAB -------
|
| 603 |
-
with gr.Tab("
|
| 604 |
with gr.Row():
|
| 605 |
with gr.Column(scale=1):
|
| 606 |
gr.Markdown("### Add a Hive")
|
|
@@ -662,7 +744,7 @@ def build_ui() -> gr.Blocks:
|
|
| 662 |
)
|
| 663 |
|
| 664 |
# ------- COMPARE TAB -------
|
| 665 |
-
with gr.Tab("
|
| 666 |
gr.Markdown(
|
| 667 |
"### Apiarist vs raw generalist VLM\n"
|
| 668 |
"Same image, two pipelines. Apiarist combines a "
|
|
@@ -739,10 +821,22 @@ internet required.
|
|
| 739 |
|
| 740 |
### Badges chased
|
| 741 |
|
| 742 |
-
|
| 743 |
"""
|
| 744 |
)
|
| 745 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 746 |
# ---- wiring ----
|
| 747 |
|
| 748 |
analyze_btn.click(
|
|
|
|
| 461 |
|
| 462 |
|
| 463 |
custom_css = """
|
| 464 |
+
@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,800&family=JetBrains+Mono:wght@400;600&display=swap');
|
| 465 |
+
|
| 466 |
.gradio-container {
|
| 467 |
+
background:
|
| 468 |
+
radial-gradient(1200px 500px at 80% -10%, rgba(244,163,0,0.10), transparent 60%),
|
| 469 |
+
linear-gradient(180deg, #17110c 0%, #221913 55%, #2a1f15 100%) !important;
|
| 470 |
color: #f4e4bc !important;
|
| 471 |
+
font-family: 'JetBrains Mono', ui-monospace, 'Courier New', monospace !important;
|
| 472 |
+
max-width: 1320px !important;
|
| 473 |
margin: 0 auto !important;
|
| 474 |
}
|
| 475 |
+
|
| 476 |
+
/* ----- Hero header ----- */
|
| 477 |
+
#apiarist-hero {
|
| 478 |
+
border: 1px solid rgba(244,163,0,0.30);
|
| 479 |
+
border-radius: 18px;
|
| 480 |
+
padding: 22px 26px;
|
| 481 |
+
margin: 6px 0 14px 0;
|
| 482 |
+
background:
|
| 483 |
+
linear-gradient(135deg, rgba(244,163,0,0.14) 0%, rgba(42,31,21,0.20) 55%);
|
| 484 |
+
box-shadow: 0 8px 30px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,214,102,0.15);
|
| 485 |
+
}
|
| 486 |
+
#apiarist-hero h1 {
|
| 487 |
+
font-family: 'Fraunces', Georgia, serif !important;
|
| 488 |
+
font-weight: 800 !important;
|
| 489 |
+
font-size: 2.5rem !important;
|
| 490 |
+
letter-spacing: 0.04em;
|
| 491 |
+
margin: 0 0 4px 0 !important;
|
| 492 |
+
color: #ffc23d !important;
|
| 493 |
+
text-shadow: 0 2px 12px rgba(244,163,0,0.25);
|
| 494 |
+
}
|
| 495 |
+
#apiarist-hero .tagline {
|
| 496 |
+
color: #e8d2a0 !important;
|
| 497 |
+
font-size: 0.98rem;
|
| 498 |
+
margin: 0;
|
| 499 |
+
}
|
| 500 |
+
#apiarist-hero .pills { margin-top: 12px; }
|
| 501 |
+
#apiarist-hero .pill {
|
| 502 |
+
display: inline-block;
|
| 503 |
+
border: 1px solid rgba(244,163,0,0.40);
|
| 504 |
+
border-radius: 999px;
|
| 505 |
+
padding: 3px 11px;
|
| 506 |
+
margin: 3px 6px 0 0;
|
| 507 |
+
font-size: 0.74rem;
|
| 508 |
+
color: #ffd066;
|
| 509 |
+
background: rgba(244,163,0,0.08);
|
| 510 |
+
white-space: nowrap;
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
h1, h2, h3 { color: #f4a300 !important; font-family: 'Fraunces', Georgia, serif !important; }
|
| 514 |
h4 { color: #ffd066 !important; }
|
| 515 |
+
|
| 516 |
+
button.primary, button[variant="primary"] {
|
| 517 |
+
background: linear-gradient(180deg, #ffc23d 0%, #f4a300 100%) !important;
|
| 518 |
color: #1a1410 !important;
|
| 519 |
+
font-weight: 700 !important;
|
| 520 |
border: none !important;
|
| 521 |
+
border-radius: 12px !important;
|
| 522 |
+
box-shadow: 0 3px 12px rgba(244,163,0,0.30) !important;
|
| 523 |
transition: transform 80ms ease, box-shadow 200ms ease !important;
|
| 524 |
}
|
| 525 |
button.primary:hover {
|
| 526 |
transform: translateY(-1px);
|
| 527 |
+
box-shadow: 0 6px 20px rgba(244,163,0,0.45) !important;
|
| 528 |
}
|
| 529 |
+
|
| 530 |
.gr-box, .block, .gradio-container .block {
|
| 531 |
+
border-color: rgba(244,163,0,0.28) !important;
|
| 532 |
+
background: rgba(36,26,18,0.55) !important;
|
| 533 |
+
border-radius: 14px !important;
|
| 534 |
}
|
| 535 |
.tabs > .tab-nav > button.selected {
|
| 536 |
+
color: #ffc23d !important;
|
| 537 |
border-bottom: 2px solid #f4a300 !important;
|
| 538 |
}
|
| 539 |
+
.gradio-container a { color: #ffd066 !important; }
|
| 540 |
+
.gradio-container a:hover { color: #ffe599 !important; text-decoration: underline; }
|
| 541 |
+
table { border-color: rgba(244,163,0,0.25) !important; }
|
| 542 |
+
|
| 543 |
+
/* ----- Detection legend ----- */
|
| 544 |
+
#legend {
|
| 545 |
+
display: flex; flex-wrap: wrap; gap: 14px;
|
| 546 |
+
font-size: 0.82rem; color: #e8d2a0;
|
| 547 |
+
padding: 4px 2px 2px 2px;
|
| 548 |
}
|
| 549 |
+
#legend .chip { display: inline-flex; align-items: center; gap: 6px; }
|
| 550 |
+
#legend .sw {
|
| 551 |
+
width: 13px; height: 13px; border-radius: 3px; display: inline-block;
|
| 552 |
+
border: 1px solid rgba(255,255,255,0.25);
|
| 553 |
}
|
| 554 |
+
.sw-queen { background: #32ff64; }
|
| 555 |
+
.sw-bee { background: #f4a300; }
|
| 556 |
+
.sw-drone { background: #ff5050; }
|
| 557 |
+
.sw-mite { background: #dc32dc; }
|
| 558 |
+
|
| 559 |
+
/* ----- Footer ----- */
|
| 560 |
+
#apiarist-footer {
|
| 561 |
+
margin-top: 22px; padding-top: 14px;
|
| 562 |
+
border-top: 1px solid rgba(244,163,0,0.20);
|
| 563 |
+
color: #b8a079; font-size: 0.8rem; text-align: center;
|
| 564 |
}
|
| 565 |
"""
|
| 566 |
|
|
|
|
| 590 |
_seed_sample_data()
|
| 591 |
|
| 592 |
with gr.Blocks(title="Apiarist - Hive Frame Inspector") as app:
|
| 593 |
+
gr.HTML(
|
| 594 |
+
"""
|
| 595 |
+
<div id="apiarist-hero">
|
| 596 |
+
<h1>🐝 APIARIST</h1>
|
| 597 |
+
<p class="tagline">Offline AI hive-frame inspector for backyard
|
| 598 |
+
beekeepers — finds the queen, counts bees & drones,
|
| 599 |
+
flags varroa mites, all on a laptop with no cloud.</p>
|
| 600 |
+
<div class="pills">
|
| 601 |
+
<span class="pill">Custom YOLOv8s detector</span>
|
| 602 |
+
<span class="pill">EfficientNet-B0 queen classifier</span>
|
| 603 |
+
<span class="pill">Qwen2.5-VL-3B narrator</span>
|
| 604 |
+
<span class="pill">Runs on ZeroGPU</span>
|
| 605 |
+
<span class="pill">No cloud APIs</span>
|
| 606 |
+
</div>
|
| 607 |
+
</div>
|
| 608 |
+
"""
|
| 609 |
)
|
| 610 |
|
| 611 |
with gr.Tabs():
|
| 612 |
# ------- INSPECT TAB -------
|
| 613 |
+
with gr.Tab("Inspect"):
|
| 614 |
with gr.Row():
|
| 615 |
with gr.Column():
|
| 616 |
hive_input = gr.Dropdown(
|
|
|
|
| 667 |
)
|
| 668 |
with gr.Column():
|
| 669 |
annotated_output = gr.Image(label="Annotated Frame")
|
| 670 |
+
gr.HTML(
|
| 671 |
+
"""
|
| 672 |
+
<div id="legend">
|
| 673 |
+
<span class="chip"><span class="sw sw-queen"></span>Queen</span>
|
| 674 |
+
<span class="chip"><span class="sw sw-bee"></span>Worker bee</span>
|
| 675 |
+
<span class="chip"><span class="sw sw-drone"></span>Drone</span>
|
| 676 |
+
<span class="chip"><span class="sw sw-mite"></span>Varroa mite</span>
|
| 677 |
+
</div>
|
| 678 |
+
"""
|
| 679 |
+
)
|
| 680 |
narrative_output = gr.Markdown()
|
| 681 |
with gr.Accordion("Raw JSON", open=False):
|
| 682 |
json_output = gr.Code(language="json")
|
| 683 |
|
| 684 |
# ------- HIVES TAB -------
|
| 685 |
+
with gr.Tab("Hives") as hives_tab:
|
| 686 |
with gr.Row():
|
| 687 |
with gr.Column(scale=1):
|
| 688 |
gr.Markdown("### Add a Hive")
|
|
|
|
| 744 |
)
|
| 745 |
|
| 746 |
# ------- COMPARE TAB -------
|
| 747 |
+
with gr.Tab("Compare"):
|
| 748 |
gr.Markdown(
|
| 749 |
"### Apiarist vs raw generalist VLM\n"
|
| 750 |
"Same image, two pipelines. Apiarist combines a "
|
|
|
|
| 821 |
|
| 822 |
### Badges chased
|
| 823 |
|
| 824 |
+
Off the Grid - Well-Tuned - Off-Brand - Sharing is Caring
|
| 825 |
"""
|
| 826 |
)
|
| 827 |
|
| 828 |
+
gr.HTML(
|
| 829 |
+
"""
|
| 830 |
+
<div id="apiarist-footer">
|
| 831 |
+
Apiarist - built for the Build Small Hackathon -
|
| 832 |
+
custom YOLOv8s + EfficientNet-B0 + Qwen2.5-VL-3B -
|
| 833 |
+
trained on <a href="https://modal.com">Modal</a>,
|
| 834 |
+
served on <a href="https://huggingface.co/docs/hub/spaces-zerogpu">ZeroGPU</a>.
|
| 835 |
+
Fully offline at inference.
|
| 836 |
+
</div>
|
| 837 |
+
"""
|
| 838 |
+
)
|
| 839 |
+
|
| 840 |
# ---- wiring ----
|
| 841 |
|
| 842 |
analyze_btn.click(
|