2. Define Submarining Detection Metrics from Belt and Pelvis Kinematics

Why a Single Metric Is Not Enough

Submarining is a kinematic event, not a force event. The lap belt initially loads the anterior superior iliac spine (ASIS) region; when the belt slips superiorly past the iliac crest and compresses the abdomen, the pelvis has already begun rotating under the belt. Any detection scheme that relies on one signal alone will either fire late (belt force drop lags migration by tens of milliseconds) or fire falsely (pelvis rotation occurs in non-submarining pulses too). The practical approach is to define three independent, physically interpretable metrics, evaluate each on its own threshold, and then combine them with a persistence rule so that a single noisy frame cannot trigger a flag.

The three metrics used here are:

  • Belt-to-iliac-crest migration — the superior displacement of the lap-belt centerline relative to a pelvis-fixed iliac crest marker, measured along the pelvis vertical axis.
  • Pelvis rotation — the pitch angle of the pelvis about the lateral (Y) axis, referenced to the initial seated posture, positive in the direction of posterior pelvic tilt that accompanies belt ride-up.
  • Lap-belt force drop — the normalized reduction in lap-belt tension after its peak, computed as (F_peak − F(t)) / F_peak over a trailing window.

Defining the Metrics Quantitatively

All three metrics must be computed in a consistent reference frame. Define the pelvis local frame from three nodes: the sacrum (origin), the right ASIS, and the left ASIS. The pelvis vertical axis e_z points from the sacrum toward the midpoint of the two ASIS nodes; the lateral axis e_y points from left to right ASIS; e_x = e_y × e_z completes a right-handed frame. Migration is then the projection of the belt-center displacement onto e_z, which makes the metric insensitive to whole-body forward excursion.

Pelvis pitch is extracted from the same frame: θ = atan2(−e_x · g, e_z · g), where g is the global gravity unit vector. This gives a signed angle that is zero in the baseline posture and increases as the pelvis rotates under the belt. Using the frame rather than raw nodal coordinates avoids contaminating the angle with rigid-body translation of the occupant.

Belt force drop is computed from the lap-belt seatbelt element force output. Because belt force is noisy during the loading phase, the peak is taken as the maximum over the loading window, and the drop is evaluated only after the peak time. A trailing 10 ms mean is applied before the ratio is formed.

Reference conventions used throughout this case: global X = forward, Y = left, Z = up. Pelvis pitch is positive for posterior tilt (belt riding up the abdomen). Migration is positive when the belt moves toward the head relative to the iliac crest. All thresholds below assume a 50th-percentile male occupant model and a 3-point belt with a pretensioned retractor.

Threshold Selection and the Persistence Rule

Thresholds should be derived from the baseline run rather than assumed. Run the baseline model from Case 1, extract the three time histories, and record the maximum value each metric reaches before any visible belt penetration of the abdomen. Those pre-submarining maxima define the noise floor. Set each threshold at the noise floor plus a margin that reflects the metric's sensitivity:

MetricSymbolTypical pre-submarining max (example)Proposed thresholdRationale
Belt-to-iliac-crest migrationΔz_belt8 mm15 mmRide-up beyond the crest is geometrically irreversible past ~15 mm
Pelvis pitchθ_pelvis6°12°Posterior tilt of 12° corresponds to loss of ASIS purchase
Lap-belt force dropΔF/F_peak0.100.2525% unloading indicates the belt has slipped off the load path

These numbers are illustrative for a specific model and must be re-derived for any new occupant or belt configuration. The persistence rule is what makes the criteria robust: a metric is considered "active" only if it exceeds its threshold continuously for at least 5 ms. Submarining onset is flagged at the first time at which at least two of the three metrics are simultaneously active. Requiring two-of-three suppresses single-channel false positives (e.g., a force spike during pretensioner firing) while still catching genuine ride-up, which always produces migration plus either rotation or unloading.

Submarining Detection: Metric Extraction and Two-of-Three Decision FE Output Channels • Sacrum / ASIS node coords • Lap-belt centerline nodes • Seatbelt element force • Time step Δt = 0.1 ms Pelvis Local Frame e_z: sacrum → ASIS mid e_y: L-ASIS → R-ASIS e_x = e_y × e_z θ = atan2(−e_x·g, e_z·g) Δz_belt (migration) belt center · e_z − crest threshold 15 mm θ_pelvis (pitch) signed, posterior tilt + threshold 12° ΔF/F_peak (unload) (F_peak − F)/F_peak, 10 ms mean threshold 0.25 Persist ≥ 5 ms active = 1 else active = 0 Persist ≥ 5 ms active = 1 else active = 0 Persist ≥ 5 ms active = 1 else active = 0 Two-of-Three Decision sum(active) ≥ 2 → flag onset at t else → no submarining Output t_onset, active mask, peak values

Post-Processing Script

The script below reads three CSV exports (node coordinates, belt centerline, belt force) and produces a single onset time plus a per-frame active mask. It is self-contained: it defines its own frame construction, smoothing, persistence logic, and threshold constants, so it can be run directly against the baseline model output without depending on earlier chapters.

import numpy as np
import pandas as pd

# ---- Thresholds (re-derive per model; values are illustrative) ----
THRESH_MIGRATION_MM = 15.0
THRESH_PITCH_DEG    = 12.0
THRESH_FORCE_DROP   = 0.25
PERSIST_MS          = 5.0
DT_MS               = 0.1

