Spaces:
Sleeping
Sleeping
new layout
Browse files- app.py +114 -56
- diff_ai.py +33 -27
- requirements.txt +2 -1
app.py
CHANGED
|
@@ -3,6 +3,13 @@ import sys
|
|
| 3 |
import tempfile
|
| 4 |
import cv2
|
| 5 |
import dotenv
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
# Load environment variables (e.g. GROQ_API_KEY)
|
| 8 |
dotenv.load_dotenv()
|
|
@@ -12,20 +19,26 @@ current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
| 12 |
sys.path.append(current_dir)
|
| 13 |
sys.path.append(os.path.join(current_dir, "backend"))
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
from
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
if img1 is None or img2 is None:
|
| 23 |
-
return None, None, "Please upload both images.", {}
|
| 24 |
|
| 25 |
with tempfile.TemporaryDirectory() as tmpdir:
|
| 26 |
path1 = os.path.join(tmpdir, "img1.jpg")
|
| 27 |
path2 = os.path.join(tmpdir, "img2.jpg")
|
| 28 |
output_path = os.path.join(tmpdir, "result.png")
|
|
|
|
| 29 |
heatmap_path = os.path.join(tmpdir, "heatmap.png")
|
| 30 |
|
| 31 |
# Convert RGB inputs (Gradio) to BGR (OpenCV)
|
|
@@ -39,11 +52,13 @@ def gradio_compare(img1, img2, style_option="Segment Overlay"):
|
|
| 39 |
# Run comparison pipeline
|
| 40 |
pipeline_used = "AI Pipeline"
|
| 41 |
try:
|
| 42 |
-
|
| 43 |
-
result_data = compare_images_ai(path1, path2, output_path, heatmap_path, draw_style=draw_style)
|
| 44 |
except Exception as e:
|
| 45 |
print(f"[Gradio] AI pipeline failed ({e}); falling back to classical")
|
| 46 |
result_data = compare_images(path1, path2, output_path, heatmap_path)
|
|
|
|
|
|
|
|
|
|
| 47 |
result_data["pipeline"] = "classical"
|
| 48 |
pipeline_used = "Classical Pipeline"
|
| 49 |
|
|
@@ -54,13 +69,9 @@ def gradio_compare(img1, img2, style_option="Segment Overlay"):
|
|
| 54 |
description = f"Error generating description: {e}"
|
| 55 |
|
| 56 |
# Load output images (convert BGR back to RGB for Gradio display)
|
| 57 |
-
result_img = None
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
if os.path.exists(output_path):
|
| 61 |
-
result_img = cv2.cvtColor(cv2.imread(output_path), cv2.COLOR_BGR2RGB)
|
| 62 |
-
if os.path.exists(heatmap_path):
|
| 63 |
-
heatmap_img = cv2.cvtColor(cv2.imread(heatmap_path), cv2.COLOR_BGR2RGB)
|
| 64 |
|
| 65 |
# Format metrics dictionary
|
| 66 |
metrics = {
|
|
@@ -73,65 +84,112 @@ def gradio_compare(img1, img2, style_option="Segment Overlay"):
|
|
| 73 |
"Alignment Failed": "Yes" if result_data.get("alignment_failed") else "No"
|
| 74 |
}
|
| 75 |
|
| 76 |
-
|
|
|
|
|
|
|
| 77 |
|
| 78 |
-
# UI Theme and Layout Design
|
| 79 |
-
theme
|
|
|
|
| 80 |
primary_hue="indigo",
|
| 81 |
secondary_hue="slate",
|
| 82 |
neutral_hue="slate"
|
| 83 |
)
|
| 84 |
|
| 85 |
css_styling = """
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
}
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
"""
|
| 95 |
|
| 96 |
-
with gr.Blocks() as demo:
|
| 97 |
-
gr.HTML(
|
| 98 |
-
"""
|
| 99 |
-
<div style="text-align: center; max-width: 800px; margin: 0 auto 2rem auto; font-family: system-ui, -apple-system, sans-serif;">
|
| 100 |
-
<h1 style="font-size: 2.2rem; color: #4f46e5; margin-bottom: 0.5rem; font-weight: 700;">🔍 DiffLens AI — Visual Comparison & Change Analysis</h1>
|
| 101 |
-
<p style="font-size: 1.1rem; color: #4b5563; line-height: 1.6; margin: 0;">
|
| 102 |
-
Compare <b>Before</b> and <b>After</b> images of structures, scenes, or objects. The system aligns the images, detects change zones, runs segmentation (SAM2) and semantic filtering (DINOv2), and writes a detailed natural language explanation of changes.
|
| 103 |
-
</p>
|
| 104 |
-
</div>
|
| 105 |
-
"""
|
| 106 |
-
)
|
| 107 |
|
| 108 |
with gr.Row():
|
| 109 |
-
|
|
|
|
| 110 |
gr.Markdown("### Input Images")
|
| 111 |
-
img1_input = gr.Image(label="Before Image", type="numpy")
|
| 112 |
-
img2_input = gr.Image(label="After Image", type="numpy")
|
| 113 |
-
style_input = gr.Radio(
|
| 114 |
-
choices=["Segment Overlay", "Bounding Boxes Only"],
|
| 115 |
-
value="Segment Overlay",
|
| 116 |
-
label="Visualization Output Style"
|
| 117 |
-
)
|
| 118 |
-
submit_btn = gr.Button("Compare & Analyze", variant="primary", size="lg")
|
| 119 |
|
| 120 |
-
|
| 121 |
-
gr.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
with gr.Tabs():
|
| 123 |
-
with gr.TabItem("
|
| 124 |
-
result_img_output = gr.Image(label="
|
|
|
|
|
|
|
|
|
|
| 125 |
heatmap_img_output = gr.Image(label="Difference Heatmap")
|
| 126 |
-
with gr.TabItem("AI Interpretation & Metrics"):
|
| 127 |
-
desc_output = gr.Textbox(label="AI Explanation of What Changed", lines=6)
|
| 128 |
-
metrics_output = gr.JSON(label="Quantitative Comparison Metrics")
|
| 129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
submit_btn.click(
|
| 131 |
fn=gradio_compare,
|
| 132 |
-
inputs=[img1_input, img2_input
|
| 133 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
)
|
| 135 |
|
| 136 |
if __name__ == "__main__":
|
| 137 |
-
demo.launch(
|
|
|
|
| 3 |
import tempfile
|
| 4 |
import cv2
|
| 5 |
import dotenv
|
| 6 |
+
import gradio as gr
|
| 7 |
+
|
| 8 |
+
# Import the image slider component
|
| 9 |
+
try:
|
| 10 |
+
from gradio_imageslider import ImageSlider
|
| 11 |
+
except ImportError:
|
| 12 |
+
raise ImportError("Please install the slider component by running: pip install gradio-imageslider")
|
| 13 |
|
| 14 |
# Load environment variables (e.g. GROQ_API_KEY)
|
| 15 |
dotenv.load_dotenv()
|
|
|
|
| 19 |
sys.path.append(current_dir)
|
| 20 |
sys.path.append(os.path.join(current_dir, "backend"))
|
| 21 |
|
| 22 |
+
# Fallback imports in case backend files aren't found in your environment
|
| 23 |
+
try:
|
| 24 |
+
from diff_ai import compare_images_ai
|
| 25 |
+
from diff import compare_images
|
| 26 |
+
from describe import describe_change
|
| 27 |
+
except ImportError:
|
| 28 |
+
print("Warning: Backend modules not found. Using mock functions.")
|
| 29 |
+
def compare_images_ai(*args, **kwargs): return {"ssim_score": 0.95, "severity": "low"}
|
| 30 |
+
def compare_images(*args, **kwargs): return {"ssim_score": 0.95, "severity": "low"}
|
| 31 |
+
def describe_change(*args, **kwargs): return "Mock description of changes."
|
| 32 |
+
|
| 33 |
+
def gradio_compare(img1, img2):
|
| 34 |
if img1 is None or img2 is None:
|
| 35 |
+
return None, None, None, None, "Please upload both images.", {}
|
| 36 |
|
| 37 |
with tempfile.TemporaryDirectory() as tmpdir:
|
| 38 |
path1 = os.path.join(tmpdir, "img1.jpg")
|
| 39 |
path2 = os.path.join(tmpdir, "img2.jpg")
|
| 40 |
output_path = os.path.join(tmpdir, "result.png")
|
| 41 |
+
output_box_path = os.path.join(tmpdir, "result_box.png")
|
| 42 |
heatmap_path = os.path.join(tmpdir, "heatmap.png")
|
| 43 |
|
| 44 |
# Convert RGB inputs (Gradio) to BGR (OpenCV)
|
|
|
|
| 52 |
# Run comparison pipeline
|
| 53 |
pipeline_used = "AI Pipeline"
|
| 54 |
try:
|
| 55 |
+
result_data = compare_images_ai(path1, path2, output_path, heatmap_path, output_box_path=output_box_path)
|
|
|
|
| 56 |
except Exception as e:
|
| 57 |
print(f"[Gradio] AI pipeline failed ({e}); falling back to classical")
|
| 58 |
result_data = compare_images(path1, path2, output_path, heatmap_path)
|
| 59 |
+
if os.path.exists(output_path):
|
| 60 |
+
import shutil
|
| 61 |
+
shutil.copy(output_path, output_box_path)
|
| 62 |
result_data["pipeline"] = "classical"
|
| 63 |
pipeline_used = "Classical Pipeline"
|
| 64 |
|
|
|
|
| 69 |
description = f"Error generating description: {e}"
|
| 70 |
|
| 71 |
# Load output images (convert BGR back to RGB for Gradio display)
|
| 72 |
+
result_img = cv2.cvtColor(cv2.imread(output_path), cv2.COLOR_BGR2RGB) if os.path.exists(output_path) else None
|
| 73 |
+
box_img = cv2.cvtColor(cv2.imread(output_box_path), cv2.COLOR_BGR2RGB) if os.path.exists(output_box_path) else None
|
| 74 |
+
heatmap_img = cv2.cvtColor(cv2.imread(heatmap_path), cv2.COLOR_BGR2RGB) if os.path.exists(heatmap_path) else None
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
# Format metrics dictionary
|
| 77 |
metrics = {
|
|
|
|
| 84 |
"Alignment Failed": "Yes" if result_data.get("alignment_failed") else "No"
|
| 85 |
}
|
| 86 |
|
| 87 |
+
# Return slider tuple (img1, img2), plus the other images and texts
|
| 88 |
+
return (img1, img2), result_img, box_img, heatmap_img, description, metrics
|
| 89 |
+
|
| 90 |
|
| 91 |
+
# --- UI Theme and Layout Design ---
|
| 92 |
+
# Switched to Base theme for a more minimal starting point
|
| 93 |
+
theme = gr.themes.Base(
|
| 94 |
primary_hue="indigo",
|
| 95 |
secondary_hue="slate",
|
| 96 |
neutral_hue="slate"
|
| 97 |
)
|
| 98 |
|
| 99 |
css_styling = """
|
| 100 |
+
/* Container limit */
|
| 101 |
+
.gradio-container { max-width: 1600px !important; }
|
| 102 |
+
|
| 103 |
+
/* Force Tailwind text-sm (14px, line-height 20px) on text elements */
|
| 104 |
+
.gradio-container,
|
| 105 |
+
.gradio-container .prose *,
|
| 106 |
+
.gradio-container button,
|
| 107 |
+
.gradio-container span,
|
| 108 |
+
.gradio-container label,
|
| 109 |
+
.gradio-container input,
|
| 110 |
+
.gradio-container textarea,
|
| 111 |
+
.gradio-container p {
|
| 112 |
+
font-size: 0.875rem !important;
|
| 113 |
+
line-height: 1.25rem !important;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
/* Ensure headings are also text-sm but bolder to maintain hierarchy */
|
| 117 |
+
.gradio-container h1, .gradio-container h2, .gradio-container h3, .gradio-container h4 {
|
| 118 |
+
font-size: 0.875rem !important;
|
| 119 |
+
font-weight: 600 !important;
|
| 120 |
+
margin-bottom: 0.5rem !important;
|
| 121 |
}
|
| 122 |
+
|
| 123 |
+
/* Sidebar & Dark Panel styling */
|
| 124 |
+
.dark-panel {
|
| 125 |
+
padding: 20px;
|
| 126 |
+
border-radius: 12px;
|
| 127 |
+
background: rgba(0, 0, 0, 0.2) !important;
|
| 128 |
+
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
/* Minimal Process Button */
|
| 132 |
+
.process-btn {
|
| 133 |
+
margin-top: 20px;
|
| 134 |
+
font-weight: 600 !important;
|
| 135 |
+
border-radius: 8px !important;
|
| 136 |
+
background: rgba(255, 255, 255, 0.1) !important;
|
| 137 |
+
border: 1px solid rgba(255, 255, 255, 0.2) !important;
|
| 138 |
}
|
| 139 |
+
.process-btn:hover {
|
| 140 |
+
background: rgba(255, 255, 255, 0.15) !important;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
/* Remove default Gradio block borders for an ultra-minimal look */
|
| 144 |
+
.gr-block { border: none !important; }
|
| 145 |
"""
|
| 146 |
|
| 147 |
+
with gr.Blocks(theme=theme, css=css_styling) as demo:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
|
| 149 |
with gr.Row():
|
| 150 |
+
# --- LEFT COLUMN: Input Controls (Scale 1 for fixed-like width) ---
|
| 151 |
+
with gr.Column(scale=1, min_width=280, elem_classes=["dark-panel"]):
|
| 152 |
gr.Markdown("### Input Images")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
|
| 154 |
+
img1_input = gr.Image(label="BEFORE IMAGE", type="numpy", height=250)
|
| 155 |
+
img2_input = gr.Image(label="AFTER IMAGE", type="numpy", height=250)
|
| 156 |
+
|
| 157 |
+
submit_btn = gr.Button("Process Analysis", variant="primary", elem_classes=["process-btn"])
|
| 158 |
+
|
| 159 |
+
# --- RIGHT COLUMN: Unified Comparison Canvas (Scale 5 to consume all remaining space) ---
|
| 160 |
+
with gr.Column(scale=5, elem_classes=["dark-panel"]):
|
| 161 |
+
gr.Markdown("### Outputs")
|
| 162 |
+
|
| 163 |
+
# Image Slider component
|
| 164 |
+
slider_output = ImageSlider(label="Before / After", type="numpy", show_label=False)
|
| 165 |
+
|
| 166 |
+
# Visuals Tabs
|
| 167 |
with gr.Tabs():
|
| 168 |
+
with gr.TabItem("Segment Overlays"):
|
| 169 |
+
result_img_output = gr.Image(label="Segment Overlay Highlight")
|
| 170 |
+
with gr.TabItem("Bounding Boxes"):
|
| 171 |
+
result_box_output = gr.Image(label="Bounding Box Detections")
|
| 172 |
+
with gr.TabItem("Difference Heatmap"):
|
| 173 |
heatmap_img_output = gr.Image(label="Difference Heatmap")
|
|
|
|
|
|
|
|
|
|
| 174 |
|
| 175 |
+
# --- LOWER SECTION: AI Analysis & Metrics (Single Column) ---
|
| 176 |
+
gr.Markdown("### AI Analysis & Metrics")
|
| 177 |
+
desc_output = gr.Textbox(label="Explanation of Changes", lines=4)
|
| 178 |
+
metrics_output = gr.JSON(label="Quantitative Comparison")
|
| 179 |
+
|
| 180 |
+
# Connect the backend function
|
| 181 |
submit_btn.click(
|
| 182 |
fn=gradio_compare,
|
| 183 |
+
inputs=[img1_input, img2_input],
|
| 184 |
+
outputs=[
|
| 185 |
+
slider_output,
|
| 186 |
+
result_img_output,
|
| 187 |
+
result_box_output,
|
| 188 |
+
heatmap_img_output,
|
| 189 |
+
desc_output,
|
| 190 |
+
metrics_output
|
| 191 |
+
]
|
| 192 |
)
|
| 193 |
|
| 194 |
if __name__ == "__main__":
|
| 195 |
+
demo.launch()
|
diff_ai.py
CHANGED
|
@@ -458,7 +458,7 @@ def _composite_object(base, object_img, mask, bbox, color):
|
|
| 458 |
# MAIN ENTRY
|
| 459 |
# =====================================================================
|
| 460 |
|
| 461 |
-
def _compare_images_ai_impl(img1_path, img2_path, output_path, heatmap_path,
|
| 462 |
"""End-to-end change-region-driven AI pipeline. Raises RuntimeError
|
| 463 |
if any required model is unavailable."""
|
| 464 |
if not _get_loftr() or not _get_sam() or not _get_dinov2():
|
|
@@ -611,29 +611,18 @@ def _compare_images_ai_impl(img1_path, img2_path, output_path, heatmap_path, dra
|
|
| 611 |
n_total = n_added + n_removed + n_moved
|
| 612 |
severity = "HIGH" if n_total > 0 else "NONE"
|
| 613 |
|
| 614 |
-
# 5) Render output image
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
_draw_box(result_img, a["bbox"], COLOR_ADDED, "ADDED")
|
| 619 |
-
for r in removed_objects:
|
| 620 |
-
_draw_box(result_img, r["bbox"], COLOR_REMOVED, "REMOVED")
|
| 621 |
-
for m in moved_objects:
|
| 622 |
-
_draw_box(result_img, m["from"]["bbox"], COLOR_MOVED, "MOVED")
|
| 623 |
-
_draw_box(result_img, m["to"]["bbox"], COLOR_MOVED, "MOVED")
|
| 624 |
-
else:
|
| 625 |
-
# Dark-tint background + highlight changed objects (overlay)
|
| 626 |
-
DARK_FACTOR = 0.3
|
| 627 |
-
result_img = cv2.multiply(aligned, np.array([DARK_FACTOR] * 3, dtype=np.float64))
|
| 628 |
-
result_img = np.clip(result_img, 0, 255).astype(np.uint8)
|
| 629 |
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
|
| 638 |
# Draw severity label in top-left
|
| 639 |
label = f"{severity} +{n_added} -{n_removed} ~{n_moved}"
|
|
@@ -645,6 +634,23 @@ def _compare_images_ai_impl(img1_path, img2_path, output_path, heatmap_path, dra
|
|
| 645 |
cv2.imwrite(output_path, result_img)
|
| 646 |
cv2.imwrite(heatmap_path, heatmap)
|
| 647 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 648 |
return {
|
| 649 |
"pipeline": "ai",
|
| 650 |
"added": n_added,
|
|
@@ -658,8 +664,8 @@ def _compare_images_ai_impl(img1_path, img2_path, output_path, heatmap_path, dra
|
|
| 658 |
|
| 659 |
if HAS_SPACES:
|
| 660 |
@spaces.GPU
|
| 661 |
-
def compare_images_ai(img1_path, img2_path, output_path, heatmap_path,
|
| 662 |
-
return _compare_images_ai_impl(img1_path, img2_path, output_path, heatmap_path,
|
| 663 |
else:
|
| 664 |
-
def compare_images_ai(img1_path, img2_path, output_path, heatmap_path,
|
| 665 |
-
return _compare_images_ai_impl(img1_path, img2_path, output_path, heatmap_path,
|
|
|
|
| 458 |
# MAIN ENTRY
|
| 459 |
# =====================================================================
|
| 460 |
|
| 461 |
+
def _compare_images_ai_impl(img1_path, img2_path, output_path, heatmap_path, output_box_path=None):
|
| 462 |
"""End-to-end change-region-driven AI pipeline. Raises RuntimeError
|
| 463 |
if any required model is unavailable."""
|
| 464 |
if not _get_loftr() or not _get_sam() or not _get_dinov2():
|
|
|
|
| 611 |
n_total = n_added + n_removed + n_moved
|
| 612 |
severity = "HIGH" if n_total > 0 else "NONE"
|
| 613 |
|
| 614 |
+
# 5) Render output image - Segment Overlay (overlay)
|
| 615 |
+
DARK_FACTOR = 0.3
|
| 616 |
+
result_img = cv2.multiply(aligned, np.array([DARK_FACTOR] * 3, dtype=np.float64))
|
| 617 |
+
result_img = np.clip(result_img, 0, 255).astype(np.uint8)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 618 |
|
| 619 |
+
for a in added_objects:
|
| 620 |
+
_composite_object(result_img, aligned, a["mask"], a["bbox"], COLOR_ADDED)
|
| 621 |
+
for r in removed_objects:
|
| 622 |
+
_composite_object(result_img, r["source"], r["mask"], r["bbox"], COLOR_REMOVED)
|
| 623 |
+
for m in moved_objects:
|
| 624 |
+
_composite_object(result_img, m["from"]["source"], m["from"]["mask"], m["from"]["bbox"], COLOR_MOVED)
|
| 625 |
+
_composite_object(result_img, aligned, m["to"]["mask"], m["to"]["bbox"], COLOR_MOVED)
|
| 626 |
|
| 627 |
# Draw severity label in top-left
|
| 628 |
label = f"{severity} +{n_added} -{n_removed} ~{n_moved}"
|
|
|
|
| 634 |
cv2.imwrite(output_path, result_img)
|
| 635 |
cv2.imwrite(heatmap_path, heatmap)
|
| 636 |
|
| 637 |
+
# 6) Render output image - Bounding Box (box)
|
| 638 |
+
if output_box_path:
|
| 639 |
+
box_img = aligned.copy()
|
| 640 |
+
for a in added_objects:
|
| 641 |
+
_draw_box(box_img, a["bbox"], COLOR_ADDED, "ADDED")
|
| 642 |
+
for r in removed_objects:
|
| 643 |
+
_draw_box(box_img, r["bbox"], COLOR_REMOVED, "REMOVED")
|
| 644 |
+
for m in moved_objects:
|
| 645 |
+
_draw_box(box_img, m["from"]["bbox"], COLOR_MOVED, "MOVED")
|
| 646 |
+
_draw_box(box_img, m["to"]["bbox"], COLOR_MOVED, "MOVED")
|
| 647 |
+
|
| 648 |
+
cv2.putText(box_img, label, (20, 42),
|
| 649 |
+
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 255, 255), 5)
|
| 650 |
+
cv2.putText(box_img, label, (20, 42),
|
| 651 |
+
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 0), 2)
|
| 652 |
+
cv2.imwrite(output_box_path, box_img)
|
| 653 |
+
|
| 654 |
return {
|
| 655 |
"pipeline": "ai",
|
| 656 |
"added": n_added,
|
|
|
|
| 664 |
|
| 665 |
if HAS_SPACES:
|
| 666 |
@spaces.GPU
|
| 667 |
+
def compare_images_ai(img1_path, img2_path, output_path, heatmap_path, output_box_path=None):
|
| 668 |
+
return _compare_images_ai_impl(img1_path, img2_path, output_path, heatmap_path, output_box_path=output_box_path)
|
| 669 |
else:
|
| 670 |
+
def compare_images_ai(img1_path, img2_path, output_path, heatmap_path, output_box_path=None):
|
| 671 |
+
return _compare_images_ai_impl(img1_path, img2_path, output_path, heatmap_path, output_box_path=output_box_path)
|
requirements.txt
CHANGED
|
@@ -13,4 +13,5 @@ kornia
|
|
| 13 |
ultralytics
|
| 14 |
transformers
|
| 15 |
python-dotenv
|
| 16 |
-
gradio
|
|
|
|
|
|
| 13 |
ultralytics
|
| 14 |
transformers
|
| 15 |
python-dotenv
|
| 16 |
+
gradio
|
| 17 |
+
gradio-imageslider
|