img-processing / app.py
harsh-dev's picture
new layout
17a25a5 unverified
Raw
History Blame Contribute Delete
7.71 kB
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()