File size: 7,714 Bytes
3ba5b62
 
 
 
 
17a25a5
 
 
 
 
 
 
3ba5b62
 
 
 
 
 
 
 
 
17a25a5
 
 
 
 
 
 
 
 
 
 
 
3ba5b62
17a25a5
3ba5b62
 
 
 
 
17a25a5
3ba5b62
 
 
 
 
 
 
 
 
 
 
 
 
17a25a5
3ba5b62
 
 
17a25a5
 
 
3ba5b62
 
 
 
 
 
 
 
 
 
17a25a5
 
 
3ba5b62
 
 
 
 
 
 
 
 
 
 
 
17a25a5
 
 
3ba5b62
17a25a5
 
 
3ba5b62
 
 
 
 
 
17a25a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3ba5b62
17a25a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3ba5b62
17a25a5
 
 
 
 
 
3ba5b62
 
17a25a5
3ba5b62
 
17a25a5
 
3ba5b62
 
17a25a5
 
 
 
 
 
 
 
 
 
 
 
 
3ba5b62
17a25a5
 
 
 
 
3ba5b62
 
17a25a5
 
 
 
 
 
3ba5b62
 
17a25a5
 
 
 
 
 
 
 
 
3ba5b62
 
 
17a25a5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import os
import sys
import tempfile
import cv2
import dotenv
import gradio as gr

# Import the image slider component
try:
    from gradio_imageslider import ImageSlider
except ImportError:
    raise ImportError("Please install the slider component by running: pip install gradio-imageslider")

# Load environment variables (e.g. GROQ_API_KEY)
dotenv.load_dotenv()

# Add app.py directory and backend/ directory to system path so imports work in all layouts
current_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(current_dir)
sys.path.append(os.path.join(current_dir, "backend"))

# Fallback imports in case backend files aren't found in your environment
try:
    from diff_ai import compare_images_ai
    from diff import compare_images
    from describe import describe_change
except ImportError:
    print("Warning: Backend modules not found. Using mock functions.")
    def compare_images_ai(*args, **kwargs): return {"ssim_score": 0.95, "severity": "low"}
    def compare_images(*args, **kwargs): return {"ssim_score": 0.95, "severity": "low"}
    def describe_change(*args, **kwargs): return "Mock description of changes."

def gradio_compare(img1, img2):
    if img1 is None or img2 is None:
        return None, None, None, None, "Please upload both images.", {}

    with tempfile.TemporaryDirectory() as tmpdir:
        path1 = os.path.join(tmpdir, "img1.jpg")
        path2 = os.path.join(tmpdir, "img2.jpg")
        output_path = os.path.join(tmpdir, "result.png")
        output_box_path = os.path.join(tmpdir, "result_box.png")
        heatmap_path = os.path.join(tmpdir, "heatmap.png")

        # Convert RGB inputs (Gradio) to BGR (OpenCV)
        img1_bgr = cv2.cvtColor(img1, cv2.COLOR_RGB2BGR)
        img2_bgr = cv2.cvtColor(img2, cv2.COLOR_RGB2BGR)

        # Save images
        cv2.imwrite(path1, img1_bgr)
        cv2.imwrite(path2, img2_bgr)

        # Run comparison pipeline
        pipeline_used = "AI Pipeline"
        try:
            result_data = compare_images_ai(path1, path2, output_path, heatmap_path, output_box_path=output_box_path)
        except Exception as e:
            print(f"[Gradio] AI pipeline failed ({e}); falling back to classical")
            result_data = compare_images(path1, path2, output_path, heatmap_path)
            if os.path.exists(output_path):
                import shutil
                shutil.copy(output_path, output_box_path)
            result_data["pipeline"] = "classical"
            pipeline_used = "Classical Pipeline"

        # Generate description
        try:
            description = describe_change(path1, path2, result_data)
        except Exception as e:
            description = f"Error generating description: {e}"

        # Load output images (convert BGR back to RGB for Gradio display)
        result_img = cv2.cvtColor(cv2.imread(output_path), cv2.COLOR_BGR2RGB) if os.path.exists(output_path) else None
        box_img = cv2.cvtColor(cv2.imread(output_box_path), cv2.COLOR_BGR2RGB) if os.path.exists(output_box_path) else None
        heatmap_img = cv2.cvtColor(cv2.imread(heatmap_path), cv2.COLOR_BGR2RGB) if os.path.exists(heatmap_path) else None

        # Format metrics dictionary
        metrics = {
            "Pipeline Used": pipeline_used,
            "SSIM Score (Similarity)": f"{result_data.get('ssim_score', 0):.4f}" if isinstance(result_data.get('ssim_score'), (int, float)) else "N/A",
            "LPIPS Distance (Perceptual)": f"{result_data.get('lpips_max', 0):.4f}" if isinstance(result_data.get('lpips_max'), (int, float)) else "N/A",
            "Detected Object Changes": result_data.get("object_changes", "0"),
            "Detected Crack Count": result_data.get("crack_count", "0"),
            "Change Severity": str(result_data.get("severity", "none")).upper(),
            "Alignment Failed": "Yes" if result_data.get("alignment_failed") else "No"
        }

        # Return slider tuple (img1, img2), plus the other images and texts
        return (img1, img2), result_img, box_img, heatmap_img, description, metrics


