Files
impakt/README.md
2026-04-11 15:20:27 -04:00

18 KiB

Impakt

Crash test data analysis, visualization, and reporting.

Impakt is a modular, scriptable Python toolkit for working with automotive crash test data. It reads ISO 13499 MME data natively, provides non-destructive signal processing, interactive web-based visualization, injury criteria calculation, and protocol-compliant report generation for Euro NCAP, US NCAP, and IIHS.

Quick Start

# Install
uv sync --dev

# Run tests (258 tests, 70% coverage)
uv run pytest tests/

# View test metadata
uv run impakt info tests/mme_data/3239

# Launch web UI
uv run impakt serve tests/mme_data/3239

# Python scripting
uv run python -c "
from impakt import Session
s = Session.open('tests/mme_data/3239')
print(s)  # Session(3239, 133 channels)

# Fluent transforms
ch = s.channel('11HEAD0000H3ACXP').transform.cfc(1000).transform.y_align()
print(f'Peak: {ch.peak:.1f} {ch.unit}')

# One-call protocol evaluation
result = s.evaluate('euro_ncap')
print(f'{result.stars} stars ({result.percentage:.0f}%)')
"

See docs/STATUS.md for detailed project state, known issues, and next steps.


Contents


Design Principles

  1. Immutable raw data. Original MME files are never modified. All state — sessions, configuration, cached results — lives in a .impakt/ subfolder alongside the test data.
  2. Non-destructive transforms. Filtering, alignment, and math operations produce new channel views via TransformChain. The chain is serializable and reproducible.
  3. Scriptable first. Every operation available in the UI is accessible through a Python API. The web UI calls the same Session and PlotEngine used by scripts.
  4. Layered configuration. All behavior is configurable via YAML files at three levels: package defaults, user defaults (~/.impakt/), and per-test session overrides (.impakt/).
  5. Template-driven workflow. Reusable templates define channel selections, filter chains, corridors, and report configs. Sessions bind templates to specific test data.
  6. Protocol-aware. Built-in Euro NCAP, US NCAP, and IIHS scoring with versioned YAML threshold files.
  7. Plugin-extensible. Custom readers, transforms, criteria, and protocols registered via entry points, directory scanning, or API.

Architecture Overview

graph TB
    subgraph "Data Layer"
        IO[impakt.io]
        CH[impakt.channel]
    end

    subgraph "Processing Layer"
        TR[impakt.transform]
        CR[impakt.criteria]
        PR[impakt.protocol]
    end

    subgraph "Presentation Layer"
        PL[impakt.plot]
        RP[impakt.report]
        WB[impakt.web]
    end

    subgraph "Orchestration Layer"
        CF[impakt.config]
        TM[impakt.template]
        SC[impakt.script]
        PG[impakt.plugin]
    end

    IO -->|"parse MME"| CH
    CH -->|"channel data"| TR
    TR -->|"filtered/aligned"| CR
    CR -->|"injury values"| PR
    PR -->|"scores & ratings"| RP

    CH -->|"raw & derived"| PL
    TR -->|"processed"| PL
    CR -->|"criteria results"| PL

    PL -->|"figures"| RP
    PL -->|"interactive plots"| WB

    CF -.->|"configures"| PL
    CF -.->|"configures"| TR
    CF -.->|"configures"| CR
    CF -.->|"configures"| PR

    SC -->|"drives"| TM
    SC -->|"drives"| IO
    SC -->|"drives"| TR
    SC -->|"drives"| PL

    PG -.->|"extends"| IO
    PG -.->|"extends"| TR
    PG -.->|"extends"| CR
    PG -.->|"extends"| PR

    WB -->|"calls"| SC

Configuration System

All configurable behavior is defined in YAML files with three-layer resolution:

graph LR
    PKG["Package Defaults\nsrc/impakt/defaults/config.yaml"] --> USER["User Defaults\n~/.impakt/config.yaml"]
    USER --> SESSION["Session Overrides\n<test_dir>/.impakt/config.yaml"]
    SESSION --> ACTIVE["Active Config"]

    style PKG fill:#e1f5fe
    style USER fill:#fff9c4
    style SESSION fill:#c8e6c9
    style ACTIVE fill:#f3e5f5

Configurable sections:

Section Examples
plot Color palette, line widths, focus styling, margins, cursor colors, grid, resampling
transforms Default CFC class, Y-align, X-align method
criteria Channel patterns for auto-detection (per criterion, glob-style)
protocols Default protocol, version mapping, threshold file locations
session Auto-save behavior, .impakt/ directory name
web Default layout, cursor poll interval, port

