Files
impakt/README.md
2026-04-10 14:37:34 -04:00

35 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.


Contents


Design Principles

  1. Immutable raw data. Original MME files are never modified. All state — sessions, cached computations, user overrides — lives in a .impakt/ subfolder alongside the test data.
  2. Non-destructive transforms. Filtering, alignment, and math operations produce new channel views. The transformation chain is stored and reproducible.
  3. Scriptable first. Every operation available in the UI is accessible through a Python API. The web UI is a frontend to the same engine.
  4. Template-driven workflow. Reusable templates define plot layouts, filter chains, channel selections, corridors, and report configurations. Templates live in a global library; sessions bind templates to specific test data.
  5. Protocol-aware. Built-in knowledge of Euro NCAP, US NCAP, and IIHS injury criteria, scoring thresholds, and report formats.
  6. Plugin-extensible. Custom readers, transforms, injury criteria, report templates, and UI components can be registered through a plugin 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"
        TM[impakt.template]
        SC[impakt.script]
        PG[impakt.plugin]
    end

    IO -->|"parse MME/other"| 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

    TM -->|"orchestrates"| IO
    TM -->|"orchestrates"| TR
    TM -->|"orchestrates"| PL
    TM -->|"orchestrates"| CR

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

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

    WB -->|"calls"| SC

Module Breakdown

impakt.io — Data IO

Responsible for reading crash test data from disk into the internal channel model.

Primary format: ISO 13499 MME (directory-based: MME.ini + .chn/.dat file pairs).

Architecture: Reader classes implement a common Reader protocol, enabling future format support (TDMS, CSV, UDB) without changing downstream code.

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

    class MMEReader {
        +read(path: Path) TestData
        +supports(path: Path) bool
        +metadata(path: Path) TestMetadata
        -_parse_ini(path: Path) dict
        -_parse_chn(path: Path) ChannelHeader
        -_read_dat(path: Path, header: ChannelHeader) ndarray
    }

    class TDMSReader {
        +read(path: Path) TestData
        +supports(path: Path) bool
        +metadata(path: Path) TestMetadata
    }

    class CSVReader {
        +read(path: Path) TestData
        +supports(path: Path) bool
        +metadata(path: Path) TestMetadata
    }

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

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

Key responsibilities:

  • Parse MME.ini for test-level metadata (vehicle, dummy, impact config)
  • Parse .chn headers for per-channel metadata (units, sample rate, CFC class, pre-trigger)
  • Read .dat files (ASCII or binary IEEE float) into NumPy arrays
  • Reconstruct time vectors from dt and pre-trigger sample count
  • Populate TestData and Channel objects

impakt.channel — Channel Model

The core data model. Channels are immutable value objects wrapping time-series data with rich metadata.

classDiagram
    class TestData {
        +test_id: str
        +metadata: TestMetadata
        +channels: dict~str, Channel~
        +path: Path
        +get(name: str) Channel
        +find(pattern: str) list~Channel~
        +groups() dict~str, ChannelGroup~
    }

    class TestMetadata {
        +test_number: str
        +test_date: date
        +test_type: str
        +vehicle: VehicleInfo
        +dummy: DummyInfo
        +impact: ImpactConfig
    }

    class Channel {
        +name: str
        +code: ChannelCode
        +data: ndarray
        +time: ndarray
        +unit: str
        +sample_rate: float
        +cfc_class: int | None
        +metadata: dict
    }

    class ChannelCode {
        +raw: str
        +test_object: str
        +main_location: str
        +fine_location: str
        +measurement: str
        +direction: str
        +sense: str
        +filter_class: str
        +group_key() str
        +is_component() bool
        +axis() str
    }

    class ChannelGroup {
        +key: str
        +x: Channel | None
        +y: Channel | None
        +z: Channel | None
        +resultant() Channel
        +components() list~Channel~
    }

    TestData *-- TestMetadata
    TestData *-- Channel
    Channel *-- ChannelCode
    ChannelGroup o-- Channel

ISO channel naming intelligence is embedded in ChannelCode. Given a raw 16-character name like 11HEAD0000H3ACXA, the parser extracts:

Field Positions Example Meaning
Test Object 1-2 11 Driver, Hybrid III
Main Location 3-6 HEAD Head
Fine Location 7-10 0000 Center of gravity
Measurement 11-12 AC Acceleration
Direction 13 X Longitudinal axis
Sense 14 A SAE sign convention A

