1242 lines
35 KiB
Markdown
1242 lines
35 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.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Contents
|
||
|
|
|
||
|
|
- [[#Design Principles]]
|
||
|
|
- [[#Architecture Overview]]
|
||
|
|
- [[#Module Breakdown]]
|
||
|
|
- [[#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 — PDF and Report Generation]]
|
||
|
|
- [[#impakt.template — Templates and Sessions]]
|
||
|
|
- [[#impakt.web — Web UI]]
|
||
|
|
- [[#impakt.plugin — Plugin System]]
|
||
|
|
- [[#impakt.script — Scripting API]]
|
||
|
|
- [[#Data Flow]]
|
||
|
|
- [[#Template and Session Lifecycle]]
|
||
|
|
- [[#Injury Criteria Pipeline]]
|
||
|
|
- [[#Web UI Architecture]]
|
||
|
|
- [[#Plugin Architecture]]
|
||
|
|
- [[#ISO Channel Naming Intelligence]]
|
||
|
|
- [[#Directory Structure]]
|
||
|
|
- [[#Scripting Examples]]
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 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
|
||
|
|
|
||
|
|
```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"
|
||
|
|
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.
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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.
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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.
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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:**
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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.
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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.
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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.
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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 |
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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):
|
||
|
|
|
||
|
|
```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`):
|
||
|
|
|
||
|
|
```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:**
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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).
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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.
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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:**
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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.
|
||
|
|
|
||
|
|
```python
|
||
|
|
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:
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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:
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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.
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
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
|
||
|
|
|
||
|
|
```python
|
||
|
|
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
|
||
|
|
|
||
|
|
```python
|
||
|
|
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
|
||
|
|
|
||
|
|
```python
|
||
|
|
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
|
||
|
|
|
||
|
|
```python
|
||
|
|
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
|
||
|
|
|
||
|
|
```python
|
||
|
|
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
|
||
|
|
```
|