3. Signal conditioning and preprocessing

Raw transducer output is almost never ready for injury metrics. A piezoelectric accelerometer may ride on a few hundred millivolts of DC offset, a strain-gauge load cell bridge may drift with temperature, and both channels may be logged in units that are inconvenient for the biomechanical model downstream. Before any filtering, integration, or injury criterion (HIC, NIC, chest deflection, femur force) is computed, the signal must be conditioned: offsets removed, drift suppressed, baselines zeroed, and units converted to a consistent physical reference. This chapter treats those four operations as a single, ordered preprocessing pipeline and shows how to implement each step in Python with explicit, auditable parameters.

Why order matters: the preprocessing chain

The four operations are not interchangeable. Offset correction removes a constant bias; drift removal targets a slow, time-varying bias; baseline zeroing re-references the signal to a defined pre-event state; unit conversion scales the amplitude into SI or the model's native units. Applying them out of order produces artifacts: zeroing a baseline before removing drift leaves a residual ramp, and converting units before offset correction propagates a scaled bias into every downstream calculation. The recommended sequence is unit conversion → offset correction → drift removal → baseline zeroing, with the caveat that unit conversion can be deferred if the offset is expressed in the same raw units. The figure below shows the chain and the signal state at each stage.

Signal Conditioning Chain: Raw Transducer Output to Conditioned Channel Raw input mV or raw counts Unit conversion → m/s², N Offset correction remove constant bias Drift removal high-pass / detrend Baseline zeroing pre-event reference raw: bias + drift + event scaled to m/s² bias removed, drift remains drift removed zeroed to pre-event mean time → amplitude (arbitrary) Each stage is reversible and logged; parameters (bias, drift model, baseline window) are stored with the channel.

Unit conversion: from raw counts to physical units

Accelerometers and load cells rarely output physical units directly. A MEMS accelerometer may report signed 16-bit counts with a sensitivity of, say, 0.061 mg/LSB; a piezoelectric accelerometer may output mV/g through a charge amplifier; a strain-gauge load cell may output mV/V that must be scaled by excitation voltage and rated output. The conversion must be explicit and documented, because a silent factor-of-9.81 error between g and m/s² is one of the most common causes of implausible injury numbers.

For a digital accelerometer, the conversion is a single scale factor:

import numpy as np

# Example parameters (illustrative, not measured)
SENSITIVITY_MG_PER_LSB = 0.061      # mg per least-significant bit
G = 9.80665                          # m/s^2 per g

def counts_to_ms2(counts, sensitivity_mg_per_lsb=SENSITIVITY_MG_PER_LSB):
    """Convert signed ADC counts to acceleration in m/s^2."""
    accel_g = counts * (sensitivity_mg_per_lsb / 1000.0)
    return accel_g * G

raw_counts = np.array([0, 164, -164, 820], dtype=float)
print(counts_to_ms2(raw_counts))
# [ 0.       0.980665 -0.980665  4.903325]  (illustrative output)

For an analog load cell, the chain is: bridge output (mV) → divide by excitation (V) → divide by rated output (mV/V) → multiply by rated capacity (N). If the DAQ already reports volts, the excitation and rated output collapse into a single calibration factor in N/V. Always record which stage the calibration factor applies to; mixing a mV/V factor with a volt-domain signal is a silent scaling error.

Offset correction: removing the constant bias

Offset (DC bias) is a constant added to the true signal. It arises from amplifier offset, ADC mid-scale, or sensor zero-g output. The simplest correction subtracts the mean of a known quiescent window — a segment where the true signal is expected to be zero (pre-impact, unloaded, or a dedicated tare period). For a load cell, the tare window is the unloaded state before the event; for an accelerometer, it is the stationary pre-event interval.

def remove_offset(signal, tare_slice):
    """Subtract the mean of a quiescent window (tare_slice) from the signal."""
    bias = np.mean(signal[tare_slice])
    return signal - bias, bias

# Example: first 200 samples are the stationary pre-event window
conditioned, bias = remove_offset(accel_ms2, slice(0, 200))
print(f"estimated bias = {bias:.4f} m/s^2")

Two cautions apply. First, the tare window must genuinely be quiescent; if it contains the event onset, the bias estimate is contaminated and the baseline is pulled toward the event. Second, for a load cell the tare must be taken with the same mechanical setup as the test — a preload or fixture change invalidates the tare. When no quiescent window exists, use a robust estimator such as the median of the lowest-variance segment rather than the global mean.

Drift removal: suppressing slow baseline wander