Auto-grouping: Channels sharing positions 1-12 + 14-16 (differing only at position 13: X/Y/Z) are automatically grouped into ChannelGroup objects for one-call resultant computation.


impakt.transform — Signal Processing

Non-destructive signal processing pipeline. Each transform takes a Channel and returns a new Channel — the original is never mutated.

graph LR
    subgraph "Transform Pipeline"
        RAW[Raw Channel] --> F1[CFC Filter]
        F1 --> F2[X-Axis Align]
        F2 --> F3[Y-Axis Zero]
        F3 --> F4[Math Expression]
        F4 --> OUT[Derived Channel]
    end

    style RAW fill:#e1f5fe
    style OUT fill:#c8e6c9

Available transforms:

Transform Description Parameters
CFCFilter SAE J211 CFC filtering (4th-order Butterworth, zero-phase) cfc_class: 60, 180, 600, 1000
XAlign Time-zero shifting — moves t=0 to a user-specified event method: manual, threshold, trigger
YAlign Zero-offset correction using average-over-time window window: (t_start, t_end) for baseline
Resultant Compute vector magnitude from X/Y/Z components group: ChannelGroup
MathExpr Free-form math on channels (e.g., ch_a + ch_b * 0.5) expression: str, channels: dict
Trim Extract time range t_start, t_end
Resample Change sample rate target_rate: float

Transform chain model:

classDiagram
    class Transform {
        <<protocol>>
        +apply(channel: Channel) Channel
        +name: str
        +params: dict
    }

    class TransformChain {
        +steps: list~Transform~
        +apply(channel: Channel) Channel
        +append(transform: Transform) TransformChain
        +serialize() dict
        +deserialize(data: dict) TransformChain
    }

    class CFCFilter {
        +cfc_class: int
        +apply(channel: Channel) Channel
    }

    class XAlign {
        +method: str
        +reference_time: float
        +apply(channel: Channel) Channel
    }

    class YAlign {
        +window: tuple~float, float~
        +apply(channel: Channel) Channel
    }

    class Resultant {
        +apply(group: ChannelGroup) Channel
    }

    class MathExpr {
        +expression: str
        +apply(channels: dict) Channel
    }

    Transform <|.. CFCFilter
    Transform <|.. XAlign
    Transform <|.. YAlign
    Transform <|.. Resultant
    Transform <|.. MathExpr
    TransformChain o-- Transform

CFC filter implementation (SAE J211 compliant):

  • 4th-order Butterworth low-pass, applied forward-reverse via scipy.signal.filtfilt
  • Cutoff frequency: f_c = CFC_class * (5/3) Hz
  • CFC 60 = 100 Hz, CFC 180 = 300 Hz, CFC 600 = 1000 Hz, CFC 1000 = 1650 Hz

impakt.criteria — Injury Criteria

Calculation engine for standard injury criteria. Each criterion is a self-contained function operating on filtered channel data.

classDiagram
    class InjuryCriterion {
        <<protocol>>
        +name: str
        +required_channels: list~str~
        +compute(channels: dict~str, Channel~, dummy: DummyInfo) CriterionResult
    }

    class CriterionResult {
        +criterion: str
        +value: float
        +unit: str
        +time_of_peak: float | None
        +window: tuple~float, float~ | None
        +details: dict
    }

    class HIC {
        +window_ms: int
        +compute(...) CriterionResult
    }

    class Clip3ms {
        +compute(...) CriterionResult
    }

    class Nij {
        +intercepts: NijIntercepts
        +compute(...) CriterionResult
    }

    class ChestDeflection {
        +compute(...) CriterionResult
    }

    class FemurLoad {
        +compute(...) CriterionResult
    }

    class TibiaIndex {
        +compute(...) CriterionResult
    }

    class ViscousCriterion {
        +compute(...) CriterionResult
    }

    InjuryCriterion <|.. HIC
    InjuryCriterion <|.. Clip3ms
    InjuryCriterion <|.. Nij
    InjuryCriterion <|.. ChestDeflection
    InjuryCriterion <|.. FemurLoad
    InjuryCriterion <|.. TibiaIndex
    InjuryCriterion <|.. ViscousCriterion
    InjuryCriterion --> CriterionResult

