"""Waveform-shape metrics for false-trigger detection. A false trigger is an isolated impulse (quiet → spike → quiet); a real event rings for many cycles. Two numbers separate them: crest factor (how far the peak stands above the typical sample) and how many samples sit near the peak. """ from __future__ import annotations import numpy as np _GEO_CHANNELS = ("Tran", "Vert", "Long") NEAR_PEAK_FRACTION = 0.5 # a sample "near the peak" is >= this * peak amplitude def channel_shape(x) -> dict | None: x = np.asarray(x, dtype=float) if x.size < 2: return None peak = float(np.max(np.abs(x))) if peak <= 0: return None rms = float(np.sqrt(np.mean(x ** 2))) if rms <= 0: return None near = int(np.sum(np.abs(x) >= NEAR_PEAK_FRACTION * peak)) return {"crest_factor": peak / rms, "near_peak_count": near, "sample_count": int(x.size)} def shape_from_samples(chans: dict) -> dict | None: best_axis, best_peak, best_x = None, -1.0, None for ax in _GEO_CHANNELS: x = chans.get(ax) if x is None: continue x = np.asarray(x, dtype=float) if x.size < 2: continue p = float(np.max(np.abs(x))) if p > best_peak: best_axis, best_peak, best_x = ax, p, x if best_axis is None: return None s = channel_shape(best_x) if s is None: return None s["axis"] = best_axis return s def shape_from_h5(path) -> dict | None: import h5py try: with h5py.File(path, "r") as f: chans = {ax: f[f"samples/{ax}"][:] for ax in _GEO_CHANNELS if f"samples/{ax}" in f} except Exception: return None return shape_from_samples(chans)