Drift is a slow, time-varying bias, typically from thermal effects in strain-gauge bridges or charge leakage in piezoelectric channels. Unlike offset, it cannot be removed by a single subtraction. Two practical approaches dominate:

  • High-pass filtering with a cutoff well below the event's frequency content. For crash pulses, the event energy is concentrated between roughly 5 Hz and 1 kHz, so a 0.5–1 Hz Butterworth high-pass removes drift while preserving the pulse. Use a zero-phase filter (filtfilt) to avoid phase distortion that would shift the pulse timing.
  • Polynomial detrending over a window that excludes the event, fitting a low-order polynomial to the pre- and post-event baseline and subtracting it. This is preferable when the drift is monotonic and the event occupies a short fraction of the record.
from scipy.signal import butter, filtfilt

def highpass_drift_removal(signal, fs, cutoff_hz=0.5, order=2):
    """Zero-phase Butterworth high-pass to remove slow baseline drift."""
    b, a = butter(order, cutoff_hz / (fs / 2.0), btype="high")
    return filtfilt(b, a, signal)

fs = 10000.0  # Hz, illustrative
accel_drift_removed = highpass_drift_removal(conditioned, fs, cutoff_hz=0.5)

Choose the cutoff by inspecting the event spectrum, not by habit. A cutoff too high attenuates the leading edge of the crash pulse and biases HIC downward; a cutoff too low leaves visible baseline wander that corrupts integration into velocity and displacement. Verify by checking that the pre-event baseline is flat and that the pulse peak is unchanged relative to the unfiltered record.

Baseline zeroing: referencing to the pre-event state

Baseline zeroing re-references the signal so that the pre-event mean is exactly zero. It is the final step and the one that defines the reference for integration. For acceleration, a non-zero baseline integrates into a spurious velocity ramp; for a load cell, a non-zero baseline produces a false static force. The operation is a second, smaller offset correction applied after drift removal, using a defined pre-event window:

def zero_baseline(signal, baseline_slice):
    """Re-reference signal so the pre-event mean is zero."""
    ref = np.mean(signal[baseline_slice])
    return signal - ref, ref

accel_final, ref = zero_baseline(accel_drift_removed, slice(0, 200))
print(f"baseline reference = {ref:.5f} m/s^2")

The baseline window must be documented and consistent across channels. A common convention is 50–100 ms before the trigger, provided the channel is stationary in that interval. If the pre-event window is noisy, report the residual standard deviation as a quality metric; a large residual indicates that drift removal was insufficient or that the window is not quiescent.

Putting it together: a reproducible preprocessing function

The following function composes the four stages in the recommended order and returns both the conditioned signal and a metadata dictionary. The metadata is the audit trail: it records the calibration factor, estimated bias, drift cutoff, and baseline reference so that any downstream injury metric can be traced back to its preprocessing assumptions.

def preprocess_channel(raw, fs, cal_factor, tare_slice, baseline_slice,
                       drift_cutoff_hz=0.5):
    """Full conditioning chain: convert, offset, drift, zero."""
    physical = raw * cal_factor
    offset_removed, bias = remove_offset(physical, tare_slice)
    drift_removed = highpass_drift_removal(offset_removed, fs, drift_cutoff_hz)
    final, ref = zero_baseline(drift_removed, baseline_slice)
    meta = {
        "cal_factor": cal_factor,
        "estimated_bias": float(bias),
        "drift_cutoff_hz": drift_cutoff_hz,
        "baseline_reference": float(ref),
        "baseline_residual_std": float(np.std(final[baseline_slice])),
    }
    return final, meta

Run this on each channel independently, then verify three things before proceeding: the pre-event baseline is flat and near zero, the event peak is preserved relative to the raw record, and the baseline residual standard deviation is small compared with the event amplitude. If any check fails, revisit the tare window, the drift cutoff, or the calibration factor — in that order, since a wrong calibration factor masquerades as an offset problem.

Keep the raw channel untouched and store the preprocessing metadata alongside the conditioned signal. Injury calculations are only as defensible as the preprocessing assumptions behind them, and a reviewer will ask for the bias estimate, the drift cutoff, and the baseline window.
Do not apply baseline zeroing before drift removal. Zeroing a drifting signal leaves a residual ramp that integrates into a false velocity, and the error grows with record length. Likewise, never convert units after offset correction unless the offset is expressed in the same raw units — a scaled bias is still a bias, but its magnitude is no longer interpretable.
StageOperationKey parameterFailure symptom if wrong
Unit conversionScale raw counts or volts to m/s² or NCalibration factor (mg/LSB, N/V)Implausible peak magnitude; factor-of-9.81 error
Offset correctionSubtract quiescent-window meanTare windowBaseline pulled toward event; biased peak
Drift removalZero-phase high-pass or detrendCutoff frequencyAttenuated pulse edge or residual wander
Baseline zeroingRe-reference to pre-event meanBaseline windowSpurious velocity ramp or false static force

With the channel conditioned and its metadata recorded, the signal is ready for the filtering and integration steps that follow. The discipline established here — explicit parameters, documented windows, and verification against the raw record — is what separates a defensible injury calculation from a plausible-looking number.