feat: add --show-events overlay with raw log intensity
Visualize raw temporal brightness change (threshold=0, log domain) as green(+)/red(-) gradient overlay proportional to |change|. Supports video output and live display modes. Enables EventProcessor threshold=0 for raw mode without clipping. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -71,9 +71,14 @@ class EventProcessor:
|
||||
frame: np.ndarray, shape (H, W) or (H, W, C), uint8 or float.
|
||||
|
||||
Returns:
|
||||
When threshold > 0:
|
||||
events_binary: np.ndarray (H, W), values in {-1, 0, +1}
|
||||
events_strength: np.ndarray (H, W), values in [-1, 1]
|
||||
event_count: int, number of non-zero events
|
||||
When threshold == 0 (raw output, no thresholding):
|
||||
change_raw: np.ndarray (H, W), raw log/linear brightness change (float32)
|
||||
change_raw: same as above
|
||||
event_count: int, number of pixels with non-zero change
|
||||
"""
|
||||
brightness = self._to_grayscale(frame)
|
||||
|
||||
@@ -81,10 +86,18 @@ class EventProcessor:
|
||||
if self.prev_brightness is None:
|
||||
self.prev_brightness = brightness
|
||||
h, w = brightness.shape
|
||||
if self.threshold == 0:
|
||||
return np.zeros((h, w), dtype=np.float32), np.zeros((h, w), dtype=np.float32), 0
|
||||
return np.zeros((h, w), dtype=np.int8), np.zeros((h, w), dtype=np.float32), 0
|
||||
|
||||
change = self._compute_change(brightness)
|
||||
|
||||
# threshold == 0: raw mode, skip thresholding
|
||||
if self.threshold == 0:
|
||||
self.prev_brightness = brightness
|
||||
change_f32 = change.astype(np.float32)
|
||||
return change_f32, change_f32, int(np.count_nonzero(change))
|
||||
|
||||
if self.auto_threshold:
|
||||
self._update_auto_threshold(change)
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ from src.velocity_prediction.utils import (
|
||||
R_ODOM_TO_BODY,
|
||||
)
|
||||
from src.velocity_prediction.config import DATASET_ROOT, VELOCITY_MEAN, VELOCITY_STD
|
||||
from src.event_utils import EventProcessor
|
||||
|
||||
|
||||
# ──────────────────────────── Data loading ────────────────────────────
|
||||
@@ -140,6 +141,8 @@ def draw_pose_overlay(
|
||||
euler: np.ndarray,
|
||||
frame_idx: int,
|
||||
ts: float,
|
||||
events: np.ndarray | None = None,
|
||||
show_events: bool = False,
|
||||
):
|
||||
"""
|
||||
Draw body-frame pose and velocity information onto the image.
|
||||
@@ -285,6 +288,21 @@ def draw_pose_overlay(
|
||||
cv2.LINE_AA,
|
||||
)
|
||||
|
||||
# ── Event overlay (gradient temporal intensity) ──
|
||||
if show_events and events is not None:
|
||||
limit = max(np.abs(events).max(), 1e-6)
|
||||
norm = np.clip(events / limit, -1.0, 1.0)
|
||||
pos = norm > 0
|
||||
neg = norm < 0
|
||||
intensity = np.abs(norm) # [0, 1] magnitude
|
||||
overlay = np.zeros_like(display, dtype=np.uint8)
|
||||
# bg = np.ones_like(display, dtype=np.uint8) * 255
|
||||
# Color intensity proportional to |norm|: dark → bright
|
||||
overlay[pos, 1] = (255 * intensity[pos]).astype(np.uint8) # green channel
|
||||
overlay[neg, 2] = (255 * intensity[neg]).astype(np.uint8) # red channel
|
||||
# display = cv2.addWeighted(bg, 0.5, overlay, 1.0, 0)
|
||||
display = cv2.addWeighted(display, 0.5, overlay, 1.0, 0)
|
||||
|
||||
return display
|
||||
|
||||
|
||||
@@ -297,6 +315,7 @@ def create_video(
|
||||
fps: float = 30.0,
|
||||
max_frames: int | None = None,
|
||||
show: bool = False,
|
||||
show_events: bool = False,
|
||||
):
|
||||
"""
|
||||
Read scene data, overlay pose info, and write to video file (or show).
|
||||
@@ -316,6 +335,9 @@ def create_video(
|
||||
# Reset attitude offset for this scene
|
||||
reset_attitude_offset()
|
||||
|
||||
# Event processor (threshold=0 → raw temporal intensity)
|
||||
event_processor = EventProcessor(threshold=0.3, use_log=True) if show_events else None
|
||||
|
||||
# Get dimensions from first frame
|
||||
h, w = frames[0]["img"].shape
|
||||
|
||||
@@ -334,6 +356,12 @@ def create_video(
|
||||
for i, frame_data in enumerate(frames):
|
||||
q_raw = frame_data["pose"][3:7] # [qx, qy, qz, qw] world→odom
|
||||
|
||||
# Compute events if enabled
|
||||
if event_processor is not None:
|
||||
events_binary, _, _ = event_processor(frame_data["img"])
|
||||
else:
|
||||
events_binary = None
|
||||
|
||||
# Body up vector (pitch & roll only, no yaw) — matches DiffPhysDrone
|
||||
body_up = body_up_vector_np(q_raw) # (3,) unit vector
|
||||
|
||||
@@ -354,6 +382,8 @@ def create_video(
|
||||
euler=euler_deg,
|
||||
frame_idx=i,
|
||||
ts=frame_data["ts"],
|
||||
events=events_binary,
|
||||
show_events=show_events,
|
||||
)
|
||||
|
||||
if show:
|
||||
@@ -404,6 +434,9 @@ def main():
|
||||
parser.add_argument(
|
||||
"--show", action="store_true", help="Display on screen instead of saving video"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--show-events", action="store_true", help="Overlay event frames (green=+1, red=-1)"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Collect scenes to process
|
||||
@@ -436,6 +469,7 @@ def main():
|
||||
fps=args.fps,
|
||||
max_frames=args.max_frames,
|
||||
show=args.show,
|
||||
show_events=args.show_events,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user