Spaces:
Sleeping
Sleeping
Commit ·
7d0f836
1
Parent(s): debf6fe
Clean visualization: subtle tint overlay, outline-only boxes, 30-region cap, high-contrast labels
Browse files- app/detection_engine.py +31 -30
app/detection_engine.py
CHANGED
|
@@ -728,18 +728,22 @@ def _severity_from_region(region, total_pixels):
|
|
| 728 |
return "major"
|
| 729 |
|
| 730 |
|
| 731 |
-
#
|
| 732 |
_SEVERITY_COLORS = {
|
| 733 |
-
"minor": (
|
| 734 |
-
"moderate": (
|
| 735 |
-
"major": (
|
| 736 |
}
|
| 737 |
|
|
|
|
|
|
|
|
|
|
| 738 |
|
| 739 |
def visualize_changes(img1, img2, change_mask, regions=None, total_pixels=None):
|
| 740 |
"""
|
| 741 |
-
|
| 742 |
-
|
|
|
|
| 743 |
"""
|
| 744 |
if img1.shape != img2.shape:
|
| 745 |
img2 = cv2.resize(img2, (img1.shape[1], img1.shape[0]))
|
|
@@ -750,50 +754,47 @@ def visualize_changes(img1, img2, change_mask, regions=None, total_pixels=None):
|
|
| 750 |
mask_bool = change_mask > 127
|
| 751 |
mask_float = mask_bool.astype(np.float32)
|
| 752 |
|
| 753 |
-
#
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
|
|
|
| 757 |
for c in range(3):
|
| 758 |
overlay[:, :, c] = (overlay[:, :, c] * (1 - mask_float * alpha)
|
| 759 |
-
+
|
| 760 |
|
| 761 |
overlay_uint8 = np.clip(overlay, 0, 255).astype(np.uint8)
|
| 762 |
total_px = total_pixels if total_pixels is not None else (img2.shape[0] * img2.shape[1])
|
| 763 |
|
| 764 |
if regions:
|
| 765 |
diag = np.sqrt(img2.shape[0]**2 + img2.shape[1]**2)
|
| 766 |
-
line_thickness = max(1, int(diag /
|
|
|
|
|
|
|
| 767 |
|
| 768 |
-
for r in
|
| 769 |
x, y, w, h = r["bbox"]
|
| 770 |
severity = r.get("severity") or _severity_from_region(r, total_px)
|
| 771 |
color = _SEVERITY_COLORS.get(severity, (255, 255, 255))
|
| 772 |
|
| 773 |
-
#
|
| 774 |
-
x1c = max(0, x)
|
| 775 |
-
y1c = max(0, y)
|
| 776 |
-
x2c = min(overlay_uint8.shape[1], x + w)
|
| 777 |
-
y2c = min(overlay_uint8.shape[0], y + h)
|
| 778 |
-
roi = overlay_uint8[y1c:y2c, x1c:x2c]
|
| 779 |
-
fill = np.full_like(roi, color, dtype=np.uint8)
|
| 780 |
-
cv2.addWeighted(fill, 0.07, roi, 0.93, 0, roi)
|
| 781 |
-
|
| 782 |
cv2.rectangle(overlay_uint8, (x, y), (x + w, y + h), color, line_thickness)
|
| 783 |
|
|
|
|
| 784 |
rid = r.get("id", 0)
|
| 785 |
label = str(rid)
|
| 786 |
font = cv2.FONT_HERSHEY_SIMPLEX
|
| 787 |
-
font_scale = max(0.
|
| 788 |
-
|
| 789 |
-
(tw, th), _ = cv2.getTextSize(label, font, font_scale,
|
| 790 |
lx = x
|
| 791 |
-
ly = max(th +
|
|
|
|
| 792 |
cv2.rectangle(overlay_uint8,
|
| 793 |
-
(lx, ly - th -
|
| 794 |
-
|
| 795 |
-
cv2.putText(overlay_uint8, label, (lx +
|
| 796 |
-
font, font_scale,
|
| 797 |
|
| 798 |
return overlay_uint8
|
| 799 |
|
|
|
|
| 728 |
return "major"
|
| 729 |
|
| 730 |
|
| 731 |
+
# RGB colors for severity — high-contrast, colorblind-friendly palette
|
| 732 |
_SEVERITY_COLORS = {
|
| 733 |
+
"minor": (50, 205, 50), # Lime green
|
| 734 |
+
"moderate": (255, 165, 0), # Orange
|
| 735 |
+
"major": (255, 50, 50), # Bright red
|
| 736 |
}
|
| 737 |
|
| 738 |
+
# Maximum bounding boxes drawn on the image to avoid visual clutter
|
| 739 |
+
_MAX_VISIBLE_BOXES = 30
|
| 740 |
+
|
| 741 |
|
| 742 |
def visualize_changes(img1, img2, change_mask, regions=None, total_pixels=None):
|
| 743 |
"""
|
| 744 |
+
Clean visualization: subtle tinted overlay for changed pixels,
|
| 745 |
+
color-coded contour outlines (not filled boxes) for the top regions,
|
| 746 |
+
and compact numbered labels.
|
| 747 |
"""
|
| 748 |
if img1.shape != img2.shape:
|
| 749 |
img2 = cv2.resize(img2, (img1.shape[1], img1.shape[0]))
|
|
|
|
| 754 |
mask_bool = change_mask > 127
|
| 755 |
mask_float = mask_bool.astype(np.float32)
|
| 756 |
|
| 757 |
+
# Subtle warm tint on changed pixels (18% alpha) — enough to see, not enough to hide
|
| 758 |
+
tint = np.zeros_like(img2, dtype=np.float32)
|
| 759 |
+
tint[:, :, 0] = 255
|
| 760 |
+
tint[:, :, 1] = 80
|
| 761 |
+
alpha = 0.18
|
| 762 |
for c in range(3):
|
| 763 |
overlay[:, :, c] = (overlay[:, :, c] * (1 - mask_float * alpha)
|
| 764 |
+
+ tint[:, :, c] * mask_float * alpha)
|
| 765 |
|
| 766 |
overlay_uint8 = np.clip(overlay, 0, 255).astype(np.uint8)
|
| 767 |
total_px = total_pixels if total_pixels is not None else (img2.shape[0] * img2.shape[1])
|
| 768 |
|
| 769 |
if regions:
|
| 770 |
diag = np.sqrt(img2.shape[0]**2 + img2.shape[1]**2)
|
| 771 |
+
line_thickness = max(1, int(diag / 1100))
|
| 772 |
+
|
| 773 |
+
visible = regions[:_MAX_VISIBLE_BOXES]
|
| 774 |
|
| 775 |
+
for r in visible:
|
| 776 |
x, y, w, h = r["bbox"]
|
| 777 |
severity = r.get("severity") or _severity_from_region(r, total_px)
|
| 778 |
color = _SEVERITY_COLORS.get(severity, (255, 255, 255))
|
| 779 |
|
| 780 |
+
# Draw only the outline — no fill, keeps the image readable
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 781 |
cv2.rectangle(overlay_uint8, (x, y), (x + w, y + h), color, line_thickness)
|
| 782 |
|
| 783 |
+
# Compact label: region number in a small pill
|
| 784 |
rid = r.get("id", 0)
|
| 785 |
label = str(rid)
|
| 786 |
font = cv2.FONT_HERSHEY_SIMPLEX
|
| 787 |
+
font_scale = max(0.32, min(0.48, w / 200))
|
| 788 |
+
txt_thick = 1
|
| 789 |
+
(tw, th), _ = cv2.getTextSize(label, font, font_scale, txt_thick)
|
| 790 |
lx = x
|
| 791 |
+
ly = max(th + 3, y - 3)
|
| 792 |
+
# Dark background pill for contrast on any terrain
|
| 793 |
cv2.rectangle(overlay_uint8,
|
| 794 |
+
(lx, ly - th - 2), (lx + tw + 5, ly + 1),
|
| 795 |
+
(30, 30, 30), cv2.FILLED)
|
| 796 |
+
cv2.putText(overlay_uint8, label, (lx + 2, ly - 1),
|
| 797 |
+
font, font_scale, color, txt_thick, cv2.LINE_AA)
|
| 798 |
|
| 799 |
return overlay_uint8
|
| 800 |
|