"""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()