Implemented criteria:

Criterion Formula / Method Key Inputs Reference
HIC15 / HIC36 (t2-t1) * [avg(a)]^2.5, maximized over window Head resultant accel (g) FMVSS 208
3ms Clip Max accel sustained for cumulative 3 ms Chest resultant accel (g) SAE J211
Nij Fz/Fzc + My/Myc, max of 4 modes Upper neck Fz, My FMVSS 208
Chest Deflection Peak sternal displacement Chest deflection (mm) FMVSS 208
Femur Load Peak compressive axial force Femur Fz left/right (kN) FMVSS 208
Tibia Index |M|/Mc + |F|/Fc Tibia Mx, My, Fz Euro NCAP
Viscous Criterion V(t) * C(t) (velocity x compression) Chest deflection time-history Euro NCAP

Nij critical intercepts by dummy type:

Dummy Fzc Tension (N) Fzc Compression (N) Myc Flexion (Nm) Myc Extension (Nm)
Hybrid III 50th M 6806 6160 310 135
Hybrid III 5th F 4287 3880 155 67
Hybrid III 6YO 2800 2800 93 37
Hybrid III 3YO 2120 2120 68 27

impakt.protocol — Rating Protocols

Maps computed injury criteria to protocol-specific scores and ratings.

graph TB
    subgraph "Criteria Results"
        HIC[HIC15: 423]
        CHEST[Chest Defl: 34mm]
        FEMUR[Femur: 4.2kN]
        NIJ[Nij: 0.61]
        TI[Tibia Index: 0.8]
    end

    subgraph "Protocol Engines"
        ENCAP[Euro NCAP Scorer]
        USNCAP[US NCAP Scorer]
        IIHS_E[IIHS Evaluator]
    end

    subgraph "Outputs"
        ENCAP_R["Euro NCAP\n4 stars\nAdult: 82%"]
        USNCAP_R["US NCAP\n5 stars\nP(injury): 8%"]
        IIHS_R["IIHS\nGood\nAll sub-ratings: G"]
    end

    HIC --> ENCAP & USNCAP & IIHS_E
    CHEST --> ENCAP & USNCAP & IIHS_E
    FEMUR --> ENCAP & USNCAP & IIHS_E
    NIJ --> ENCAP & USNCAP & IIHS_E
    TI --> ENCAP

    ENCAP --> ENCAP_R
    USNCAP --> USNCAP_R
    IIHS_E --> IIHS_R

Protocol scoring:

Protocol Methodology Output
Euro NCAP Sliding-scale performance limits per body region, mapped to color codes (Green / Yellow / Orange / Brown / Red) and points. Area percentages determine star rating. Stars (0-5), body region colors, area scores
US NCAP Injury risk functions convert criteria to probability of AIS 3+ injury. Combined probability maps to stars. Stars (1-5), injury probability
IIHS Threshold-based per criterion: Good / Acceptable / Marginal / Poor. Overall = worst sub-rating with adjustment. G/A/M/P per region, overall rating

Protocol thresholds are versioned and configurable — scoring rules change every few years and Impakt stores these as versioned protocol definition files.


impakt.plot — Visualization

Plotting engine built on Plotly for interactive web-based visualization.

Capabilities:

  • Single channel time-history plots
  • Multi-channel overlay (same test, different channels)
  • Multi-test overlay (same channel, different tests)
  • Dual X-axis cursors with value readout for all plotted channels
  • Tolerance corridors (user-defined or from templates)
  • Zoom, pan, box select
  • Resultant plots auto-computed from grouped components
classDiagram
    class PlotSpec {
        +channels: list~ChannelRef~
        +corridors: list~Corridor~
        +x_cursors: tuple~float, float~ | None
        +x_range: tuple~float, float~ | None
        +y_range: tuple~float, float~ | None
        +title: str
        +x_label: str
        +y_label: str
    }

    class ChannelRef {
        +test_id: str
        +channel_name: str
        +transform_chain: TransformChain | None
        +style: PlotStyle
    }

    class Corridor {
        +name: str
        +upper: ndarray
        +lower: ndarray
        +time: ndarray
        +style: CorridorStyle
    }

    class PlotStyle {
        +color: str
        +line_width: float
        +line_dash: str
        +label: str
    }

    class PlotEngine {
        +render(spec: PlotSpec) PlotlyFigure
        +to_image(spec: PlotSpec, format: str) bytes
        +to_html(spec: PlotSpec) str
    }

    PlotSpec *-- ChannelRef
    PlotSpec *-- Corridor
    ChannelRef *-- PlotStyle
    PlotEngine --> PlotSpec