# --- UI Theme and Layout Design ---
# Switched to Base theme for a more minimal starting point
theme = gr.themes.Base(
    primary_hue="indigo",
    secondary_hue="slate",
    neutral_hue="slate"
)

css_styling = """
/* Container limit */
.gradio-container { max-width: 1600px !important; }

/* Force Tailwind text-sm (14px, line-height 20px) on text elements */
.gradio-container, 
.gradio-container .prose *, 
.gradio-container button, 
.gradio-container span, 
.gradio-container label, 
.gradio-container input, 
.gradio-container textarea, 
.gradio-container p {
    font-size: 0.875rem !important; 
    line-height: 1.25rem !important;
}

/* Ensure headings are also text-sm but bolder to maintain hierarchy */
.gradio-container h1, .gradio-container h2, .gradio-container h3, .gradio-container h4 {
    font-size: 0.875rem !important;
    font-weight: 600 !important;
    margin-bottom: 0.5rem !important;
}

/* Sidebar & Dark Panel styling */
.dark-panel { 
    padding: 20px;
    border-radius: 12px; 
    background: rgba(0, 0, 0, 0.2) !important;
    border: 1px solid rgba(255, 255, 255, 0.1) !important;
}

/* Minimal Process Button */
.process-btn { 
    margin-top: 20px; 
    font-weight: 600 !important; 
    border-radius: 8px !important; 
    background: rgba(255, 255, 255, 0.1) !important;
    border: 1px solid rgba(255, 255, 255, 0.2) !important;
}
.process-btn:hover {
    background: rgba(255, 255, 255, 0.15) !important;
}

/* Remove default Gradio block borders for an ultra-minimal look */
.gr-block { border: none !important; }
"""

with gr.Blocks(theme=theme, css=css_styling) as demo:
    
    with gr.Row():
        # --- LEFT COLUMN: Input Controls (Scale 1 for fixed-like width) ---
        with gr.Column(scale=1, min_width=280, elem_classes=["dark-panel"]):
            gr.Markdown("### Input Images")
            
            img1_input = gr.Image(label="BEFORE IMAGE", type="numpy", height=250)
            img2_input = gr.Image(label="AFTER IMAGE", type="numpy", height=250)
                
            submit_btn = gr.Button("Process Analysis", variant="primary", elem_classes=["process-btn"])
            
        # --- RIGHT COLUMN: Unified Comparison Canvas (Scale 5 to consume all remaining space) ---
        with gr.Column(scale=5, elem_classes=["dark-panel"]):
            gr.Markdown("### Outputs")
                
            # Image Slider component
            slider_output = ImageSlider(label="Before / After", type="numpy", show_label=False)
            
            # Visuals Tabs
            with gr.Tabs():
                with gr.TabItem("Segment Overlays"):
                    result_img_output = gr.Image(label="Segment Overlay Highlight")
                with gr.TabItem("Bounding Boxes"):
                    result_box_output = gr.Image(label="Bounding Box Detections")
                with gr.TabItem("Difference Heatmap"):
                    heatmap_img_output = gr.Image(label="Difference Heatmap")
                    
            # --- LOWER SECTION: AI Analysis & Metrics (Single Column) ---
            gr.Markdown("### AI Analysis & Metrics")
            desc_output = gr.Textbox(label="Explanation of Changes", lines=4)
            metrics_output = gr.JSON(label="Quantitative Comparison")

    # Connect the backend function
    submit_btn.click(
        fn=gradio_compare,
        inputs=[img1_input, img2_input],
        outputs=[
            slider_output, 
            result_img_output, 
            result_box_output, 
            heatmap_img_output, 
            desc_output, 
            metrics_output
        ]
    )

if __name__ == "__main__":
    demo.launch()