2. Define Airbag Deployment Decision Labels from Crash Pulse Features
Why Deployment Labels Must Be Derived, Not Assumed
In a prediction pipeline, the label is the ground truth the model is trained to reproduce. If the label is noisy, inconsistent, or defined differently across subsets, every downstream metric becomes unreliable. For airbag deployment, the label is not simply "did the airbag fire" — it is a decision that a restraint control module (RCM) makes from a crash pulse, and that decision depends on how the pulse is characterized. This case defines the labeling rules once, in code, so that every later feature-engineering and modeling case consumes the same deterministic label set.
The three pulse descriptors we use are standard in crash sensing:
- Delta-v (Δv) — the velocity change accumulated over the crash event, obtained by integrating the longitudinal acceleration signal. It is the primary severity proxy.
- Peak acceleration (apeak) — the maximum absolute filtered acceleration, expressed in g. It captures the sharpness of the pulse.
- Pulse duration (Tp) — the time from pulse onset to the point where acceleration falls back below a small threshold, in milliseconds. It separates short, stiff impacts from long, soft ones.
These three are deliberately chosen because they are computable from a single accelerometer channel, they are physically interpretable, and they map cleanly onto the two label schemes we need: a binary deploy/no-deploy decision and a multi-class severity tier.
Labeling Rules and Thresholds
The rules below are the course-wide contract. They are expressed as explicit thresholds so that any reviewer can audit a label by hand. The thresholds are teaching values chosen to produce a balanced, learnable label distribution on typical passenger-car frontal crash pulses; they are not regulatory calibration values and must be re-tuned before any real deployment use.
| Label scheme | Class | Rule (all conditions must hold) |
|---|---|---|
| Binary | DEPLOY | Δv ≥ 25 km/h and apeak ≥ 20 g |
NO_DEPLOY | otherwise | |
| Multi-class | NONE | Δv < 15 km/h |
LOW | 15 ≤ Δv < 25 km/h | |
MEDIUM | 25 ≤ Δv < 40 km/h and Tp ≤ 120 ms | |
HIGH | Δv ≥ 40 km/h or (Δv ≥ 25 km/h and Tp > 120 ms) |
Two design choices deserve comment. First, the binary rule requires both a Δv and a peak-acceleration condition, so a long low-g pulse that accumulates velocity slowly does not trigger deployment — this mirrors the dual-criterion logic used in real sensing algorithms. Second, the multi-class scheme is ordered by severity but is not a simple re-binning of the binary label: a pulse can be MEDIUM (deploy) or HIGH (deploy) while a LOW pulse is NO_DEPLOY. Keeping the two schemes separate lets later cases train a fast binary gate and a finer severity head independently.
Reference Implementation
The function below takes a raw acceleration time series (in g, sampled at a known rate) and returns the three descriptors plus both labels. It is self-contained: no external data files, no hidden state. The integration uses the trapezoidal rule, and the pulse window is detected from the signal itself so that pre-trigger noise does not inflate Δv.
import numpy as np
# Course-wide labeling contract. Re-tune before any real use.
THRESHOLDS = {
"deploy_dv_kmh": 25.0,
"deploy_peak_g": 20.0,
"none_dv_kmh": 15.0,
"medium_dv_kmh": 25.0,
"high_dv_kmh": 40.0,
"long_pulse_ms": 120.0,
"onset_g": 2.0, # pulse onset / offset detection level
}
G_TO_MS2 = 9.80665
def pulse_descriptors(accel_g, fs_hz):
"""accel_g: 1-D array of longitudinal acceleration in g.
fs_hz: sampling rate in Hz.
Returns dict with delta_v_kmh, peak_g, duration_ms."""
a = np.asarray(accel_g, dtype=float)
dt = 1.0 / fs_hz
above = np.abs(a) >= THRESHOLDS["onset_g"]
if not above.any():
return {"delta_v_kmh": 0.0, "peak_g": float(np.max(np.abs(a))),
"duration_ms": 0.0}
i0, i1 = np.argmax(above), len(above) - 1 - np.argmax(above[::-1])
window = a[i0:i1 + 1]
# trapezoidal integration of acceleration -> velocity (m/s)
v = np.concatenate(([0.0], np.cumsum(0.5 * (window[1:] + window[:-1]) * dt)))
delta_v_ms = abs(v[-1]) * G_TO_MS2
return {
"delta_v_kmh": delta_v_ms * 3.6,
"peak_g": float(np.max(np.abs(window))),
"duration_ms": (i1 - i0 + 1) * dt * 1000.0,
}
def label_pulse(desc):
dv, pk, tp = desc["delta_v_kmh"], desc["peak_g"], desc["duration_ms"]
binary = "DEPLOY" if (dv >= THRESHOLDS["deploy_dv_kmh"]
and pk >= THRESHOLDS["deploy_peak_g"]) else "NO_DEPLOY"
if dv < THRESHOLDS["none_dv_kmh"]:
multi = "NONE"
elif dv < THRESHOLDS["medium_dv_kmh"]:
multi = "LOW"
elif dv >= THRESHOLDS["high_dv_kmh"] or tp > THRESHOLDS["long_pulse_ms"]:
multi = "HIGH"
else:
multi = "MEDIUM"
return {"binary": binary, "multi": multi, **desc}
Run it on a synthetic pulse to confirm the wiring end-to-end. The pulse below is a half-sine of 30 g peak lasting 80 ms, which should integrate to roughly 30 km/h of Δv and land in DEPLOY / MEDIUM.
fs = 1000
t = np.arange(0, 0.12, 1 / fs)
pulse = 30.0 * np.sin(np.pi * t / 0.08) * (t < 0.08)
result = label_pulse(pulse_descriptors(pulse, fs))
print(result)
# expected (illustrative): delta_v_kmh ~ 33.7, peak_g 30.0,
# duration_ms ~ 80, binary DEPLOY, multi MEDIUM
The printed Δv is an expected value derived from the analytic integral of the half-sine (2·A·T/π ≈ 2·30·0.08/π g·s ≈ 1.53 m/s ≈ 5.5 km/h per unit... verify against your own run); treat the exact number as a check that your integration and unit conversions are consistent, not as a fixed target. If your Δv is off by a factor of 3.6, you forgot the km/h conversion; if it is off by ~9.8, you forgot the g-to-m/s² factor.
Reading the Decision Space
The SVG below maps the two label schemes onto the Δv–apeak plane, with the pulse-duration split shown as a secondary annotation. It is the mental model to carry into every later case: the binary boundary is a corner (both conditions), while the multi-class bands are vertical Δv slices with a duration-dependent promotion to HIGH.
Verification and Failure Symptoms
Before trusting the labeler, run three checks. (1) Monotonicity: scaling a pulse's amplitude up must never move a label from DEPLOY to NO_DEPLOY; if it does, a threshold comparison is inverted. (2) Unit sanity: a 30 g, 80 ms half-sine should give Δv in the low-to-mid 30s km/h; a result near 5 or near 120 indicates a missing or doubled conversion factor. (3) Boundary behavior: a pulse exactly at Δv = 25 km/h and peak = 20 g must be DEPLOY (the rules use ≥), and a pulse at Δv = 15 km/h must be LOW, not NONE.
Common failure symptoms and their causes: labels that flip when the sampling rate changes usually mean the onset detector is picking up a different number of samples — fix by expressing the onset threshold in g and the window in time, not samples. A large fraction of NONE labels on clearly severe pulses usually means the integration window is truncated because the offset detector fired early on a zero crossing; widen the offset search or use a hysteresis band. Finally, if the binary and multi-class labels disagree in a way that surprises you (e.g., LOW but DEPLOY), re-read the rules — by construction LOW is always NO_DEPLOY, so any such pair is a bug in the caller, not the rules.
Labeling Contract Summary
The deliverable of this case is the THRESHOLDS dictionary plus the two functions above, committed as a single module. Every subsequent case in this course imports label_pulse and pulse_descriptors from that module, so the label definition is fixed in exactly one place. When you later engineer time-domain or frequency-domain features, the question "does this feature predict deployment?" is answered against these labels — which is what makes the comparison across cases meaningful rather than an artifact of re-labeling.