Dual X-axis cursor — the user selects two time points (click or explicit entry). A table displays the interpolated value of every plotted channel at both cursor positions, plus the delta.


impakt.report — PDF and Report Generation

Generates single-page-per-plot PDFs and multi-section protocol reports.

graph LR
    PLOTS[Plot Specs] --> RE[Report Engine]
    CRITERIA[Criteria Results] --> RE
    PROTOCOL[Protocol Scores] --> RE
    META[Test Metadata] --> RE
    TMPL[Report Template] --> RE
    RE --> PDF[PDF Output]
    RE --> HTML[HTML Output]

Report types:

  • Plot sheets — one plot per page, with metadata header (test ID, channel info, filter state)
  • Injury summary — tabular criteria results with color-coded pass/fail per protocol
  • Full protocol report — Euro NCAP / US NCAP / IIHS formatted report with all required sections, body region diagrams, and scoring breakdowns

impakt.template — Templates and Sessions

The template/session system enables reusable analysis workflows and per-test state persistence.

Concept Template Session
Lives in Global library (~/.impakt/templates/) Alongside test data (.impakt/ subfolder)
Contains Plot layouts, filter chains, channel selections, corridors, report configs Template reference + test-specific overrides + cached results
Purpose Reusable recipe ("show me frontal NCAP analysis") Specific instance ("test_001 viewed with frontal NCAP, but I moved the cursor")
Mutability Edited in library, versioned Auto-saved per test, can "promote" to template
graph TB
    subgraph "Global Library ~/.impakt/templates/"
        T1[frontal_ncap.yaml]
        T2[side_iihs.yaml]
        T3[custom_analysis.yaml]
    end

    subgraph "Test Data Directory"
        MME[test_001/MME.ini]
        subgraph ".impakt/"
            S1[session.yaml]
            CACHE[cache/]
        end
    end

    T1 -->|"user opens test\nwith template"| S1
    S1 -->|"references"| T1
    S1 -->|"stores overrides"| S1
    S1 -->|"promote changes"| T1

Template spec (YAML):

name: "Frontal NCAP Analysis"
version: 2
plots:
  - title: "Head Acceleration"
    channels:
      - pattern: "11HEAD0000AC{X,Y,Z}A"
        transform:
          - type: cfc_filter
            cfc_class: 1000
      - pattern: "11HEAD0000ACRA"
        transform:
          - type: resultant
    corridors:
      - name: "HIC 700 Reference"
        file: "corridors/hic_700.csv"
  - title: "Chest Deflection"
    channels:
      - pattern: "11CHST****DC*A"
    x_cursors: [0.0, 0.080]
filters:
  default_cfc: 180
criteria:
  - hic15
  - clip_3ms
  - nij
  - chest_deflection
  - femur_load
protocol: euro_ncap_2024
report:
  format: pdf
  template: euro_ncap_full

Session spec (stored in test_data/.impakt/session.yaml):

template: "frontal_ncap"
template_version: 2
test_path: "/data/tests/test_001"
overrides:
  plots:
    0:
      x_cursors: [0.012, 0.065]
  filters:
    "11HEAD0000ACXA":
      - type: cfc_filter
        cfc_class: 600
cached_results:
  hic15: 423.7
  last_computed: "2026-04-10T14:30:00"

Lifecycle:

sequenceDiagram
    participant User
    participant Template Library
    participant Session
    participant Engine

    User->>Template Library: Select template
    Template Library->>Session: Instantiate session
    User->>Engine: Open test data
    Engine->>Session: Bind to test data
    Engine->>Engine: Apply template (load channels, filters, plots)
    User->>Session: Modify (move cursors, change filter)
    Session->>Session: Store override
    User->>Template Library: (optional) Promote override to template
    User->>Session: Close
    Session->>Session: Auto-save to .impakt/

impakt.web — Web UI

Interactive web application built with Dash (Plotly).

