4. CFC 60 Filter Implementation and Validation

CFC 60 Filter Implementation and Validation

Channel Frequency Class 60 (CFC 60) is the standard low-pass characteristic specified by SAE J211/1 and ISO 6487 for crash acceleration channels whose meaningful content lies below roughly 60 Hz. The class is defined not by a single cutoff frequency but by a tolerance envelope on the magnitude response: the filter must pass the low-frequency band with near-unity gain, cross the −3 dB point inside a defined window, and roll off steeply enough to suppress high-frequency content that would otherwise inflate or distort the derived injury metrics. This case implements a CFC 60 filter, applies it to a representative crash acceleration signal, and validates the result against an independent reference implementation.

What the CFC 60 Specification Actually Constrains

The CFC definition is a frequency-domain mask, not a prescription of a particular filter topology. Any implementation that stays inside the mask is compliant. The practical constraints that matter during implementation are:

  • Passband gain: approximately 1.0 (0 dB) well below the transition band, so that peak acceleration amplitude is preserved.
  • −3 dB crossing: located within the tolerance window around the nominal class frequency, not exactly at 60 Hz.
  • Stopband attenuation: the response must fall below the lower tolerance boundary at higher frequencies, which in practice requires a filter order of at least 4 (two cascaded second-order sections) for CFC 60.
  • Phase behaviour: SAE J211/1 permits a defined phase characteristic; a causal IIR filter is acceptable, but the phase response must be consistent between the device under test and the reference used for validation.

Because the mask is defined on the magnitude response, the most reliable validation strategy is to compare the implemented filter's frequency response against the mask, and then compare the time-domain filtered output against a reference implementation that is known to be compliant.

The CFC 60 class is frequently confused with a simple 60 Hz Butterworth low-pass. A Butterworth response of sufficient order will usually fit inside the mask, but the order and the exact −3 dB placement must be checked against the tolerance boundaries rather than assumed.

Implementation

The implementation below uses a fourth-order Butterworth low-pass as the CFC 60 approximation, applied with sosfilt (second-order sections) for numerical stability. The −3 dB point is placed at the class frequency, and the order is chosen so that the response stays inside the CFC 60 envelope. The code is self-contained: it synthesises a representative crash pulse, designs the filter, applies it, and runs the validation checks.

import numpy as np
from scipy import signal

# ---------------------------------------------------------------
# 1. Representative crash acceleration signal (example data)
# ---------------------------------------------------------------
fs = 10000.0                      # sampling rate [Hz]
t = np.arange(0.0, 0.30, 1.0 / fs)

# Main impact pulse: half-sine of 12 ms, peak 800 m/s^2
pulse = np.where(
    (t >= 0.05) & (t < 0.062),
    800.0 * np.sin(np.pi * (t - 0.05) / 0.012),
    0.0,
)
# Secondary structural ringing at ~180 Hz plus broadband noise
ringing = 60.0 * np.sin(2 * np.pi * 180.0 * t) * np.exp(-((t - 0.062) / 0.02) ** 2)
rng = np.random.default_rng(7)
noise = rng.normal(0.0, 8.0, t.size)
a_raw = pulse + ringing + noise

# ---------------------------------------------------------------
# 2. CFC 60 filter design (4th-order Butterworth, -3 dB at 60 Hz)
# ---------------------------------------------------------------
fc = 60.0
order = 4
sos = signal.butter(order, fc, btype="lowpass", fs=fs, output="sos")

# ---------------------------------------------------------------
# 3. Apply the filter
# ---------------------------------------------------------------
a_cfc60 = signal.sosfilt(sos, a_raw)

# ---------------------------------------------------------------
# 4. Validation against the CFC 60 tolerance envelope
# ---------------------------------------------------------------
w, h = signal.sosfreqz(sos, worN=4096, fs=fs)
mag_db = 20 * np.log10(np.abs(h) + 1e-12)

# Locate the -3 dB crossing
idx = np.argmin(np.abs(mag_db + 3.0))
f_minus3 = w[idx]

# Passband gain check (well below transition band)
passband = mag_db[w <= 10.0]
gain_dev = np.max(np.abs(passband))

# Stopband attenuation check (well above transition band)
stopband = mag_db[w >= 300.0]
max_stop = np.max(stopband)

print(f"-3 dB crossing      : {f_minus3:8.2f} Hz")
print(f"Passband max dev    : {gain_dev:8.3f} dB")
print(f"Stopband max level  : {max_stop:8.2f} dB")

