Spaces:
Sleeping
Sleeping
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() |