def build_pelvis_frame(sacrum, asis_l, asis_r):
    """Return orthonormal pelvis axes (e_x, e_y, e_z) for each frame.
    sacrum, asis_l, asis_r: (N,3) arrays in global coords."""
    e_z = (asis_l + asis_r) / 2.0 - sacrum
    e_z /= np.linalg.norm(e_z, axis=1, keepdims=True)
    e_y = asis_r - asis_l
    e_y /= np.linalg.norm(e_y, axis=1, keepdims=True)
    e_x = np.cross(e_y, e_z)
    e_x /= np.linalg.norm(e_x, axis=1, keepdims=True)
    return e_x, e_y, e_z

def pelvis_pitch_deg(e_x, e_z, gravity=(0.0, 0.0, -1.0)):
    g = np.asarray(gravity, dtype=float)
    num = -np.einsum('ij,j->i', e_x, g)
    den =  np.einsum('ij,j->i', e_z, g)
    return np.degrees(np.arctan2(num, den))

def trailing_mean(x, n):
    return pd.Series(x).rolling(window=n, min_periods=1).mean().to_numpy()

def persist(mask, n_frames):
    """True only where mask has been continuously True for n_frames."""
    out = np.zeros_like(mask, dtype=bool)
    run = 0
    for i, m in enumerate(mask):
        run = run + 1 if m else 0
        out[i] = run >= n_frames
    return out

def detect_submarining(nodes_csv, belt_csv, force_csv):
    nodes = pd.read_csv(nodes_csv)
    belt  = pd.read_csv(belt_csv)
    force = pd.read_csv(force_csv)

    t = nodes['time_ms'].to_numpy()
    sacrum = nodes[['sac_x','sac_y','sac_z']].to_numpy()
    asis_l = nodes[['asisL_x','asisL_y','asisL_z']].to_numpy()
    asis_r = nodes[['asisR_x','asisR_y','asisR_z']].to_numpy()
    crest  = nodes[['crest_x','crest_y','crest_z']].to_numpy()
    belt_c = belt[['belt_x','belt_y','belt_z']].to_numpy()

    e_x, e_y, e_z = build_pelvis_frame(sacrum, asis_l, asis_r)

    # Metric 1: migration of belt center along pelvis vertical, relative to crest
    rel = belt_c - crest
    migration_mm = np.einsum('ij,ij->i', rel, e_z)

    # Metric 2: pelvis pitch
    pitch_deg = pelvis_pitch_deg(e_x, e_z)

    # Metric 3: normalized lap-belt force drop after peak
    F = force['lap_belt_N'].to_numpy()
    F_smooth = trailing_mean(F, int(10.0 / DT_MS))
    i_peak = int(np.argmax(F_smooth))
    F_peak = F_smooth[i_peak]
    drop = np.zeros_like(F_smooth)
    drop[i_peak:] = (F_peak - F_smooth[i_peak:]) / F_peak

    n_persist = int(round(PERSIST_MS / DT_MS))
    a_mig = persist(migration_mm > THRESH_MIGRATION_MM, n_persist)
    a_pit = persist(pitch_deg     > THRESH_PITCH_DEG,    n_persist)
    a_frc = persist(drop          > THRESH_FORCE_DROP,   n_persist)

    active_count = a_mig.astype(int) + a_pit.astype(int) + a_frc.astype(int)
    flagged = active_count >= 2

    onset_ms = float(t[np.argmax(flagged)]) if flagged.any() else None
    return pd.DataFrame({
        'time_ms': t,
        'migration_mm': migration_mm,
        'pitch_deg': pitch_deg,
        'force_drop': drop,
        'active_migration': a_mig,
        'active_pitch': a_pit,
        'active_force': a_frc,
        'active_count': active_count,
        'submarining_flag': flagged,
    }), onset_ms

if __name__ == "__main__":
    result, onset = detect_submarining(
        "nodes.csv", "belt_centerline.csv", "lap_belt_force.csv")
    result.to_csv("submarining_metrics.csv", index=False)
    print(f"Submarining onset: {onset} ms" if onset is not None
          else "No submarining detected")

Verifying the Flag Against the Baseline

Run the script on the Case 1 baseline output and inspect three things. First, confirm that onset_ms falls after the lap-belt force peak — if it precedes the peak, the force-drop channel is being evaluated in the wrong window and the peak index must be recomputed. Second, plot migration_mm and pitch_deg together; in a genuine submarining event the two curves rise within a few milliseconds of each other, whereas a false positive typically shows one channel rising while the other stays flat. Third, check the active mask around onset: a clean event shows active_count stepping from 1 to 2 and staying at 2, not oscillating between 1 and 2.

If the baseline produces no flag at all, the thresholds are too high for that model — lower them incrementally until the known ride-up is captured, then re-check that a non-submarining pulse (e.g., a pure frontal sled run without belt slip) still returns no flag. If the baseline flags immediately at t = 0, the persistence window is too short or the force-drop window includes the pretensioner spike; increase PERSIST_MS and start the drop evaluation after the pretensioner has fired.

Keep the three thresholds in a single configuration block at the top of the script. When you later parametrize pelvis geometry (Case 3) or recalibrate abdomen tissue (Case 4), only that block should change — the detection logic and persistence rule stay fixed, which is what makes onset times comparable across the study.
Do not compare onset times computed with different persistence windows or different force-peak definitions. A 5 ms persistence rule and a 10 ms rule can shift onset by more than 10 ms on the same signal, which is larger than many countermeasure effects you are trying to measure.