Files
impakt/tests/test_real_mme.py
2026-04-10 14:37:34 -04:00

290 lines
9.6 KiB
Python

"""Tests against real ISO 13499 MME data.
These tests validate the reader against actual crash test data from
multiple labs (NHTSA/Calspan, BASt, UTAC, Volkswagen).
"""
from pathlib import Path
import numpy as np
import pytest
from impakt.channel.code import ChannelCode
from impakt.io.mme import MMEReader
MME_DATA_DIR = Path(__file__).parent / "mme_data"
# Skip all tests if real data directory is missing
pytestmark = pytest.mark.skipif(
not MME_DATA_DIR.exists(),
reason="Real MME test data not available",
)
class TestNHTSA3239:
"""NHTSA test 3239: 2000 Volkswagen Passat frontal barrier, 133 channels."""
TEST_DIR = MME_DATA_DIR / "3239"
def test_supports(self):
reader = MMEReader()
assert reader.supports(self.TEST_DIR)
def test_metadata(self):
reader = MMEReader()
meta = reader.metadata(self.TEST_DIR)
assert meta.test_number == "3239"
assert "CALSPAN" in meta.test_facility.upper()
assert meta.test_date is not None
assert meta.test_date.year == 1999
def test_read_all_channels(self):
reader = MMEReader()
data = reader.read(self.TEST_DIR)
assert data.test_id == "3239"
assert len(data) == 133
def test_channel_code_parsing(self):
reader = MMEReader()
data = reader.read(self.TEST_DIR)
# First channel: 11HEAD0000H3ACXP
ch = data.get("11HEAD0000H3ACXP")
assert ch.code.is_valid
assert ch.code.test_object == "11"
assert ch.code.main_location == "HEAD"
assert ch.code.fine_location == "0000"
assert ch.code.dummy_type == "H3"
assert ch.code.measurement == "AC"
assert ch.code.direction == "X"
assert ch.code.sense == "P"
def test_channel_data_properties(self):
reader = MMEReader()
data = reader.read(self.TEST_DIR)
ch = data.get("11HEAD0000H3ACXP")
assert ch.unit == "m/s²"
assert ch.sample_rate == 10000.0
assert ch.n_samples == 3312
assert len(ch.time) == 3312
# First sample time should be negative (pre-trigger)
assert ch.time[0] < 0
def test_channel_data_realistic(self):
reader = MMEReader()
data = reader.read(self.TEST_DIR)
ch = data.get("11HEAD0000H3ACXP")
# Head X accel in a frontal crash should have a significant negative peak
# (m/s² units, so a 40g crash = ~400 m/s²)
assert ch.peak > 100.0 # At least 10g peak
def test_auto_grouping(self):
reader = MMEReader()
data = reader.read(self.TEST_DIR)
groups = data.groups()
# Should have head accel group for driver (11) and rear passenger (13)
head_groups = [k for k in groups if "HEAD" in k and "AC" in k]
assert len(head_groups) >= 2 # 11HEAD and 13HEAD at minimum
def test_find_driver_head_channels(self):
reader = MMEReader()
data = reader.read(self.TEST_DIR)
head_channels = data.find("11HEAD0000H3AC*")
assert len(head_channels) == 3 # X, Y, Z
def test_resultant_computation(self):
reader = MMEReader()
data = reader.read(self.TEST_DIR)
head_channels = data.find("11HEAD0000H3AC*")
assert len(head_channels) == 3
# Compute resultant
from impakt.transform.resultant import resultant_from_channels
res = resultant_from_channels(*head_channels)
assert res.code.direction == "R"
assert res.peak > 0
def test_barrier_load_cells(self):
reader = MMEReader()
data = reader.read(self.TEST_DIR)
barrier_channels = data.find("B0FBAR*")
assert len(barrier_channels) > 0 # Should have load cell barrier data
def test_cfc_filter_on_real_data(self):
from impakt.transform.cfc import cfc_filter
reader = MMEReader()
data = reader.read(self.TEST_DIR)
ch = data.get("11HEAD0000H3ACXP")
filtered = cfc_filter(ch, 1000)
assert filtered.cfc_class == 1000
assert filtered.peak <= ch.peak
class TestBASt_AK3T02FO:
"""BASt test AK3T02FO: Frontal offset 40% deformable barrier, 97 channels."""
TEST_DIR = MME_DATA_DIR / "AK3T02FO"
def test_supports(self):
reader = MMEReader()
assert reader.supports(self.TEST_DIR)
def test_metadata(self):
reader = MMEReader()
meta = reader.metadata(self.TEST_DIR)
assert meta.test_number == "AK3T02FO"
assert "BASt" in meta.test_facility
def test_read_all_channels(self):
reader = MMEReader()
data = reader.read(self.TEST_DIR)
assert data.test_id == "AK3T02FO"
assert len(data) == 97
def test_multiple_occupants(self):
"""This test has driver (11), rear passenger (13), and child (14)."""
reader = MMEReader()
data = reader.read(self.TEST_DIR)
driver_chs = data.find("11*")
rear_chs = data.find("13*")
assert len(driver_chs) > 0
assert len(rear_chs) > 0
class TestBASt_AK3T02SI:
"""BASt test AK3T02SI: Side impact, 97 channels."""
TEST_DIR = MME_DATA_DIR / "AK3T02SI"
def test_supports(self):
reader = MMEReader()
assert reader.supports(self.TEST_DIR)
def test_read_all_channels(self):
reader = MMEReader()
data = reader.read(self.TEST_DIR)
assert len(data) > 0
class TestVW_Pedestrian:
"""Volkswagen test VW1FGS15: Pedestrian headform impactor, 10 channels."""
TEST_DIR = MME_DATA_DIR / "VW1FGS15"
def test_supports(self):
reader = MMEReader()
assert reader.supports(self.TEST_DIR)
def test_metadata(self):
reader = MMEReader()
meta = reader.metadata(self.TEST_DIR)
assert meta.test_date is not None
assert meta.test_date.year == 2006
def test_read_all_channels(self):
reader = MMEReader()
data = reader.read(self.TEST_DIR)
assert len(data) == 10
def test_pedestrian_channel_codes(self):
"""This test uses D0 (impactor) test object prefix."""
reader = MMEReader()
data = reader.read(self.TEST_DIR)
# All channels should start with D0
for ch in data:
assert ch.code.test_object == "D0"
def test_has_resultant_channel(self):
"""This test includes a pre-computed resultant (direction=R)."""
reader = MMEReader()
data = reader.read(self.TEST_DIR)
resultants = [ch for ch in data if ch.code.direction == "R"]
assert len(resultants) >= 1
class TestUTAC_98_7707:
"""UTAC test 98/7707: Vehicle-to-vehicle frontal, no channel data."""
TEST_DIR = MME_DATA_DIR / "98_7707"
def test_supports(self):
"""Has .mme file so should be supported even without channels."""
reader = MMEReader()
assert reader.supports(self.TEST_DIR)
def test_metadata(self):
reader = MMEReader()
meta = reader.metadata(self.TEST_DIR)
assert meta.test_number == "M5533" or meta.test_number == "98/7707"
assert meta.test_date is not None
assert meta.test_date.year == 1998
def test_read_empty_channels(self):
"""This test has no Channel/ directory — should load with 0 channels."""
reader = MMEReader()
data = reader.read(self.TEST_DIR)
assert len(data) == 0
class TestChannelCodeRealWorld:
"""Test ChannelCode parsing against all codes found in real data."""
@pytest.mark.parametrize(
"code,expected_meas,expected_dir",
[
("11HEAD0000H3ACXP", "AC", "X"),
("11HEAD0000H3ACYP", "AC", "Y"),
("11HEAD0000H3ACZP", "AC", "Z"),
("11NECKUP00H3FOXP", "FO", "X"),
("11NECKUP00H3FOZP", "FO", "Z"),
("11NECKUP00H3MOYP", "MO", "Y"),
("11CHST0000H3DSXP", "DS", "X"),
("11FEMRLE00H3FOZP", "FO", "Z"),
("11TIBILEUPH3MOXP", "MO", "X"),
("B0FBAR010100FOXP", "FO", "X"),
("D0HEAD0000PCACXA", "AC", "X"),
("D0HEAD0000PCACRA", "AC", "R"),
("D0HEAD0000PCDSXV", "DS", "X"),
("D0HEAD0000PCANXV", "AN", "X"),
("14HEAD0000P3ACXP", "AC", "X"),
],
)
def test_parse_real_codes(self, code, expected_meas, expected_dir):
parsed = ChannelCode.parse(code)
assert parsed.is_valid, f"Failed to parse: {code}"
assert parsed.measurement == expected_meas, (
f"{code}: expected meas={expected_meas}, got {parsed.measurement}"
)
assert parsed.direction == expected_dir, (
f"{code}: expected dir={expected_dir}, got {parsed.direction}"
)
@pytest.mark.parametrize(
"code,expected_dummy",
[
("11HEAD0000H3ACXP", "H3"),
("14HEAD0000P3ACXP", "P3"),
("D0HEAD0000PCACXA", "PC"),
],
)
def test_dummy_type_extraction(self, code, expected_dummy):
parsed = ChannelCode.parse(code)
assert parsed.dummy_type == expected_dummy
def test_group_key_consistency(self):
"""X/Y/Z channels with same prefix should share a group key."""
cx = ChannelCode.parse("11HEAD0000H3ACXP")
cy = ChannelCode.parse("11HEAD0000H3ACYP")
cz = ChannelCode.parse("11HEAD0000H3ACZP")
assert cx.group_key() == cy.group_key() == cz.group_key()
def test_group_key_separates_locations(self):
head = ChannelCode.parse("11HEAD0000H3ACXP")
chest = ChannelCode.parse("11CHST0000H3ACXP")
assert head.group_key() != chest.group_key()
def test_group_key_separates_occupants(self):
driver = ChannelCode.parse("11HEAD0000H3ACXP")
rear = ChannelCode.parse("13HEAD0000H3ACXP")
assert driver.group_key() != rear.group_key()