Update processing.py
Browse files- processing.py +79 -23
processing.py
CHANGED
|
@@ -356,34 +356,72 @@ def generate_vector_sketch(detections: List[Dict[str, Any]], max_bytes: int = 10
|
|
| 356 |
|
| 357 |
|
| 358 |
# --------------------- sonar overlay / wireframe ---------------------------
|
| 359 |
-
def fuse_sonar_overlay(rgb: np.ndarray, sonar_data: Dict[str, Any]) -> str:
|
| 360 |
-
|
| 361 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
|
|
|
|
|
|
|
|
|
|
| 363 |
center = (w // 2, h // 2)
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
|
| 372 |
# Keep original contour logic also
|
| 373 |
if sonar_data:
|
| 374 |
contours = sonar_data.get("contours", [])
|
| 375 |
for c in contours:
|
| 376 |
-
|
| 377 |
for nx, ny in c:
|
| 378 |
px = int(np.clip(nx, 0.0, 1.0) * (w - 1))
|
| 379 |
py = int(np.clip(ny, 0.0, 1.0) * (h - 1))
|
| 380 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 381 |
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
cv2.polylines(canvas, [pts_np], True, (0, 255, 255), 2)
|
| 385 |
|
| 386 |
-
return _array_to_base64(canvas, fmt="PNG")
|
| 387 |
|
| 388 |
# --------------------------- SITREP helper ---------------------------------
|
| 389 |
# ===================== 🔥 NEW VISUAL FEATURES ==============================
|
|
@@ -417,18 +455,36 @@ def draw_detection_boxes(rgb: np.ndarray, detections: List[Dict[str, Any]]) -> s
|
|
| 417 |
|
| 418 |
def generate_bioluminescence(rgb: np.ndarray) -> str:
|
| 419 |
"""
|
| 420 |
-
|
|
|
|
|
|
|
| 421 |
"""
|
| 422 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
|
| 424 |
-
tint
|
| 425 |
-
tint
|
| 426 |
-
tint[:, :,
|
|
|
|
|
|
|
| 427 |
|
| 428 |
-
|
| 429 |
-
|
|
|
|
|
|
|
| 430 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
return _array_to_base64(final, fmt="JPEG")
|
|
|
|
| 432 |
|
| 433 |
def detections_to_sitrep_txt(detections: List[Dict[str, Any]]) -> str:
|
| 434 |
if not detections:
|
|
|
|
| 356 |
|
| 357 |
|
| 358 |
# --------------------- sonar overlay / wireframe ---------------------------
|
| 359 |
+
def fuse_sonar_overlay(rgb: np.ndarray, sonar_data: Optional[Dict[str, Any]] = None) -> str:
|
| 360 |
+
"""
|
| 361 |
+
Draw sonar overlay with radar rings and sweep wedge.
|
| 362 |
+
sonar_data can contain:
|
| 363 |
+
- angle: center angle in degrees (0 = right, 90 = up)
|
| 364 |
+
- sweep: sweep half-width in degrees
|
| 365 |
+
- max_range: radius for wedge (in px)
|
| 366 |
+
- contours: list of normalized contour polygons
|
| 367 |
+
"""
|
| 368 |
+
if rgb is None:
|
| 369 |
+
raise ValueError("rgb image is required")
|
| 370 |
|
| 371 |
+
# OpenCV drawing expects BGR. Convert, draw, then convert back.
|
| 372 |
+
bgr = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
|
| 373 |
+
h, w = bgr.shape[:2]
|
| 374 |
center = (w // 2, h // 2)
|
| 375 |
+
radius_limit = min(center)
|
| 376 |
+
|
| 377 |
+
# draw concentric rings
|
| 378 |
+
for r in range(50, max(60, radius_limit), 60):
|
| 379 |
+
if r >= radius_limit:
|
| 380 |
+
break
|
| 381 |
+
cv2.circle(bgr, center, r, (0, 255, 0), 1)
|
| 382 |
+
|
| 383 |
+
# parameters from sonar_data or defaults
|
| 384 |
+
angle = float(sonar_data.get("angle", 0)) if sonar_data else 0.0
|
| 385 |
+
sweep = float(sonar_data.get("sweep", 20)) if sonar_data else 20.0
|
| 386 |
+
max_r = int(sonar_data.get("max_range", radius_limit * 0.9)) if sonar_data else int(radius_limit * 0.9)
|
| 387 |
+
|
| 388 |
+
# make a translucent wedge for the sweep
|
| 389 |
+
overlay = bgr.copy()
|
| 390 |
+
start_angle = angle - sweep / 2.0
|
| 391 |
+
end_angle = angle + sweep / 2.0
|
| 392 |
+
|
| 393 |
+
# build polygon points (center + arc)
|
| 394 |
+
points = [center]
|
| 395 |
+
for ang in np.linspace(start_angle, end_angle, num=40):
|
| 396 |
+
rad = np.deg2rad(ang)
|
| 397 |
+
x = int(center[0] + max_r * np.cos(rad))
|
| 398 |
+
y = int(center[1] - max_r * np.sin(rad)) # coordinate system: y down => subtract
|
| 399 |
+
points.append((x, y))
|
| 400 |
+
|
| 401 |
+
pts = np.array(points, dtype=np.int32)
|
| 402 |
+
cv2.fillPoly(overlay, [pts], (0, 255, 0))
|
| 403 |
+
fused = cv2.addWeighted(bgr, 1.0, overlay, 0.20, 0)
|
| 404 |
+
|
| 405 |
+
# optional: draw a sweep outline
|
| 406 |
+
cv2.polylines(fused, [pts], isClosed=False, color=(0, 255, 0), thickness=1)
|
| 407 |
|
| 408 |
# Keep original contour logic also
|
| 409 |
if sonar_data:
|
| 410 |
contours = sonar_data.get("contours", [])
|
| 411 |
for c in contours:
|
| 412 |
+
pts_contour = []
|
| 413 |
for nx, ny in c:
|
| 414 |
px = int(np.clip(nx, 0.0, 1.0) * (w - 1))
|
| 415 |
py = int(np.clip(ny, 0.0, 1.0) * (h - 1))
|
| 416 |
+
pts_contour.append([px, py])
|
| 417 |
+
|
| 418 |
+
if len(pts_contour) >= 2:
|
| 419 |
+
pts_np = np.array(pts_contour, dtype=np.int32)
|
| 420 |
+
cv2.polylines(fused, [pts_np], True, (0, 255, 255), 2)
|
| 421 |
|
| 422 |
+
final_rgb = cv2.cvtColor(fused, cv2.COLOR_BGR2RGB)
|
| 423 |
+
return _array_to_base64(final_rgb, fmt="PNG")
|
|
|
|
| 424 |
|
|
|
|
| 425 |
|
| 426 |
# --------------------------- SITREP helper ---------------------------------
|
| 427 |
# ===================== 🔥 NEW VISUAL FEATURES ==============================
|
|
|
|
| 455 |
|
| 456 |
def generate_bioluminescence(rgb: np.ndarray) -> str:
|
| 457 |
"""
|
| 458 |
+
Create a bioluminescence effect:
|
| 459 |
+
- stronger blurred glow (Gaussian)
|
| 460 |
+
- cyan/teal tint blended on top
|
| 461 |
"""
|
| 462 |
+
if rgb is None:
|
| 463 |
+
raise ValueError("rgb image is required")
|
| 464 |
+
|
| 465 |
+
# use an explicit odd kernel for blur (clear and reliable)
|
| 466 |
+
glow = cv2.GaussianBlur(rgb, (21, 21), 0)
|
| 467 |
|
| 468 |
+
# build a cyan/teal tint - in RGB format (not BGR)
|
| 469 |
+
tint = np.zeros_like(rgb, dtype=np.uint8)
|
| 470 |
+
tint[:, :, 0] = 100 # Blue channel (in RGB)
|
| 471 |
+
tint[:, :, 1] = 160 # Green channel (in RGB)
|
| 472 |
+
tint[:, :, 2] = 40 # Red channel (keep low for cyan/teal)
|
| 473 |
|
| 474 |
+
# operate in float to avoid early clipping, then clip at the end
|
| 475 |
+
base_f = rgb.astype(np.float32)
|
| 476 |
+
glow_f = glow.astype(np.float32)
|
| 477 |
+
tint_f = tint.astype(np.float32)
|
| 478 |
|
| 479 |
+
# mix base + glow (glow should be visible but not wash out)
|
| 480 |
+
combined = cv2.addWeighted(base_f, 0.7, glow_f, 0.4, 0.0)
|
| 481 |
+
|
| 482 |
+
# add tint softly
|
| 483 |
+
final_f = cv2.addWeighted(combined, 1.0, tint_f, 0.25, 0.0)
|
| 484 |
+
|
| 485 |
+
final = np.clip(final_f, 0, 255).astype(np.uint8)
|
| 486 |
return _array_to_base64(final, fmt="JPEG")
|
| 487 |
+
|
| 488 |
|
| 489 |
def detections_to_sitrep_txt(detections: List[Dict[str, Any]]) -> str:
|
| 490 |
if not detections:
|