# Peak preservation on the filtered signal
print(f"Raw peak            : {a_raw.max():8.2f} m/s^2")
print(f"CFC 60 peak         : {a_cfc60.max():8.2f} m/s^2")

Running this produces a −3 dB crossing at approximately 60 Hz, a passband deviation well under 0.1 dB below 10 Hz, and a stopband level below −40 dB above 300 Hz. The filtered peak is slightly lower than the raw peak because the 180 Hz ringing and broadband noise are removed; the underlying impact pulse amplitude is preserved. These are expected outcomes of the design, not measured results from a specific device.

Validating Against a Reference Implementation

Frequency-response compliance alone is necessary but not sufficient. The second validation layer compares the time-domain output against an independent reference. A practical reference is a direct-form implementation of the same transfer function, or a filter designed by a different toolchain (for example, a state-space realisation or a manually constructed biquad cascade). The comparison should be done on the same input, with the same initial conditions, and the residual should be at the numerical-noise level.

# Independent reference: same poles/zeros, direct-form II transposed
b, a = signal.butter(order, fc, btype="lowpass", fs=fs)
a_ref = signal.lfilter(b, a, a_raw)

residual = a_cfc60 - a_ref
print(f"Max |residual|      : {np.max(np.abs(residual)):8.3e} m/s^2")
print(f"RMS residual        : {np.sqrt(np.mean(residual**2)):8.3e} m/s^2")

For a correctly implemented filter, the maximum residual should be at the level of floating-point round-off relative to the signal amplitude — typically several orders of magnitude below the peak. A residual that is a visible fraction of the peak indicates a mismatch in coefficients, order, or initial conditions, and the implementation should not be signed off.

Do not validate by comparing against a filter with a different phase characteristic (for example, a zero-phase filtfilt result). The CFC 60 specification is a causal characteristic; a zero-phase reference will produce a large residual that reflects the phase difference, not an implementation error.

Reading the Validation Result

The figure below summarises the two validation layers side by side: the magnitude response against the CFC 60 tolerance envelope, and the time-domain overlay of the raw signal, the implemented filter output, and the reference output. The key judgement points are the position of the −3 dB crossing inside the tolerance window, the flatness of the passband, the stopband level, and the near-zero residual between the two time-domain traces.

Magnitude response vs. CFC 60 envelope 0 dB -20 dB -40 dB 1 10 100 1000 Frequency [Hz] (log) -3 dB @ ~60 Hz CFC 60 tolerance Implemented filter Time-domain overlay and residual peak 0 0 0.15 0.30 Time [s] Residual (filter − reference) ≈ round-off level Raw CFC 60 output Reference

Failure Symptoms and Troubleshooting

When the validation does not pass, the symptom usually points to a specific cause. The table below maps the most common observations to their likely root cause and the corrective action.

ObservationLikely causeCorrective action
−3 dB crossing far from 60 HzWrong fc or fs passed to the design callConfirm fs matches the actual sampling rate of the signal; re-check fc units
Passband droop visible below 10 HzFilter order too high for the chosen cutoff, or an unintended high-pass stage in the chainReduce order or verify no other filter is applied before CFC 60
Stopband level above the envelopeOrder too low (e.g. second-order Butterworth)Increase to fourth order or cascade two biquads
Large time-domain residual vs. referenceDifferent order, cutoff, or initial conditions between implementationsAlign coefficients and initial conditions; compare sos arrays directly
Residual grows over timeNumerical instability from direct-form high-order realisationUse second-order sections (sosfilt) instead of a single high-order transfer function
Peak amplitude reduced more than expectedLegitimate removal of high-frequency content, or cutoff set too lowCompare against the unfiltered pulse envelope; verify the cutoff is at the class frequency

Sign-Off Checklist

A CFC 60 implementation is ready for use when all of the following hold on the target signal and sampling rate:

  • The magnitude response stays inside the CFC 60 tolerance envelope across the full frequency range, with the −3 dB crossing inside the specified window.
  • Passband deviation below 10 Hz is negligible (well under 0.1 dB in the example above).
  • Stopband attenuation above 300 Hz is at least 40 dB below the passband.
  • The time-domain residual against an independent reference implementation is at round-off level relative to the signal peak.
  • The filtered peak acceleration is consistent with the physical impact pulse, with high-frequency ringing and noise removed rather than the pulse itself attenuated.

Once these checks pass, the filtered channel can be used for downstream injury-curve computation with confidence that the CFC 60 characteristic has been applied correctly and consistently with the reference used elsewhere in the analysis chain.