initial commit
This commit is contained in:
103
src/event_utils.py
Normal file
103
src/event_utils.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
Event camera simulation utilities for ML preprocessing.
|
||||
|
||||
Core logic extracted from EventCameraSimulator (test.py).
|
||||
Designed for frame-by-frame preprocessing in training pipelines.
|
||||
|
||||
Output:
|
||||
events_binary: (-1, 0, +1) hard threshold decision
|
||||
events_strength: [-1, 1] continuous change intensity (clipped & normalized)
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from collections import deque
|
||||
|
||||
|
||||
class EventProcessor:
|
||||
"""Lightweight event computation module. No visualization, no OpenCV dependency."""
|
||||
|
||||
def __init__(self, threshold=0.1, use_log=True, auto_threshold=False):
|
||||
self.threshold = threshold
|
||||
self.use_log = use_log
|
||||
self.auto_threshold = auto_threshold
|
||||
|
||||
self.prev_brightness = None
|
||||
self.change_history = deque(maxlen=100)
|
||||
self.threshold_scale = 1.5
|
||||
|
||||
def reset(self):
|
||||
"""Clear temporal state (call on video/reset)."""
|
||||
self.prev_brightness = None
|
||||
self.change_history.clear()
|
||||
|
||||
def _to_grayscale(self, frame):
|
||||
"""Convert frame to grayscale float32."""
|
||||
if frame.ndim == 3:
|
||||
# RGB/HWC -> gray via luminance weights
|
||||
gray = 0.299 * frame[..., 0] + 0.587 * frame[..., 1] + 0.114 * frame[..., 2]
|
||||
else:
|
||||
gray = frame
|
||||
return gray.astype(np.float32)
|
||||
|
||||
def _compute_change(self, brightness):
|
||||
"""Compute log or linear brightness change."""
|
||||
if self.use_log:
|
||||
eps = 1e-3
|
||||
return np.log(brightness + eps) - np.log(self.prev_brightness + eps)
|
||||
else:
|
||||
return brightness - self.prev_brightness
|
||||
|
||||
def _update_auto_threshold(self, change):
|
||||
"""Adapt threshold based on global change statistics."""
|
||||
abs_change = np.abs(change)
|
||||
mean_change = np.mean(abs_change)
|
||||
self.change_history.append(mean_change)
|
||||
|
||||
if len(self.change_history) > 10:
|
||||
avg_change = np.mean(self.change_history)
|
||||
new_threshold = max(avg_change * self.threshold_scale, 0.01)
|
||||
self.threshold = self.threshold * 0.9 + new_threshold * 0.1
|
||||
|
||||
if self.use_log:
|
||||
self.threshold = np.clip(self.threshold, 0.01, 0.5)
|
||||
else:
|
||||
self.threshold = np.clip(self.threshold, 1, 50)
|
||||
|
||||
def __call__(self, frame):
|
||||
"""
|
||||
Process a single frame.
|
||||
|
||||
Args:
|
||||
frame: np.ndarray, shape (H, W) or (H, W, C), uint8 or float.
|
||||
|
||||
Returns:
|
||||
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
|
||||
"""
|
||||
brightness = self._to_grayscale(frame)
|
||||
|
||||
# First frame — initialise, no events
|
||||
if self.prev_brightness is None:
|
||||
self.prev_brightness = brightness
|
||||
h, w = brightness.shape
|
||||
return np.zeros((h, w), dtype=np.int8), np.zeros((h, w), dtype=np.float32), 0
|
||||
|
||||
change = self._compute_change(brightness)
|
||||
|
||||
if self.auto_threshold:
|
||||
self._update_auto_threshold(change)
|
||||
|
||||
# Binary events
|
||||
events_binary = np.zeros_like(brightness, dtype=np.int8)
|
||||
events_binary[change > self.threshold] = 1
|
||||
events_binary[change < -self.threshold] = -1
|
||||
|
||||
# Continuous strength: clip to [-threshold, threshold] then normalise to [-1, 1]
|
||||
events_strength = np.clip(change, -self.threshold, self.threshold) / self.threshold
|
||||
|
||||
event_count = int(np.count_nonzero(events_binary))
|
||||
|
||||
self.prev_brightness = brightness
|
||||
|
||||
return events_binary, events_strength, event_count
|
||||
Reference in New Issue
Block a user