from __future__ import annotations from pathlib import Path from typing import Any, Dict import gradio as gr import torch from PIL import Image from huggingface_hub import hf_hub_download import sys from pathlib import Path as _Path ROOT = _Path(__file__).resolve().parent SRC_DIR = ROOT / "src" if str(SRC_DIR) not in sys.path: sys.path.insert(0, str(SRC_DIR)) from infer.pipeline import LoadedModel, load_checkpoint, predict_image MODEL_REPO_ID = "alphamike/GraphAttributeLearning_Model" MODEL_FILENAMES = { "baseline": "baseline/best.pt", "gnn1": "gnn1/best.pt", "gnn2": "gnn2/best.pt", } class ModelRegistry: def __init__(self) -> None: self._cache: Dict[str, LoadedModel] = {} def get(self, key: str, path: Path, device: torch.device) -> LoadedModel: cached = self._cache.get(key) if cached is not None: return cached loaded = load_checkpoint(path, device=device) self._cache[key] = loaded return loaded registry = ModelRegistry() def infer_gradio( image: Image.Image, model_type: str, checkpoint_path: str, top_k: int, threshold: float, ) -> Any: if image is None: return [], "No image provided." # Resolve checkpoint: either use the textbox value as filename within the model repo # or fall back to the default mapping for the selected model_type. filename = (checkpoint_path or "").strip() or MODEL_FILENAMES.get(model_type, "") if not filename: return [], f"Unknown model_type '{model_type}'." try: local_ckpt_path = hf_hub_download( repo_id=MODEL_REPO_ID, filename=filename, repo_type="model", ) except Exception as exc: # noqa: BLE001 return [], f"Error downloading checkpoint from Hub: {exc}" path = Path(local_ckpt_path) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") try: key = f"{model_type}:{path}" loaded = registry.get(key, path, device) # Save temp image to disk-agnostic path is unnecessary; use pipeline logic directly. from infer.pipeline import predict_image as predict_from_loaded from infer.pipeline import LoadedModel as _LM # type: ignore assert isinstance(loaded, _LM) # Wrap PIL image call using temporary in-memory path emulation. # For simplicity, reuse adapter directly. result = loaded.adapter.predict( image=image, label_vocab=loaded.label_vocab, top_k=top_k, threshold=threshold, ) rows = [ {"label": label, "score": round(score, 4), "positive@thr": label in result.positives} for label, score in zip(result.labels, result.scores) ] message = f"Positives (>= {threshold:.2f}): {', '.join(result.positives) if result.positives else 'none'}" return rows, message except Exception as exc: # noqa: BLE001 return [], f"Error: {exc}" with gr.Blocks() as demo: gr.Markdown( "# Adjective-Aware Chair Attributes\n" "Select baseline, GNN 1, or GNN 2, upload an image, and view attribute scores.\n\n" f"Models are loaded from Hugging Face model repo: `{MODEL_REPO_ID}`." ) with gr.Row(): with gr.Column(scale=1): image_input = gr.Image(type="pil", label="Chair image") model_type = gr.Radio( choices=["baseline", "gnn1", "gnn2"], value="baseline", label="Model type", ) checkpoint = gr.Textbox( value=MODEL_FILENAMES["baseline"], label="Checkpoint filename (within Hub repo)", ) def _sync_ckpt(choice: str) -> str: return MODEL_FILENAMES.get(choice, MODEL_FILENAMES["baseline"]) model_type.change(_sync_ckpt, inputs=model_type, outputs=checkpoint) top_k = gr.Slider(1, 20, value=5, step=1, label="Top-K") threshold = gr.Slider(0.0, 1.0, value=0.5, step=0.01, label="Threshold") run_btn = gr.Button("Run inference") with gr.Column(scale=2): table = gr.Dataframe( headers=["label", "score", "positive@thr"], datatype=["str", "number", "bool"], label="Attribute scores", ) message = gr.Markdown() run_btn.click( infer_gradio, inputs=[image_input, model_type, checkpoint, top_k, threshold], outputs=[table, message], ) if __name__ == "__main__": demo.launch()