coderuday21 commited on
Commit
7d0f836
·
1 Parent(s): debf6fe

Clean visualization: subtle tint overlay, outline-only boxes, 30-region cap, high-contrast labels

Browse files
Files changed (1) hide show
  1. 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
- # BGR colors for severity (OpenCV uses BGR)
732
  _SEVERITY_COLORS = {
733
- "minor": (0, 200, 0), # Green
734
- "moderate": (0, 255, 255), # Yellow
735
- "major": (0, 0, 255), # Red
736
  }
737
 
 
 
 
738
 
739
  def visualize_changes(img1, img2, change_mask, regions=None, total_pixels=None):
740
  """
741
- Overlay change mask on 'after' image; draw color-coded bounding boxes
742
- by severity (green=minor, yellow=moderate, red=major) and numbered labels.
 
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
- # Lighter red overlay (35% alpha) so the image stays readable
754
- red_layer = np.zeros_like(img2, dtype=np.float32)
755
- red_layer[:, :, 0] = 255
756
- alpha = 0.35
 
757
  for c in range(3):
758
  overlay[:, :, c] = (overlay[:, :, c] * (1 - mask_float * alpha)
759
- + red_layer[:, :, c] * mask_float * alpha)
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 / 900))
 
 
767
 
768
- for r in regions:
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
- # Semi-transparent fill using only the ROI (avoids full-image copy)
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.35, min(0.55, w / 180))
788
- thickness = 1
789
- (tw, th), _ = cv2.getTextSize(label, font, font_scale, thickness)
790
  lx = x
791
- ly = max(th + 4, y - 4)
 
792
  cv2.rectangle(overlay_uint8,
793
- (lx, ly - th - 3), (lx + tw + 6, ly + 1),
794
- color, cv2.FILLED)
795
- cv2.putText(overlay_uint8, label, (lx + 3, ly - 1),
796
- font, font_scale, (255, 255, 255), thickness, cv2.LINE_AA)
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