When a session is saved, configuration and protocol thresholds are copied into the test's .impakt/ folder, making it self-contained and reproducible.


Module Breakdown

impakt.config — Configuration

Layered YAML configuration with typed Python dataclasses.

from impakt.config import Config

config = Config.load(session_path="tests/mme_data/3239")
config.plot.line_width          # 1.5 (from package default)
config.transforms.default_cfc   # None (no filter by default)
config.protocols.default         # "euro_ncap"

config.transforms.default_cfc = 600
config.save_session("tests/mme_data/3239")  # writes .impakt/config.yaml

impakt.io — Data IO

Reads crash test data via the ReaderProtocol. The MMEReader handles real ISO 13499 format (.mme master + .chn index + .NNN data files) and simplified INI format.

classDiagram
    class ReaderProtocol {
        <<protocol>>
        +read(path: Path) TestData
        +supports(path: Path) bool
        +metadata(path: Path) TestMetadata
    }

    class MMEReader
    class TDMSReader
    class CSVReader

    class ReaderRegistry {
        +register(reader: ReaderProtocol)
        +detect(path: Path) ReaderProtocol
        +read(path: Path) TestData
    }

    ReaderProtocol <|.. MMEReader
    ReaderProtocol <|.. TDMSReader
    ReaderProtocol <|.. CSVReader
    ReaderRegistry o-- ReaderProtocol

Tested against 5 real ISO 13499 datasets from NHTSA/Calspan, BASt, Volkswagen, and UTAC.

impakt.channel — Channel Model

Immutable Channel objects wrapping NumPy time-series data with ISO channel code intelligence.

The ChannelCode parser auto-detects 14-char and 16-char ISO codes:

Format Example Positions 11-12
16-char (with dummy) 11HEAD0000H3ACXP H3 = Hybrid III
14-char (simplified) 11HEAD0000ACXA AC = Acceleration

Auto-grouping: Channels sharing all fields except direction (X/Y/Z) are grouped for one-call resultant computation.

impakt.transform — Signal Processing

Non-destructive pipeline via TransformChain. Each transform produces a new Channel.

Transform Description
CFCFilter SAE J211 CFC filtering (4th-order Butterworth, zero-phase)
XAlign Time-zero shifting (manual, threshold, trigger)
YAlign Zero-offset correction from baseline window
Resultant Vector magnitude from X/Y/Z components
MathExpr Free-form math with safe numpy evaluation
Trim Extract time range
Resample Change sample rate (Fourier-based)

impakt.criteria — Injury Criteria

Auto-detection of channels by ISO naming patterns, with configurable patterns in config.yaml.

Criterion Method Reference
HIC15/36 Cumulative integration, optimal window search FMVSS 208
3ms Clip Cumulative exceedance SAE J211
Nij 4 modes (NTE/NTF/NCE/NCF), per-dummy intercepts FMVSS 208
Chest Deflection Peak sternal displacement FMVSS 208
Femur Load Peak compressive axial force FMVSS 208
Tibia Index M/Mc + F/Fc with intercepts Euro NCAP
Viscous Criterion V(t) * C(t) Euro NCAP

impakt.protocol — Rating Protocols

Versioned YAML threshold files loaded from defaults/protocols/, ~/.impakt/protocols/, or <test>/.impakt/protocols/.

Protocol Output
Euro NCAP Stars (0-5), body region colors (Green/Yellow/Orange/Brown/Red), points
US NCAP Stars (1-5), injury probability per body region
IIHS Good/Acceptable/Marginal/Poor per body region, overall rating

impakt.plot — Visualization

Single rendering path via PlotEngine.render(PlotSpec). Both the scripting API and web UI construct PlotSpec objects and delegate to the same engine.

  • Compact mode for web UI (no legend, tight margins, disabled hover)
  • Standard mode for scripting (full tooltips, legend)
  • FigureResampler integration for LTTB downsampling on zoom/pan
  • Focus channel rendering (amber highlight, rendered on top)
  • Corridor fills (upper/lower bands from CSV or programmatic)

impakt.report — Report Generation

Jinja2 HTML templates rendered to PDF via WeasyPrint. Three report types: plot sheets, injury summary, and full protocol reports.

impakt.template — Templates and Sessions

Concept Template Session
Lives in ~/.impakt/templates/ <test_dir>/.impakt/
Contains Channel patterns, filter chains, corridors, protocol config Template ref + overrides + config + cached results
Purpose Reusable recipe Per-test instance with user modifications

impakt.web — Web UI

Dash application with two tabs:

