jakies pierdoly
This commit is contained in:
1 parent
6a13b1ef04
commit
ae7dc34053
6 files changed
+136
-67
No files matched your search
+36
-30
@@ -16,17 +16,29 @@ MAX_CONTINUOUS_GAP_S = 0.5
|
||||
|
||||
FEATURE_COLUMNS = [
|
||||
"velocity_deg_s",
|
||||
"accel_px_s2",
|
||||
"dispersion_px",
|
||||
"accel_deg_s2",
|
||||
"dispersion_deg",
|
||||
"confidence",
|
||||
"velocity_deg_s_mean",
|
||||
"velocity_deg_s_std",
|
||||
"dispersion_px_mean",
|
||||
"dispersion_deg_mean",
|
||||
]
|
||||
|
||||
EVENT_LABELS = ("fixation", "saccade", "pursuit")
|
||||
|
||||
|
||||
def px_to_deg(distance_px: float, cm_per_px: float, viewing_distance_cm: float) -> float:
|
||||
"""Convert a pixel distance to visual angle, given screen and viewing-distance calibration.
|
||||
|
||||
Every recording can be a different physical screen at a different
|
||||
distance, so any pixel-unit feature (dispersion, acceleration) is not
|
||||
comparable across sessions unless converted through this. velocity_deg_s
|
||||
already goes through the equivalent conversion at record time.
|
||||
"""
|
||||
distance_cm = distance_px * cm_per_px
|
||||
return math.degrees(2.0 * math.atan2(distance_cm / 2.0, viewing_distance_cm))
|
||||
|
||||
|
||||
def add_rolling_features(df, session_col: str = "session_id"):
|
||||
"""Add the *_mean / *_std rolling columns to a DataFrame in place, returning it.
|
||||
|
||||
@@ -35,11 +47,11 @@ def add_rolling_features(df, session_col: str = "session_id"):
|
||||
samples of a session usable (with a noisier estimate) instead of NaN.
|
||||
"""
|
||||
grouped = df.groupby(session_col, sort=False)
|
||||
roll = grouped["velocity_deg_s"].rolling(FEATURE_WINDOW, min_periods=1)
|
||||
df["velocity_deg_s_mean"] = roll.mean().reset_index(level=0, drop=True)
|
||||
df["velocity_deg_s_std"] = roll.std().reset_index(level=0, drop=True).fillna(0.0)
|
||||
df["dispersion_px_mean"] = (
|
||||
grouped["dispersion_px"].rolling(FEATURE_WINDOW, min_periods=1)
|
||||
velocity_roll = grouped["velocity_deg_s"].rolling(FEATURE_WINDOW, min_periods=1)
|
||||
df["velocity_deg_s_mean"] = velocity_roll.mean().reset_index(level=0, drop=True)
|
||||
df["velocity_deg_s_std"] = velocity_roll.std().reset_index(level=0, drop=True).fillna(0.0)
|
||||
df["dispersion_deg_mean"] = (
|
||||
grouped["dispersion_deg"].rolling(FEATURE_WINDOW, min_periods=1)
|
||||
.mean().reset_index(level=0, drop=True)
|
||||
)
|
||||
return df
|
||||
@@ -60,47 +72,41 @@ class OnlineFeatureTracker:
|
||||
self.velocity_history: deque[float] = deque(maxlen=FEATURE_WINDOW)
|
||||
self.dispersion_history: deque[float] = deque(maxlen=FEATURE_WINDOW)
|
||||
self.prev_sample: tuple[float, float, float] | None = None
|
||||
self.prev_velocity_px_s: float | None = None
|
||||
self.prev_velocity_deg_s: float | None = None
|
||||
|
||||
def reset(self) -> None:
|
||||
self.history.clear()
|
||||
self.velocity_history.clear()
|
||||
self.dispersion_history.clear()
|
||||
self.prev_sample = None
|
||||
self.prev_velocity_px_s = None
|
||||
self.prev_velocity_deg_s = None
|
||||
|
||||
def update(self, timestamp: float, x: float, y: float, confidence: float) -> dict | None:
|
||||
velocity_px_s = accel_px_s2 = None
|
||||
velocity_deg_s = accel_deg_s2 = None
|
||||
if self.prev_sample is not None:
|
||||
prev_t, prev_x, prev_y = self.prev_sample
|
||||
dt = timestamp - prev_t
|
||||
if 0 < dt <= MAX_CONTINUOUS_GAP_S:
|
||||
dist_px = math.dist((prev_x, prev_y), (x, y))
|
||||
velocity_px_s = dist_px / dt
|
||||
if self.prev_velocity_px_s is not None:
|
||||
accel_px_s2 = (velocity_px_s - self.prev_velocity_px_s) / dt
|
||||
velocity_px_s = math.dist((prev_x, prev_y), (x, y)) / dt
|
||||
velocity_deg_s = px_to_deg(velocity_px_s, self.cm_per_px, self.viewing_distance_cm)
|
||||
if self.prev_velocity_deg_s is not None:
|
||||
accel_deg_s2 = (velocity_deg_s - self.prev_velocity_deg_s) / dt
|
||||
self.prev_sample = (timestamp, x, y)
|
||||
self.prev_velocity_px_s = velocity_px_s
|
||||
|
||||
velocity_deg_s = None
|
||||
if velocity_px_s is not None:
|
||||
velocity_cm_s = velocity_px_s * self.cm_per_px
|
||||
velocity_deg_s = math.degrees(
|
||||
2.0 * math.atan2(velocity_cm_s / 2.0, self.viewing_distance_cm)
|
||||
)
|
||||
self.prev_velocity_deg_s = velocity_deg_s
|
||||
|
||||
self.history.append((x, y, timestamp))
|
||||
dispersion_px = None
|
||||
dispersion_deg = None
|
||||
if len(self.history) >= 2:
|
||||
xs = [p[0] for p in self.history]
|
||||
ys = [p[1] for p in self.history]
|
||||
dispersion_px = (max(xs) - min(xs)) + (max(ys) - min(ys))
|
||||
dispersion_deg = px_to_deg(dispersion_px, self.cm_per_px, self.viewing_distance_cm)
|
||||
|
||||
if velocity_deg_s is None or dispersion_px is None:
|
||||
if velocity_deg_s is None or dispersion_deg is None:
|
||||
return None
|
||||
|
||||
self.velocity_history.append(velocity_deg_s)
|
||||
self.dispersion_history.append(dispersion_px)
|
||||
self.dispersion_history.append(dispersion_deg)
|
||||
|
||||
n = len(self.velocity_history)
|
||||
velocity_deg_s_mean = sum(self.velocity_history) / n
|
||||
@@ -109,14 +115,14 @@ class OnlineFeatureTracker:
|
||||
velocity_deg_s_std = math.sqrt(variance)
|
||||
else:
|
||||
velocity_deg_s_std = 0.0
|
||||
dispersion_px_mean = sum(self.dispersion_history) / len(self.dispersion_history)
|
||||
dispersion_deg_mean = sum(self.dispersion_history) / len(self.dispersion_history)
|
||||
|
||||
return {
|
||||
"velocity_deg_s": velocity_deg_s,
|
||||
"accel_px_s2": accel_px_s2 if accel_px_s2 is not None else 0.0,
|
||||
"dispersion_px": dispersion_px,
|
||||
"accel_deg_s2": accel_deg_s2 if accel_deg_s2 is not None else 0.0,
|
||||
"dispersion_deg": dispersion_deg,
|
||||
"confidence": confidence,
|
||||
"velocity_deg_s_mean": velocity_deg_s_mean,
|
||||
"velocity_deg_s_std": velocity_deg_s_std,
|
||||
"dispersion_px_mean": dispersion_px_mean,
|
||||
"dispersion_deg_mean": dispersion_deg_mean,
|
||||
}
|
||||
Reference in new issue
Block a user