| """Construction Safety Hazard Detection — Gradio Demo (HF Spaces).""" |
|
|
| from __future__ import annotations |
|
|
| import logging |
| import os |
| import sys |
|
|
| import gradio as gr |
| from PIL import Image |
|
|
| logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") |
| logger = logging.getLogger(__name__) |
|
|
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) |
|
|
| from pipeline import PipelineResult, SafetyPipeline |
| from vlm_manager import VLMManager |
| import visualizer |
|
|
| MODEL_ID = "Qwen/Qwen3.5-9B" |
| logger.info("Initializing pipeline with %s ...", MODEL_ID) |
| _vlm = VLMManager(model_id=MODEL_ID) |
| _pipeline = SafetyPipeline(_vlm) |
| logger.info("Pipeline ready.") |
|
|
|
|
| def analyze_image( |
| image: Image.Image | None, |
| ) -> tuple[Image.Image | None, str, str, str, str]: |
| if image is None: |
| return None, "Please upload a construction site image.", "", "", "" |
|
|
| result: PipelineResult = _pipeline.run(image) |
|
|
| det_text = visualizer.format_detection_text(result.detections) |
| prompt_text = result.detection_guided_prompt |
| hazard_text = visualizer.format_hazard_text(result.hazard_result) |
|
|
| timing = ( |
| f"Detection: {result.detection_time_ms:.0f}ms | " |
| f"Analysis: {result.analysis_time_ms:.0f}ms | " |
| f"Total: {result.total_time_ms:.0f}ms" |
| ) |
|
|
| raw_section = "" |
| if result.hazard_result.raw_response: |
| raw_section = f"\n\n--- Raw VLM Response ---\n{result.hazard_result.raw_response}" |
|
|
| return ( |
| result.annotated_image, |
| det_text, |
| prompt_text, |
| hazard_text + raw_section, |
| timing, |
| ) |
|
|
|
|
| with gr.Blocks( |
| title="Construction Safety Hazard Detection", |
| theme=gr.themes.Soft(), |
| ) as demo: |
| gr.Markdown( |
| "# Construction Safety Hazard Detection\n" |
| "**Two-stage detection-guided VLM pipeline** -- " |
| "upload a construction site image to identify safety hazards.\n\n" |
| "*Based on: Integration of Object Detection and Small VLMs for " |
| "Construction Safety Hazard Identification (Adil et al., 2025)*" |
| ) |
|
|
| with gr.Row(): |
| with gr.Column(scale=1): |
| input_image = gr.Image(label="Input Image", type="pil", height=400) |
| run_btn = gr.Button("Analyze Safety Hazards", variant="primary", size="lg") |
| timing_text = gr.Textbox(label="Timing", interactive=False) |
| with gr.Column(scale=1): |
| output_image = gr.Image(label="Detection + Hazard Results", type="pil", height=400) |
|
|
| with gr.Row(): |
| with gr.Column(): |
| det_output = gr.Textbox(label="Stage 1: Detected Objects", lines=8, interactive=False) |
| with gr.Column(): |
| hazard_output = gr.Textbox(label="Stage 2: Hazard Analysis", lines=8, interactive=False) |
|
|
| with gr.Accordion("Detection-Guided Prompt (sent to VLM)", open=False): |
| prompt_output = gr.Textbox(label="Constructed Prompt", lines=15, interactive=False) |
|
|
| gr.Markdown( |
| "### Hazard Categories\n" |
| "| Category | Description |\n" |
| "|---|---|\n" |
| "| **PPE Non-Compliance** | Workers not wearing hard hats or high-visibility safety vests |\n" |
| "| **Fall Hazard** | Workers near elevated positions, excavations, or temporary structures without fall protection |\n" |
| "| **Caught-between Hazard** | Workers at risk of being struck, crushed, or pinned by machinery or structures |\n" |
| "| **Unsafe Environment** | Exposed rebar, uneven terrain, debris, open electrical wires, poor lighting |" |
| ) |
|
|
| run_btn.click( |
| fn=analyze_image, |
| inputs=[input_image], |
| outputs=[output_image, det_output, prompt_output, hazard_output, timing_text], |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("GRADIO_SERVER_PORT", 7860))) |
|
|