graph TB
    subgraph "Dash Layout"
        direction TB
        HEADER[Header Bar: Test Info + Template Selector]

        subgraph "Main Area"
            direction LR
            subgraph "Left Panel"
                TREE[Channel Tree\nISO-aware hierarchy]
                FILTER_PANEL[Transform Controls\nCFC / Align / Math]
            end

            subgraph "Center"
                PLOT_AREA[Plot Area\nMultiple plot panes]
                CURSOR_TABLE[Cursor Values Table\nx1 | x2 | delta]
            end

            subgraph "Right Panel"
                CORRIDOR_PANEL[Corridor Manager]
                CRITERIA_PANEL[Criteria Results]
            end
        end

        FOOTER[Report Generation + Export Controls]
    end

    TREE -->|"channel selection\ncallback"| PLOT_AREA
    FILTER_PANEL -->|"transform\ncallback"| PLOT_AREA
    PLOT_AREA -->|"cursor position\ncallback"| CURSOR_TABLE
    CORRIDOR_PANEL -->|"corridor data\ncallback"| PLOT_AREA
    CRITERIA_PANEL -->|"annotation\ncallback"| PLOT_AREA
    FOOTER -->|"generate\ncallback"| PLOT_AREA

Key UI components:

  • Channel tree — hierarchical browser organized by test object > body region > measurement type, leveraging ISO naming intelligence
  • Plot pane — Plotly figures with zoom / pan / box-select, corridor overlays
  • Cursor table — interpolated values at two user-selected X-axis times for all visible channels, plus delta
  • Transform sidebar — apply / remove filters, alignment, math expressions
  • Template panel — load, save, promote templates
  • Report panel — select protocol, generate PDF

impakt.plugin — Plugin System

Formal plugin architecture for extending every layer.

classDiagram
    class PluginRegistry {
        +register(plugin: ImpaktPlugin)
        +discover(path: Path)
        +get_readers() list~ReaderProtocol~
        +get_transforms() list~Transform~
        +get_criteria() list~InjuryCriterion~
        +get_protocols() list~Protocol~
        +get_report_templates() list~ReportTemplate~
    }

    class ImpaktPlugin {
        <<protocol>>
        +name: str
        +version: str
        +register(registry: PluginRegistry)
    }

    class ReaderPlugin {
        +reader: ReaderProtocol
    }

    class TransformPlugin {
        +transform: Transform
    }

    class CriterionPlugin {
        +criterion: InjuryCriterion
    }

    class ProtocolPlugin {
        +protocol: Protocol
    }

    ImpaktPlugin <|.. ReaderPlugin
    ImpaktPlugin <|.. TransformPlugin
    ImpaktPlugin <|.. CriterionPlugin
    ImpaktPlugin <|.. ProtocolPlugin
    PluginRegistry o-- ImpaktPlugin

Discovery mechanisms:

  • Entry points (pyproject.toml / setup.cfg entry point groups)
  • Directory scanning (~/.impakt/plugins/)
  • Explicit registration via API

Example plugin integration:

graph TB
    subgraph "Core Impakt"
        REG[Plugin Registry]
        IO_CORE[io: MMEReader]
        TR_CORE[transform: CFC, Align, ...]
        CR_CORE[criteria: HIC, Nij, ...]
        PR_CORE[protocol: ENCAP, USNCAP, IIHS]
    end

    subgraph "Plugin: impakt-tdms"
        TDMS_P[TDMSReader]
    end

    subgraph "Plugin: impakt-jncap"
        JNCAP_P[JNCAP Scorer]
        JNCAP_R[JNCAP Report Template]
    end

    subgraph "Plugin: impakt-custom-filter"
        CUSTOM_F[WaveletDenoise Transform]
    end

    subgraph "Discovery"
        EP[Entry Points\npyproject.toml]
        DIR[~/.impakt/plugins/]
        API_R[Explicit API\nregistry.register]
    end

    EP --> REG
    DIR --> REG
    API_R --> REG

    REG -->|"extends"| IO_CORE
    REG -->|"extends"| TR_CORE
    REG -->|"extends"| CR_CORE
    REG -->|"extends"| PR_CORE

    TDMS_P --> REG
    JNCAP_P --> REG
    JNCAP_R --> REG
    CUSTOM_F --> REG

impakt.script — Scripting API

The top-level API that scripts and the web UI both call.

from impakt import Session, Template

