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
Files changed (2) hide show
  1. README.md +49 -13
  2. app.py +123 -29
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
- - bee / drone / queen / 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
43
 
44
- | Layer | Model | Job |
45
- |---|---|---|
46
- | Specialist | [Custom YOLOv8s](https://huggingface.co/maryammeda/apiarist-honey-bee-detector) (22 MB) | Detects + counts bees, drones, queens, varroa mites |
47
- | Generalist | [Qwen2.5-VL-3B-Instruct](https://huggingface.co/Qwen/Qwen2.5-VL-3B-Instruct) | Writes the narrative inspection report |
48
- | Persistence | SQLite | Hive registry + inspection history |
49
- | Reports | ReportLab | Weekly PDF generation |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 the YOLO (1× T4, 60 epochs)
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
- ## Badges chased
 
 
 
 
 
 
60
 
61
- Off the Grid · Well-Tuned · Off-Brand · Sharing is Caring · Field Notes
 
 
 
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: linear-gradient(180deg, #1a1410 0%, #2a1f15 100%) !important;
 
 
466
  color: #f4e4bc !important;
467
- font-family: 'JetBrains Mono', 'Courier New', monospace !important;
468
- max-width: 1400px !important;
469
  margin: 0 auto !important;
470
  }
471
- h1 { color: #f4a300 !important; letter-spacing: 0.05em; }
472
- h2, h3 { color: #f4a300 !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
  h4 { color: #ffd066 !important; }
474
- button.primary,
475
- button[variant="primary"] {
476
- background: linear-gradient(180deg, #ffb733 0%, #f4a300 100%) !important;
477
  color: #1a1410 !important;
478
- font-weight: bold !important;
479
  border: none !important;
480
- box-shadow: 0 2px 8px rgba(244, 163, 0, 0.25) !important;
 
481
  transition: transform 80ms ease, box-shadow 200ms ease !important;
482
  }
483
  button.primary:hover {
484
  transform: translateY(-1px);
485
- box-shadow: 0 4px 14px rgba(244, 163, 0, 0.40) !important;
486
  }
 
487
  .gr-box, .block, .gradio-container .block {
488
- border-color: rgba(244, 163, 0, 0.35) !important;
489
- background: rgba(42, 31, 21, 0.4) !important;
 
490
  }
491
  .tabs > .tab-nav > button.selected {
492
- color: #f4a300 !important;
493
  border-bottom: 2px solid #f4a300 !important;
494
  }
495
- .gradio-container a {
496
- color: #ffd066 !important;
 
 
 
 
 
 
 
497
  }
498
- .gradio-container a:hover {
499
- color: #ffe599 !important;
500
- text-decoration: underline;
 
501
  }
502
- table {
503
- border-color: rgba(244, 163, 0, 0.25) !important;
 
 
 
 
 
 
 
 
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.Markdown("# APIARIST")
534
- gr.Markdown(
535
- "*Offline AI inspector for honeybee hive frames. "
536
- "Built for the Build Small Hackathon.*"
 
 
 
 
 
 
 
 
 
 
 
 
537
  )
538
 
539
  with gr.Tabs():
540
  # ------- INSPECT TAB -------
541
- with gr.Tab(" Inspect"):
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(" Hives") as hives_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(" Compare"):
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
- Off the Grid · Well-Tuned · Off-Brand · Sharing is Caring · Field Notes
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>&#128029; APIARIST</h1>
597
+ <p class="tagline">Offline AI hive-frame inspector for backyard
598
+ beekeepers &mdash; finds the queen, counts bees &amp; 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(