Spaces:
Running on Zero
Running on Zero
| 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() | |