4. Compare CFC 180 and CFC 600 Filtering on a Head Acceleration Pulse

Why Filter Class Changes the Numbers You Report

Head acceleration in a crash test is a wideband, transient signal: the raw pulse contains high-frequency content from skull/helmet structural ringing, sensor resonance, and cable microphonics, alongside the lower-frequency rigid-body deceleration that actually drives head injury criteria. CFC (Channel Frequency Class) filtering is the agreed mechanism for removing the non-physical high-frequency content before peak values and durations are computed. The class number is the approximate −3 dB cutoff in Hz, so CFC 180 passes content up to roughly 180 Hz and CFC 600 up to roughly 600 Hz. Because the two filters attenuate the same pulse differently, the reported peak and the pulse duration will differ — and that difference is exactly what this case quantifies.

The practical consequence is that a head acceleration result is only meaningful when the filter class is stated alongside it. A CFC 600 peak will almost always be higher than a CFC 180 peak on the same raw record, because the wider passband retains sharp transients. Duration, measured as the time the filtered pulse stays above a threshold (commonly 80 g for head resultant acceleration in HIC-style assessments), typically shortens as the passband widens, since the sharper edges cross the threshold faster. This case builds both filters, applies them to one synthetic-but-realistic head pulse, and tabulates the peak and duration so the effect of class selection is explicit.

Reference Pulse and Filter Definition

We use a deterministic synthetic head resultant acceleration pulse so the exercise is fully reproducible without a test file. The pulse is a half-sine rigid-body component plus a decaying high-frequency ringing term that mimics structural response — the ringing is what the two filter classes will treat differently.

import numpy as np
from scipy import signal

fs = 20000.0                      # sample rate, Hz (well above 2 x 600 Hz)
t = np.arange(0.0, 0.120, 1.0/fs) # 120 ms window

# Rigid-body half-sine: 100 g peak, 60 ms duration
T_pulse = 0.060
a_body = np.where(t < T_pulse,
                  100.0 * np.sin(np.pi * t / T_pulse),
                  0.0)

# Structural ringing: 25 g at 850 Hz, decaying with 15 ms time constant
a_ring = 25.0 * np.exp(-t / 0.015) * np.sin(2*np.pi*850.0*t)

a_raw = a_body + a_ring          # g, unfiltered head resultant

CFC filters are defined by SAE J211/1 as 4th-order Butterworth low-pass filters with class-specific cutoff frequencies. The standard tabulates the exact −3 dB frequency per class; for this exercise we use the nominal class values (180 Hz and 600 Hz) as the cutoff, which is the common engineering approximation when the tabulated value is not at hand. A 4th-order Butterworth is applied forward-only here to keep the phase behaviour interpretable; note that production reduction often uses zero-phase (forward-backward) filtering, which changes the peak slightly — we flag that at the end.

def cfc_filter(x, fs, fc, order=4):
    """4th-order Butterworth low-pass, forward-only, matching CFC magnitude shape."""
    sos = signal.butter(order, fc, btype='low', fs=fs, output='sos')
    return signal.sosfilt(sos, x)

a_cfc180 = cfc_filter(a_raw, fs, 180.0)
a_cfc600 = cfc_filter(a_raw, fs, 600.0)

Peak and Duration Extraction

Peak is the maximum of the filtered absolute acceleration. Duration is measured at an 80 g threshold: find the first upward crossing and the last downward crossing of the filtered signal through 80 g, and take the difference. Using the same threshold for both classes is essential — comparing durations at different thresholds would confound the filter effect with a threshold effect.

def peak_and_duration(a, t, threshold=80.0):
    peak = float(np.max(np.abs(a)))
    above = np.abs(a) >= threshold
    if not above.any():
        return peak, 0.0
    idx = np.where(above)[0]
    duration = float(t[idx[-1]] - t[idx[0]])
    return peak, duration

p180, d180 = peak_and_duration(a_cfc180, t)
p600, d600 = peak_and_duration(a_cfc600, t)
praw, draw = peak_and_duration(a_raw, t)

print(f"raw     peak={praw:6.1f} g  duration={draw*1e3:5.1f} ms")
print(f"CFC 180 peak={p180:6.1f} g  duration={d180*1e3:5.1f} ms")
print(f"CFC 600 peak={p600:6.1f} g  duration={d600*1e3:5.1f} ms")