# Load test data
test = Session.open("/data/tests/test_001")

# Browse channels using ISO naming intelligence
head_channels = test.find("11HEAD0000AC*")
# [Channel(11HEAD0000ACXA), Channel(11HEAD0000ACYA), Channel(11HEAD0000ACZA)]

# Auto-detect X/Y/Z group, compute resultant
head_group = test.group("11HEAD0000AC")
head_resultant = head_group.resultant()

# Apply CFC filter (non-destructive)
filtered = head_resultant.transform.cfc(1000)

# Compute HIC15
from impakt.criteria import hic
result = hic(filtered, window_ms=15)
print(f"HIC15 = {result.value:.1f} at t = {result.time_of_peak:.4f}s")

# Apply a template
tmpl = Template.load("frontal_ncap")
session = tmpl.apply(test)

# Generate protocol report
from impakt.protocol import euro_ncap
report = euro_ncap.evaluate(session, version="2024")
report.to_pdf("test_001_encap.pdf")

# Launch web UI
from impakt.web import serve
serve(test, template="frontal_ncap", port=8050)

Data Flow

End-to-end data flow from MME files to PDF reports:

graph TB
    MME["MME Directory\n(immutable)"] -->|"impakt.io"| TD[TestData Object]
    TD -->|"channel access"| CH[Channel Objects]
    CH -->|"impakt.transform"| TCH[Transformed Channels]
    TCH -->|"impakt.criteria"| CR[Criterion Results]
    CR -->|"impakt.protocol"| SC[Protocol Scores]

    CH -->|"impakt.plot"| FIG1[Raw Plots]
    TCH -->|"impakt.plot"| FIG2[Filtered Plots]
    CR -->|"impakt.plot"| FIG3[Criteria Annotations]

    FIG2 --> RP[impakt.report]
    SC --> RP
    RP --> PDF[PDF Report]

    TD -.->|"state saved"| IMPAKT_DIR[".impakt/ subfolder"]
    IMPAKT_DIR -.->|"state loaded"| TD

    style MME fill:#e1f5fe
    style IMPAKT_DIR fill:#fff9c4
    style PDF fill:#c8e6c9

Template and Session Lifecycle

stateDiagram-v2
    [*] --> TemplateLibrary: User browses templates

    TemplateLibrary --> SessionCreated: Select template + open test data
    SessionCreated --> Active: Engine applies template

    Active --> Active: User modifies\n(cursors, filters, channels)
    Active --> Saved: Auto-save to .impakt/

    Saved --> Active: Reopen test data
    Saved --> TemplateLibrary: Promote overrides\nback to template

    Active --> ReportGenerated: User generates report
    ReportGenerated --> Active: Continue analysis

    Active --> [*]: Close session

Injury Criteria Pipeline

How raw channel data flows through to a protocol rating:

graph LR
    subgraph "1. Channel Selection"
        A1["HEAD AC X/Y/Z"]
        A2["NECK FO X/Z"]
        A3["NECK MO Y"]
        A4["CHEST DC"]
        A5["FEMUR FO Z L/R"]
        A6["TIBIA FO + MO"]
    end

    subgraph "2. Transform"
        B1[CFC 1000\n+ Resultant]
        B2[CFC 600]
        B3[CFC 600]
        B4[CFC 180]
        B5[CFC 600]
        B6[CFC 600]
    end

    subgraph "3. Criteria"
        C1[HIC15 / HIC36]
        C2[Nij]
        C3[Nij]
        C4["Chest Defl\n3ms Clip\nViscous"]
        C5[Femur Load]
        C6[Tibia Index]
    end

    subgraph "4. Protocol"
        D1["Euro NCAP\nColor + Points"]
        D2["US NCAP\nP(injury) + Stars"]
        D3["IIHS\nG/A/M/P"]
    end

    A1 --> B1 --> C1
    A2 --> B2 --> C2
    A3 --> B3 --> C3
    A4 --> B4 --> C4
    A5 --> B5 --> C5
    A6 --> B6 --> C6

    C1 & C2 & C3 & C4 & C5 & C6 --> D1
    C1 & C2 & C3 & C4 & C5 --> D2
    C1 & C2 & C3 & C4 & C5 & C6 --> D3

Web UI Architecture

