560 lines
18 KiB
Markdown
560 lines
18 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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]]
|
|
- [[#Architecture Overview]]
|
|
- [[#Configuration System]]
|
|
- [[#Module Breakdown]]
|
|
- [[#impakt.config — Configuration]]
|
|
- [[#impakt.io — Data IO]]
|
|
- [[#impakt.channel — Channel Model]]
|
|
- [[#impakt.transform — Signal Processing]]
|
|
- [[#impakt.criteria — Injury Criteria]]
|
|
- [[#impakt.protocol — Rating Protocols]]
|
|
- [[#impakt.plot — Visualization]]
|
|
- [[#impakt.report — Report Generation]]
|
|
- [[#impakt.template — Templates and Sessions]]
|
|
- [[#impakt.web — Web UI]]
|
|
- [[#impakt.plugin — Plugin System]]
|
|
- [[#impakt.script — Scripting API]]
|
|
- [[#Data Flow]]
|
|
- [[#Configuration Layers]]
|
|
- [[#ISO Channel Naming Intelligence]]
|
|
- [[#Directory Structure]]
|
|
- [[#Scripting Examples]]
|
|
|
|
---
|
|
|
|
## 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
|
|
|
|
```mermaid
|
|
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:
|
|
|
|
```mermaid
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
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
|
|
```
|