3. Engineer Time-Domain Features from Accelerometer Signals for Deployment Classification
Why Time-Domain Features Matter for Deployment Classification
Airbag deployment decisions are driven by the shape of the crash pulse, not just its magnitude. A 40 g pulse that rises in 8 ms and decays quickly is a fundamentally different event from a 40 g pulse that ramps up over 60 ms. Time-domain features capture exactly this: how fast the acceleration builds, how long it stays high, how abruptly it changes, and how much energy it delivers. These are the quantities a crash-sensing algorithm (and a downstream classifier) can act on within the 10–30 ms decision window available before deployment.
In this case we work with two accelerometer channels recorded at 10 kHz: a vehicle channel (B-pillar or tunnel-mounted, measuring the crash pulse) and an occupant channel (seat- or chest-mounted, measuring occupant response). We extract five time-domain features per channel — peak, rise time, impulse, jerk, and energy — and then evaluate how well each feature separates deploying from non-deploying events.
Feature Definitions and Sign Conventions
All signals are expressed in g with the crash direction defined as positive (a frontal impact produces a positive acceleration pulse). This convention is fixed for the entire case; every formula below assumes it. Sampling interval is dt = 1/10000 = 1e-4 s.
| Feature | Definition | Units | What it captures |
|---|---|---|---|
| Peak | max(a[n]) | g | Severity of the pulse |
| Rise time | time from 10% to 90% of peak, on the leading edge | ms | How fast the crash develops |
| Impulse | Σ a[n]·dt over the pulse window | g·s | Integrated momentum change (≈ delta-v) |
| Jerk | max(|a[n] − a[n−1]|)/dt | g/s | Abruptness / high-frequency content |
| Energy | Σ a[n]²·dt | g²·s | Signal power delivered to the structure |
Rise time is computed on the first crossing of the 10% and 90% thresholds relative to the peak, searching forward from the pulse onset. If the signal never reaches 90% of peak before the peak sample, rise time is set to the time from onset to peak. This edge case matters for clipped or saturated channels.
Implementation
The extractor below is self-contained: it takes a dictionary of channels (each a 1-D NumPy array in g) and returns a feature row. It is written to run as-is with only NumPy and pandas.
import numpy as np
import pandas as pd
FS = 10_000.0
DT = 1.0 / FS
def time_domain_features(a, fs=FS):
a = np.asarray(a, dtype=float)
dt = 1.0 / fs
peak = float(np.max(a))
# onset: first sample exceeding 5% of peak
onset_idx = int(np.argmax(a >= 0.05 * peak))
peak_idx = int(np.argmax(a))
# rise time: 10% -> 90% of peak on leading edge
lo = 0.10 * peak
hi = 0.90 * peak
seg = a[onset_idx:peak_idx + 1]
t10 = np.argmax(seg >= lo)
t90 = np.argmax(seg >= hi)
if seg[t90] < hi: # never reached 90% before peak
rise_ms = (peak_idx - onset_idx) * dt * 1000.0
else:
rise_ms = (t90 - t10) * dt * 1000.0
impulse = float(np.sum(a) * dt)
jerk = float(np.max(np.abs(np.diff(a))) / dt)
energy = float(np.sum(a ** 2) * dt)
return {
"peak": peak,
"rise_ms": rise_ms,
"impulse": impulse,
"jerk": jerk,
"energy": energy,
}
def build_feature_table(channels, labels):
rows = []
for i, (veh, occ) in enumerate(zip(channels["vehicle"], channels["occupant"])):
fv = time_domain_features(veh)
fo = time_domain_features(occ)
row = {"event_id": i, "label": labels[i]}
row.update({f"veh_{k}": v for k, v in fv.items()})
row.update({f"occ_{k}": v for k, v in fo.items()})
rows.append(row)
return pd.DataFrame(rows)
Two implementation details are worth calling out. First, the onset threshold (5% of peak) prevents pre-trigger noise from inflating impulse and energy. Second, jerk is computed on the raw signal; if your channel has sensor noise above ~2 kHz, low-pass filter at 1.5 kHz before computing jerk, otherwise the derivative amplifies noise and the feature becomes dominated by measurement artifacts rather than crash dynamics.
Discriminative Power: How to Judge Each Feature
With a labeled set of events (deploy = 1, no-deploy = 0), the practical question is which features actually separate the classes. Two cheap, robust checks are used here:
- Class separation ratio —
|mean_deploy − mean_nodeploy| / pooled_std. Values above ~1.0 indicate a feature worth keeping; below ~0.3 it is likely noise for this task. - Univariate AUC — rank-based area under the ROC curve for a single feature. AUC near 0.5 means no discrimination; above 0.85 is strong for a single time-domain feature.
Both are computed without any model training, so they give a fast read on feature quality before committing to a classifier. The snippet below assumes the feature table from above:
from sklearn.metrics import roc_auc_score
def separation_ratio(x, y):
x = np.asarray(x); y = np.asarray(y)
m1, m0 = x[y == 1].mean(), x[y == 0].mean()
s1, s0 = x[y == 1].std(ddof=1), x[y == 0].std(ddof=1)
pooled = np.sqrt((s1**2 + s0**2) / 2.0)
return abs(m1 - m0) / pooled if pooled > 0 else 0.0
feat_cols = [c for c in df.columns if c not in ("event_id", "label")]
report = []
for c in feat_cols:
report.append({
"feature": c,
"sep_ratio": separation_ratio(df[c], df["label"]),
"auc": roc_auc_score(df["label"], df[c]),
})
report = pd.DataFrame(report).sort_values("auc", ascending=False)
print(report.to_string(index=False))
Reading the Results
On a typical frontal-crash dataset, the expected pattern is that veh_peak, veh_impulse, and veh_energy carry the strongest separation, because deployment is fundamentally a severity decision. veh_rise_ms adds value for borderline events: a slow-ramp 30 g pulse may not deploy while a sharp 30 g pulse does. Occupant-channel features tend to be weaker and noisier — the occupant channel is downstream of the restraint system, so its signal is partly a consequence of deployment rather than a cause. Treat occupant features as secondary and check for leakage: if the occupant channel was recorded after deployment, it cannot be used as a pre-deployment predictor.
Verification Checklist
Before moving on, confirm the following on your own feature table:
- Every event has exactly 10 feature columns (5 per channel) plus
event_idandlabel. - No feature is computed over samples beyond the 30 ms decision window.
rise_msis positive and less than the window length for all events; a zero or negative value indicates an onset-detection bug.impulseandenergyare non-negative for a positive-polarity pulse; negative values mean the sign convention was violated.- The AUC report is sorted and reproducible — rerunning the script on the same table yields identical values.
If veh_jerk shows an AUC near 0.5 while peak and impulse are strong, the most common cause is unfiltered sensor noise dominating the derivative. Apply a 1.5 kHz low-pass filter and recompute; jerk should then track the sharpness of the leading edge rather than measurement noise.