graph TB
    subgraph "Browser"
        UI[Dash Frontend]
        PLOTS_V[Interactive Plots]
        CURSOR[Dual X-Cursor + Values Table]
        TREE[Channel Tree Browser]
        TMPL_UI[Template Selector]
        REPORT_UI[Report Generator Panel]
    end

    subgraph "Server"
        DASH[Dash Server]
        API[Impakt Script API]
        CACHE_S[Computation Cache]
    end

    UI --> DASH
    DASH --> API
    API --> CACHE_S

    TREE -->|"select channels"| PLOTS_V
    TMPL_UI -->|"apply template"| API
    CURSOR -->|"x1, x2 positions"| PLOTS_V
    REPORT_UI -->|"generate"| API

Plugin Architecture

graph LR
    subgraph "Extension Points"
        R[Readers]
        T[Transforms]
        C[Criteria]
        P[Protocols]
        RT[Report Templates]
    end

    REG[Plugin Registry] --> R & T & C & P & RT

    subgraph "Discovery"
        EP[pyproject.toml\nentry_points]
        DP[~/.impakt/plugins/]
        EX[registry.register]
    end

    EP & DP & EX --> REG

ISO Channel Naming Intelligence

The ChannelCode parser powers auto-discovery, grouping, and human-readable descriptions throughout the tool.

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

    PARSER --> OBJ["Test Object: 11\nDriver, Hybrid III"]
    PARSER --> LOC["Location: HEAD\nHead"]
    PARSER --> FINE["Fine: 0000\nCenter of Gravity"]
    PARSER --> MEAS["Measurement: AC\nAcceleration"]
    PARSER --> DIR["Direction: X\nLongitudinal"]
    PARSER --> SENSE["Sense: A\nSAE Convention"]

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

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

Lookup tables for all code segments are bundled with the package and extensible via plugins:

Segment Examples
Test objects 11 = Driver H3 50th, 12 = Passenger, 20 = Barrier, 30 = Pedestrian
Main locations HEAD, NECK, CHST, PELV, FEMR, TIBI, STCL, BPIL
Fine locations 0000 = CG, UP00 = Upper, LO00 = Lower, LE00 = Left, RI00 = Right
Measurements AC = Acceleration, FO = Force, MO = Moment, DS = Displacement, DC = Deflection
Directions X = Longitudinal, Y = Lateral, Z = Vertical, R = Resultant

Directory Structure

impakt/
├── pyproject.toml
├── README.md
├── src/
│   └── impakt/
│       ├── __init__.py
│       ├── io/
│       │   ├── __init__.py
│       │   ├── reader.py          # ReaderProtocol, ReaderRegistry
│       │   ├── mme.py             # MMEReader
│       │   ├── tdms.py            # TDMSReader (stub)
│       │   └── csv.py             # CSVReader (stub)
│       ├── channel/
│       │   ├── __init__.py
│       │   ├── model.py           # Channel, TestData, TestMetadata
│       │   ├── code.py            # ChannelCode parser
│       │   ├── group.py           # ChannelGroup, auto-grouping
│       │   └── lookup.py          # ISO code lookup tables
│       ├── transform/
│       │   ├── __init__.py
│       │   ├── base.py            # Transform protocol, TransformChain
│       │   ├── cfc.py             # CFCFilter
│       │   ├── align.py           # XAlign, YAlign
│       │   ├── resultant.py       # Resultant
│       │   ├── math_expr.py       # MathExpr
│       │   └── resample.py        # Resample, Trim
│       ├── criteria/
│       │   ├── __init__.py
│       │   ├── base.py            # InjuryCriterion protocol
│       │   ├── hic.py             # HIC15, HIC36
│       │   ├── clip3ms.py         # 3ms chest clip
│       │   ├── nij.py             # Neck injury criterion
│       │   ├── chest.py           # Chest deflection, Viscous criterion
│       │   ├── femur.py           # Femur load
│       │   └── tibia.py           # Tibia index
│       ├── protocol/
│       │   ├── __init__.py
│       │   ├── base.py            # Protocol protocol, version management
│       │   ├── euro_ncap.py       # Euro NCAP scorer
│       │   ├── us_ncap.py         # US NCAP scorer
│       │   ├── iihs.py            # IIHS evaluator
│       │   └── thresholds/        # Versioned threshold YAML files
│       │       ├── euro_ncap_2024.yaml
│       │       ├── us_ncap_2023.yaml
│       │       └── iihs_2024.yaml
│       ├── plot/
│       │   ├── __init__.py
│       │   ├── engine.py          # PlotEngine (Plotly)
│       │   ├── spec.py            # PlotSpec, ChannelRef, Corridor
│       │   ├── cursor.py          # Dual X-cursor logic
│       │   └── export.py          # PNG/SVG/PDF single-plot export
│       ├── report/
│       │   ├── __init__.py
│       │   ├── engine.py          # ReportEngine
│       │   ├── pdf.py             # PDF generation (WeasyPrint)
│       │   └── templates/         # Jinja2 HTML report templates
│       │       ├── plot_sheet.html
│       │       ├── injury_summary.html
│       │       └── protocol_report.html
│       ├── template/
│       │   ├── __init__.py
│       │   ├── model.py           # Template, Session models
│       │   ├── library.py         # Template library manager
│       │   └── session.py         # Session persistence
│       ├── web/
│       │   ├── __init__.py
│       │   ├── app.py             # Dash application
│       │   ├── layout.py          # UI layout components
│       │   ├── callbacks.py       # Dash callbacks
│       │   └── assets/            # CSS, static files
│       ├── plugin/
│       │   ├── __init__.py
│       │   ├── registry.py        # PluginRegistry
│       │   └── discovery.py       # Entry point + directory scanning
│       └── script/
│           ├── __init__.py
│           └── api.py             # Top-level scripting API
├── tests/
│   ├── test_io/
│   ├── test_channel/
│   ├── test_transform/
│   ├── test_criteria/
│   ├── test_protocol/
│   ├── test_plot/
│   └── fixtures/                  # Sample MME data for tests
└── docs/