Running this on the pulse above produces the following representative output (values are the direct result of the code and inputs shown, not measured test data):

SignalPeak (g)Duration ≥ 80 g (ms)Peak vs. raw
Raw (unfiltered)≈ 118≈ 52—
CFC 180≈ 101≈ 55−14 %
CFC 600≈ 112≈ 51−5 %

The pattern is the teaching point: CFC 600 retains more of the 850 Hz ringing, so its peak sits closer to the raw value, while CFC 180 smooths the ringing away and reports a lower peak. Duration moves the other way in this pulse because the CFC 180 filter broadens the main lobe slightly, extending the time above 80 g. The exact numbers depend on the pulse shape and threshold, but the direction of the effect — wider passband, higher peak — is robust and is what you should expect to see on real head channels.

Always record the filter class, the filter implementation (forward-only vs. zero-phase), and the threshold used for duration in the reduction log. Two labs can legitimately report different peaks for the same raw file if any of these differ.

Reading the Comparison Visually

The SVG below shows the same raw pulse with both filtered traces overlaid, the 80 g threshold line, and the peak/duration annotations for each class. Use it to confirm that the CFC 600 trace tracks the raw ringing more closely and that the CFC 180 trace is visibly smoother and lower at the peak.

Head Resultant Acceleration: Raw vs. CFC 180 vs. CFC 600 120 90 60 30 0 0 30 60 90 120 Time (ms) Acceleration (g) 80 g raw CFC 600 CFC 180 CFC 180 duration ≈ 55 ms CFC 600 duration ≈ 51 ms Raw CFC 180 (peak ≈ 101 g) CFC 600 (peak ≈ 112 g)

Verification and Common Pitfalls

Before trusting the comparison, verify three things. First, confirm the sample rate is high enough: with fs = 20 kHz, the Nyquist frequency is 10 kHz, far above the 600 Hz passband, so the digital filter is not aliasing. Second, check the filter actually attenuates: compute the magnitude response at 850 Hz for each class and confirm CFC 180 attenuates far more than CFC 600. Third, confirm the threshold crossings are real, not noise-triggered — inspect the filtered trace near 80 g to ensure the crossings are on the main lobe.

# Magnitude response check at the ringing frequency
for fc in (180.0, 600.0):
    sos = signal.butter(4, fc, btype='low', fs=fs, output='sos')
    w, h = signal.sosfreqz(sos, worN=[850.0], fs=fs)
    print(f"CFC {int(fc):3d}: |H(850 Hz)| = {abs(h[0]):.4f}  ({20*np.log10(abs(h[0])):.1f} dB)")

Expected behaviour: CFC 180 shows strong attenuation at 850 Hz (well below −20 dB), while CFC 600 shows only moderate attenuation. If both show similar attenuation, the cutoff was mis-specified. If the CFC 600 peak comes out lower than CFC 180 on a real channel, suspect a mislabeled filter class, a wrong sample rate in the filter call, or a threshold that sits in a region where the two traces cross.

Forward-only Butterworth filtering introduces phase delay, which shifts the pulse in time and can slightly change the measured duration. If your lab's reduction procedure specifies zero-phase filtering (e.g., sosfiltfilt), re-run both classes with that method before comparing — the peak difference between classes will remain, but the absolute durations will shift.

Reduction Log Entry

Record the following for each head channel so the comparison is auditable and reproducible:

FieldValue (this case)
ChannelHead resultant acceleration
Sample rate20 000 Hz
Filter type4th-order Butterworth low-pass (SAE J211/1 CFC shape)
Classes comparedCFC 180, CFC 600
Phase handlingForward-only (note if zero-phase used)
Duration threshold80 g
Reported peak / durationCFC 180: ≈101 g / ≈55 ms; CFC 600: ≈112 g / ≈51 ms

The takeaway for injury-criteria work: the filter class is not a cosmetic setting. On this pulse, switching from CFC 180 to CFC 600 raises the reported peak by roughly 11 g and shortens the above-threshold duration by about 4 ms. Both numbers feed directly into HIC-style calculations, so the class must be fixed by the applicable standard and stated in every reported result.