Data Tab:

  • Resizable left panel (draggable splitter) with channel grid + transform controls
  • Channel grid: flat sortable DataTable, wildcard filter, facet dropdowns, consistent color coding
  • Plot area with multi-pane layout (1x1 through 3x1)
  • Channel Values table: combined statistics + live cursor values (#, ISO Code, Description, Unit, Min@Time, Max@Time, X1, X2, Cursor)
  • Custom JS cursor tracker (mousemove → pixel-to-data-X conversion)

Analysis Tab:

  • Injury criteria auto-computation with protocol scoring
  • Math expression builder with variable binding
  • Template management (library browser, save/apply/delete)
  • Corridor upload (CSV)
  • Export (CSV, PNG/SVG/PDF, protocol report)

impakt.plugin — Plugin System

Entry point + directory + API discovery. PluginRegistry.register_reader() forwards to the IO registry. Plugins discovered on first Session.open().

impakt.script — Scripting API

Session is the primary entry point. The web UI's AppState holds Session objects — the same code path for scripts and UI.


Data Flow

graph TB
    MME["MME Directory\n(immutable)"] -->|"impakt.io"| TD[TestData]
    TD --> CH[Channels]
    CH -->|"TransformChain"| TCH[Transformed Channels]
    TCH -->|"criteria"| CR[CriterionResults]
    CR -->|"protocol"| SC[ProtocolResult]

    CH & TCH -->|"PlotSpec"| PE[PlotEngine]
    PE --> FIG[Plotly Figure]

    SC --> RP[Report Engine]
    FIG --> RP
    RP --> PDF[PDF/HTML]

    CFG["Config\n(layered YAML)"] -.-> PE
    CFG -.-> CR
    CFG -.-> SC

    TD -.->|"state"| IMPAKT[".impakt/"]
    IMPAKT -.->|"restore"| TD

    style MME fill:#e1f5fe
    style IMPAKT fill:#fff9c4
    style PDF fill:#c8e6c9
    style CFG fill:#f3e5f5

Configuration Layers

graph TB
    subgraph "Package (shipped with pip install)"
        PKG_CFG[defaults/config.yaml]
        PKG_PROTO[defaults/protocols/*.yaml]
    end

    subgraph "User (~/.impakt/)"
        USR_CFG[config.yaml]
        USR_PROTO[protocols/*.yaml]
        USR_TMPL[templates/*.yaml]
        USR_CORR[corridors/*.csv]
    end

    subgraph "Test Session (<test_dir>/.impakt/)"
        SES_CFG[config.yaml]
        SES_PROTO[protocols/*.yaml]
        SES_SESS[session.yaml]
        SES_CORR[corridors/]
        SES_DER[derived/]
    end

    PKG_CFG -->|"overridden by"| USR_CFG
    USR_CFG -->|"overridden by"| SES_CFG

    PKG_PROTO -->|"copied to"| USR_PROTO
    USR_PROTO -->|"copied to"| SES_PROTO

Save Session copies config + protocols into the test's .impakt/ folder. The test directory becomes self-contained and reproducible.


ISO Channel Naming Intelligence

graph LR
    RAW["11HEAD0000H3ACXP"] --> PARSER[ChannelCode Parser]

    PARSER --> OBJ["Object: 11\nDriver"]
    PARSER --> LOC["Location: HEAD"]
    PARSER --> FINE["Fine: 0000\nCenter of Gravity"]
    PARSER --> DUMMY["Dummy: H3\nHybrid III"]
    PARSER --> MEAS["Measurement: AC\nAcceleration"]
    PARSER --> DIR["Direction: X"]
    PARSER --> SENSE["Sense: P"]

    OBJ & LOC & FINE & DUMMY & MEAS & DIR --> GROUP["Group Key\n(X/Y/Z family)"]

    GROUP --> AUTO["Auto-features:\n- Resultant computation\n- Criteria channel matching\n- Channel grid placement\n- Human-readable labels"]

Directory Structure

impakt/
├── pyproject.toml               # PEP 621, uv dependency-groups
├── uv.lock
├── .gitignore
├── README.md                    # This file
├── BRAINSTORM.md                # Feature ideas (80+)
├── docs/
│   ├── STATUS.md                # Detailed project state
│   └── QA-*.md                  # Quality assessment scorecards
├── research/
│   └── landscape.md             # Competitive landscape analysis
├── src/impakt/
│   ├── __init__.py              # exports Session, Template
│   ├── config/                  # Layered YAML configuration
│   │   ├── __init__.py
│   │   └── model.py             # Config, PlotConfig, TransformConfig, etc.
│   ├── defaults/                # Package-level defaults (shipped)
│   │   ├── config.yaml          # All configurable fields with comments
│   │   └── protocols/           # Euro NCAP, IIHS threshold YAMLs
│   ├── channel/                 # Data model + ISO naming
│   │   ├── code.py              # ChannelCode parser (14/16-char auto-detect)
│   │   ├── model.py             # Channel, ChannelGroup, TestData, TestMetadata
│   │   ├── group.py             # Auto-grouping utilities
│   │   └── lookup.py            # ISO naming lookup tables (150+ entries)
│   ├── io/                      # Data readers
│   │   ├── reader.py            # ReaderProtocol, ReaderRegistry
│   │   ├── mme.py               # ISO 13499 MME reader (real + simplified)
│   │   ├── tdms.py              # TDMS stub
│   │   └── csv.py               # CSV stub
│   ├── transform/               # Signal processing
│   │   ├── base.py              # Transform protocol, TransformChain
│   │   ├── cfc.py               # SAE J211 CFC filter
│   │   ├── align.py             # X-align, Y-align
│   │   ├── resultant.py         # Vector magnitude
│   │   ├── math_expr.py         # Safe math expressions
│   │   └── resample.py          # Trim, Resample
│   ├── criteria/                # Injury criteria calculations
│   ├── protocol/                # Rating protocol scorers
│   │   ├── thresholds/          # Versioned YAML threshold files
│   │   └── *.py                 # Euro NCAP, US NCAP, IIHS
│   ├── plot/                    # Plotly rendering engine
│   │   ├── engine.py            # PlotEngine (single rendering path)
│   │   ├── spec.py              # PlotSpec, ChannelRef, Corridor
│   │   └── export.py            # Image/HTML export
│   ├── report/                  # PDF/HTML report generation
│   │   ├── engine.py            # Jinja2 + WeasyPrint
│   │   └── templates/           # HTML report templates
│   ├── template/                # Template + session persistence
│   ├── web/                     # Dash web application
│   │   ├── app.py               # App factory
│   │   ├── state.py             # AppState (holds Sessions server-side)
│   │   ├── layout.py            # Two-tab layout (Data + Analysis)
│   │   ├── components/          # 10 reusable UI components
│   │   ├── callbacks/           # 9 feature-specific callback modules
│   │   └── assets/              # CSS, JS (splitter, cursor tracker)
│   ├── plugin/                  # Plugin registry + discovery
│   └── script/                  # Session API + CLI
│       ├── api.py               # Session, ChannelHandle, Template
│       └── cli.py               # impakt serve/info/channels/evaluate
├── tests/
│   ├── fixtures/                # Synthetic MME test data (26 channels)
│   ├── mme_data/                # 5 real ISO 13499 datasets
│   └── test_*/                  # 258 tests across all modules

Scripting Examples

One-call evaluation

from impakt import Session

s = Session.open("tests/mme_data/3239")

# Auto-detect channels and score against Euro NCAP
result = s.evaluate("euro_ncap")
print(result.summary())
# Euro NCAP 2024
#   Rating: ***** (5/5 stars)
#   Score: 14.0/16.0 (88%)
#   Head: 233.08  [green] (4.0/4.0)
#   Chest: 44.04 g [yellow] (3.0/4.0)
#   ...

Fluent transform chaining

from impakt import Session

s = Session.open("tests/mme_data/3239")

# Each transform returns a ChannelHandle — fully chainable
ch = (
    s.channel("11HEAD0000H3ACXP")
    .transform.cfc(1000)
    .transform.y_align()
    .transform.trim(t_start=0.0, t_end=0.1)
)
print(f"Peak: {ch.peak:.1f} {ch.unit}")

Configuration override

from impakt import Session

s = Session.open("tests/mme_data/3239")

# Override default CFC for this session
s.config.transforms.default_cfc = 600
s.config.plot.line_width = 2.0
s.save_config()  # writes to 3239/.impakt/config.yaml

Custom math channel

from impakt import Session
from impakt.transform import math_expr

s = Session.open("tests/mme_data/3239")

custom = math_expr(
    expression="sqrt(a**2 + b**2)",
    channels={
        "a": s.channel("11HEAD0000H3ACXP").raw,
        "b": s.channel("11HEAD0000H3ACZP").raw,
    },
    name="Head XZ Resultant",
    unit="m/s²",
)
print(f"Peak: {custom.peak:.1f} {custom.unit}")

Launch web UI

from impakt.web import serve
from impakt import Session

s = Session.open("tests/mme_data/3239")
serve(s, port=8050)
# Opens browser at http://localhost:8050