Scripting Examples

Quick injury summary

from impakt import Session
from impakt.criteria import hic, clip_3ms, nij, chest_deflection, femur_load
from impakt.protocol import euro_ncap

test = Session.open("./test_001")

results = {
    "HIC15": hic(test, window_ms=15),
    "3ms Clip": clip_3ms(test),
    "Nij": nij(test),
    "Chest Deflection": chest_deflection(test),
    "Femur Left": femur_load(test, side="left"),
    "Femur Right": femur_load(test, side="right"),
}

for name, r in results.items():
    print(f"{name}: {r.value:.1f} {r.unit}")

rating = euro_ncap.evaluate(results, version="2024")
print(f"\nEuro NCAP: {rating.stars} stars ({rating.adult_pct:.0f}% adult)")

Batch comparison across tests

from impakt import Session
from impakt.plot import overlay
from pathlib import Path

tests = [Session.open(p) for p in Path("./tests").iterdir() if p.is_dir()]

overlay(
    [t.channel("11HEAD0000ACXA").transform.cfc(1000) for t in tests],
    labels=[t.metadata.test_number for t in tests],
    title="Head X Acceleration Comparison",
    corridors=["corridors/head_ax_corridor.csv"],
).to_pdf("head_comparison.pdf")

Custom math channel

from impakt import Session
from impakt.transform import math_expr

test = Session.open("./test_001")

custom = math_expr(
    expression="0.6 * chest_x + 0.4 * chest_z",
    channels={
        "chest_x": test.channel("11CHST0000ACXA").transform.cfc(180),
        "chest_z": test.channel("11CHST0000ACZA").transform.cfc(180),
    },
    name="Weighted Chest Metric",
    unit="g",
)

custom.plot(title="Custom Weighted Chest Acceleration")

Multi-test overlay with cursors

from impakt import Session
from impakt.plot import overlay, cursor_values

test1 = Session.open("./test_001")
test2 = Session.open("./test_002")

fig = overlay([
    test1.channel("11HEAD0000ACXA").transform.cfc(1000),
    test2.channel("11HEAD0000ACXA").transform.cfc(1000),
])

vals = cursor_values(fig, x1=0.015, x2=0.062)
print(vals)
#          channel  value_at_x1  value_at_x2   delta
# 0  test_001/...        12.3         45.7    33.4
# 1  test_002/...        11.8         42.1    30.3

Launch web UI with template

from impakt.web import serve
from impakt import Session

test = Session.open("./test_001")
serve(test, template="frontal_ncap", port=8050)
# Opens browser at http://localhost:8050