workbench / plant /app.py
GitHub Actions
Initial ZeroGPU deployment with spaces shim
7f9dfed
Raw
History Blame Contribute Delete
3.78 kB
from __future__ import annotations
import argparse
from pathlib import Path
from typing import Any, cast
import gradio as gr
import yaml
from core.deployment import current_policy, ensure_demo_mode_allowed
from datasets.field_notes import FieldNoteStore
from plant.plant_loader import SpeciesIndexBuilder
from plant.plant_service import DemoPlantVisionService, PlantVisionService
from plant.plant_tab import build_plant_tab
from plant.plant_tools import set_services
ROOT = Path(__file__).parent
DEFAULT_CONFIG = ROOT / "models.yaml"
APP_CSS = """
.plant-shell {
max-width: 1120px !important;
}
.plant-title {
margin-bottom: 0.35rem;
}
footer {
display: none !important;
}
"""
def load_config(path: str | Path = DEFAULT_CONFIG) -> dict[str, Any]:
config_path = Path(path)
data = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
if not isinstance(data, dict):
raise ValueError(f"Plant config must be a mapping: {config_path}")
return data
def build_app(
config_path: str | Path = DEFAULT_CONFIG,
no_model: bool = False,
data_dir: str | Path = "data",
model_mode: str = "openbmb",
) -> gr.Blocks:
policy = current_policy()
cfg = load_config(config_path)
root = Path(config_path).parent
species_index = SpeciesIndexBuilder(root=root).build(cfg)
note_store = FieldNoteStore(Path(data_dir) / "plant_field_notes.csv")
plant_service: Any
if no_model or model_mode == "demo":
ensure_demo_mode_allowed(policy)
plant_service = DemoPlantVisionService()
elif model_mode == "finetuned":
plant_service = PlantVisionService.from_config(config_path, "plant_vlm_finetuned")
else:
plant_service = PlantVisionService.from_config(config_path, "plant_vlm")
set_services(plant_service, note_store, species_index)
domain = cfg.get("domain", {})
title = str(domain.get("title") or "Plant Discovery")
description = str(
domain.get("description")
or "Identify plants, correct mistakes, and export local training data."
)
with gr.Blocks(
title=title,
analytics_enabled=False,
) as demo:
gr.Markdown(
f"# {title}\n\n{description}",
elem_classes=["plant-title"],
)
gr.Markdown(
"Local-first reference app generated around the OpenBMB Workbench template. "
"Model loading happens only after an explicit identify action."
)
build_plant_tab(plant_service, note_store, species_index)
return cast(gr.Blocks, demo)
def main() -> None:
parser = argparse.ArgumentParser(description="Plant Discovery reference app")
parser.add_argument("--config", default=str(DEFAULT_CONFIG))
parser.add_argument("--port", type=int, default=7861)
parser.add_argument("--share", action="store_true")
parser.add_argument(
"--model-mode",
choices=["openbmb", "finetuned", "demo"],
default="openbmb",
help=(
"openbmb uses MiniCPM-V, finetuned uses the configured adapter, "
"demo is deterministic."
),
)
parser.add_argument(
"--no-model",
action="store_true",
help="Use deterministic demo identification instead of loading a vision model.",
)
args = parser.parse_args()
model_mode = "demo" if args.no_model else args.model_mode
demo = build_app(args.config, no_model=args.no_model, model_mode=model_mode)
print(f"Starting Plant Discovery on http://127.0.0.1:{args.port}")
demo.launch(
server_port=args.port,
share=args.share,
theme=gr.themes.Soft(primary_hue="green", neutral_hue="slate"),
css=APP_CSS,
mcp_server=True,
)
if __name__ == "__main__":
main()