Agent2 / app.py
tasnime24's picture
Upload app.py
8a11007 verified
Raw
History Blame Contribute Delete
5.55 kB
"""
Agent 2 - Visual Damage-Type Classification (Gradio version)
------------------------------------------------------------------
Same pattern as Agent 1's Space: Gradio Blocks, @spaces.GPU decorator
applied from the start this time (learned from Agent 1's ZeroGPU surprise -
no need to hit that issue twice).
Routes by item_type:
- laptop -> the hybrid YOLO+VLM detector (needs laptop_damage.pt, uploaded
directly to this Space's files)
- everything else -> the VLM-only detector
GOOGLE_API_KEY is read from the environment, NOT taken as user input - set
it as a Repository Secret in this Space's Settings, never passed through
the UI or the API call itself. This matches how damage_type_classifier.py
already works (falls back to os.environ.get("GOOGLE_API_KEY") when no
api_key is explicitly passed) - no code change needed there, just correct
deployment configuration here.
Run locally to test: python app.py
"""
import os
import gradio as gr
from hybrid_damage_detector import detect_damage_hybrid
from vlm_only_detector import detect_damage_vlm_only
try:
import spaces
gpu_decorator = spaces.GPU
except ImportError:
def gpu_decorator(func):
return func
ITEM_TYPES = ["laptop", "tablet", "vr_headset", "lab_kit", "projector", "printer"]
# laptop_damage.pt must be uploaded directly into this Space alongside app.py
LAPTOP_MODEL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "laptop_damage.pt")
@gpu_decorator
def process(item_type, crop_image, change_score, baseline_image, return_image):
"""
Core logic, kept as a plain function so it's testable independently of
Gradio's UI wiring.
baseline_image/return_image are OPTIONAL - when provided, lets the VLM
check whether the flagged damage is genuinely new or already present
in the baseline (per the old-vs-new-damage feature built earlier).
"""
if not item_type or item_type not in ITEM_TYPES:
return {"error": f"Unknown item_type '{item_type}'. Known types: {ITEM_TYPES}"}
if not crop_image:
return {"error": "A crop image (the flagged region from Agent 1) is required."}
if not os.environ.get("GOOGLE_API_KEY"):
return {"error": "GOOGLE_API_KEY is not set on this Space - add it as a Repository Secret "
"in Settings, this cannot be provided through the API call itself."}
try:
if item_type == "laptop":
if not os.path.isfile(LAPTOP_MODEL_PATH):
return {"error": f"laptop_damage.pt not found at {LAPTOP_MODEL_PATH} - "
f"upload the model file directly into this Space's files."}
result = detect_damage_hybrid(
image_path=crop_image, model_path=LAPTOP_MODEL_PATH, item_type=item_type,
vlm_change_score=change_score, baseline_path=baseline_image, return_path=return_image,
)
else:
result = detect_damage_vlm_only(
image_path=crop_image, item_type=item_type,
change_score=change_score, baseline_path=baseline_image, return_path=return_image,
)
# FIX: "description" already always defaults to "" (never a missing
# key) inside result["vlm_assessment"]["description"] - confirmed
# directly in damage_type_classifier.py. Adding a TOP-LEVEL alias
# here too, since the real integration issue may be a caller
# reading result["description"] directly instead of the nested
# path - this way it works either way, regardless of which it is.
result["description"] = result.get("vlm_assessment", {}).get("description", "")
return result
except Exception as e:
return {"error": f"Agent 2 processing failed: {e}"}
with gr.Blocks(title="Agent 2 - Visual Damage Classification") as demo:
gr.Markdown("# Agent 2 - Visual Damage-Type Classification\n"
"Given a flagged photo region from Agent 1, identifies the damage type and severity. "
"Combines a trained detector (laptop only) with a vision-language model (all item types).")
# allow_custom_value=True: same fix as Agent 4 - lets an invalid value reach
# our own validation instead of Gradio crashing on it first.
item_type_input = gr.Dropdown(label="Item Type", choices=ITEM_TYPES, allow_custom_value=True)
crop_input = gr.Image(label="Flagged Crop (from Agent 1)", type="filepath")
change_score_input = gr.Slider(label="Agent 1's Change Score", minimum=0.0, maximum=1.0, value=0.5, step=0.01)
gr.Markdown("**Optional** - the full baseline/return photos, so the model can check whether "
"this damage is genuinely new or already present in the baseline:")
with gr.Row():
baseline_input = gr.Image(label="Full Baseline Photo (optional)", type="filepath")
return_input = gr.Image(label="Full Return Photo (optional)", type="filepath")
with gr.Row():
submit_btn = gr.Button("Classify Damage", variant="primary")
clear_btn = gr.ClearButton(
value="Clear All (start a fresh test)",
components=[item_type_input, crop_input, change_score_input, baseline_input, return_input],
)
output = gr.JSON(label="Result")
clear_btn.add(output)
submit_btn.click(
fn=process,
inputs=[item_type_input, crop_input, change_score_input, baseline_input, return_input],
outputs=output,
api_name="classify",
)
if __name__ == "__main__":
demo.launch()