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.
|
frame: np.ndarray, shape (H, W) or (H, W, C), uint8 or float.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
events_binary: np.ndarray (H, W), values in {-1, 0, +1}
|
When threshold > 0:
|
||||||
events_strength: np.ndarray (H, W), values in [-1, 1]
|
events_binary: np.ndarray (H, W), values in {-1, 0, +1}
|
||||||
event_count: int, number of non-zero events
|
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)
|
brightness = self._to_grayscale(frame)
|
||||||
|
|
||||||
@@ -81,10 +86,18 @@ class EventProcessor:
|
|||||||
if self.prev_brightness is None:
|
if self.prev_brightness is None:
|
||||||
self.prev_brightness = brightness
|
self.prev_brightness = brightness
|
||||||
h, w = brightness.shape
|
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
|
return np.zeros((h, w), dtype=np.int8), np.zeros((h, w), dtype=np.float32), 0
|
||||||
|
|
||||||
change = self._compute_change(brightness)
|
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:
|
if self.auto_threshold:
|
||||||
self._update_auto_threshold(change)
|
self._update_auto_threshold(change)
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ from src.velocity_prediction.utils import (
|
|||||||
R_ODOM_TO_BODY,
|
R_ODOM_TO_BODY,
|
||||||
)
|
)
|
||||||
from src.velocity_prediction.config import DATASET_ROOT, VELOCITY_MEAN, VELOCITY_STD
|
from src.velocity_prediction.config import DATASET_ROOT, VELOCITY_MEAN, VELOCITY_STD
|
||||||
|
from src.event_utils import EventProcessor
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────── Data loading ────────────────────────────
|
# ──────────────────────────── Data loading ────────────────────────────
|
||||||
@@ -140,6 +141,8 @@ def draw_pose_overlay(
|
|||||||
euler: np.ndarray,
|
euler: np.ndarray,
|
||||||
frame_idx: int,
|
frame_idx: int,
|
||||||
ts: float,
|
ts: float,
|
||||||
|
events: np.ndarray | None = None,
|
||||||
|
show_events: bool = False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Draw body-frame pose and velocity information onto the image.
|
Draw body-frame pose and velocity information onto the image.
|
||||||
@@ -285,6 +288,21 @@ def draw_pose_overlay(
|
|||||||
cv2.LINE_AA,
|
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
|
return display
|
||||||
|
|
||||||
|
|
||||||
@@ -297,6 +315,7 @@ def create_video(
|
|||||||
fps: float = 30.0,
|
fps: float = 30.0,
|
||||||
max_frames: int | None = None,
|
max_frames: int | None = None,
|
||||||
show: bool = False,
|
show: bool = False,
|
||||||
|
show_events: bool = False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Read scene data, overlay pose info, and write to video file (or show).
|
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 for this scene
|
||||||
reset_attitude_offset()
|
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
|
# Get dimensions from first frame
|
||||||
h, w = frames[0]["img"].shape
|
h, w = frames[0]["img"].shape
|
||||||
|
|
||||||
@@ -334,6 +356,12 @@ def create_video(
|
|||||||
for i, frame_data in enumerate(frames):
|
for i, frame_data in enumerate(frames):
|
||||||
q_raw = frame_data["pose"][3:7] # [qx, qy, qz, qw] world→odom
|
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 vector (pitch & roll only, no yaw) — matches DiffPhysDrone
|
||||||
body_up = body_up_vector_np(q_raw) # (3,) unit vector
|
body_up = body_up_vector_np(q_raw) # (3,) unit vector
|
||||||
|
|
||||||
@@ -354,6 +382,8 @@ def create_video(
|
|||||||
euler=euler_deg,
|
euler=euler_deg,
|
||||||
frame_idx=i,
|
frame_idx=i,
|
||||||
ts=frame_data["ts"],
|
ts=frame_data["ts"],
|
||||||
|
events=events_binary,
|
||||||
|
show_events=show_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
if show:
|
if show:
|
||||||
@@ -404,6 +434,9 @@ def main():
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--show", action="store_true", help="Display on screen instead of saving video"
|
"--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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Collect scenes to process
|
# Collect scenes to process
|
||||||
@@ -436,6 +469,7 @@ def main():
|
|||||||
fps=args.fps,
|
fps=args.fps,
|
||||||
max_frames=args.max_frames,
|
max_frames=args.max_frames,
|
||||||
show=args.show,
|
show=args.show,
|
||||||
|
show_events=args.show_events,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user