Ultrasonic guided wave damage detection toolkit
A Python library for detecting structural damage in pipes using ultrasonic guided wave pitch-catch measurements. Implements the methodology of:
Du, D., Karve, P.M., Rubin, S., Mahadevan, S., and Lawrence, S. Selective leaching detection in metal pipes using ultrasonic guided wave testing. (2024)
Given repeated ultrasonic measurements from a reference (undamaged)
pipe and a test (damaged) pipe, udwave:
- Computes six damage indicators (DIs) for every pairwise combination of reference and test signals
- Selects the best DI using the KDE-based distribution overlap metric
- Finds the optimal detection threshold by minimising the weighted Error Influence Index (EII)
- Estimates the probability of damage for any new unknown-status pipe
User-defined DIs can be plugged in alongside the built-in ones.
| Name | Type | Description |
|---|---|---|
| EMI | Single-sensor | Energy Magnitude Indicator — normalised Fourier amplitude difference |
| EDR | Single-sensor | Energy Deviation Ratio — CWT energy ratio |
| IDC | Single-sensor | Instantaneous Deviation Coefficient — CWT cross-correlation |
| SDC | Single-sensor | Signal Difference Coefficient — phase-aligned Pearson correlation |
| CSDI | Dual-sensor | Coherence-Based Signal Deviation Index — Welch coherence RMSD |
| MIEM | Dual-sensor | Matrix-based Instantaneous Energy Method — IDE cross-matrix deviation |
Single-sensor DIs require one signal per measurement (N, L).
Dual-sensor DIs require two simultaneous signals per measurement
(N, L, 2), where axis 2 holds the two sensor channels.
git clone https://github.com/Dongjin-Du/udwave.git
cd udwave
pip install -r requirements.txt
pip install -e .import numpy as np
from udwave import select_best_di, optimise_threshold, detect
# ref, damaged, unknown : (N, L) ndarrays
result = select_best_di(ref, damaged)
opt = optimise_threshold(result, w1=0.5, w2=0.5)
report = detect(ref, unknown,
di_name = result['best'],
threshold = opt['best_T'])
print(report['pipe_status']) # 'Damaged' or 'Undamaged'
print(report['pipe_probability']) # e.g. 0.82# ref, damaged, unknown : (N, L, 2) ndarrays
# axis 2 = [sensor_front, sensor_back]
t = np.arange(L) * (1 / sampling_frequency)
result = select_best_di(ref, damaged, t=t, fs=sampling_frequency)
opt = optimise_threshold(result, w1=0.75, w2=0.25)
report = detect(ref, unknown,
di_name = result['best'],
threshold = opt['best_T'],
t=t, fs=sampling_frequency)def my_di(ref, test):
# ref : (N, L), test : (M, L)
# must return (N, M) ndarray
...
result = select_best_di(ref, damaged, custom_dis={'MyDI': my_di})from udwave.plotting import plot_distribution, plot_eii, plot_detection
best = result['best']
plot_distribution(result['di_ref'][best], [result['di_test'][best]], best)
plot_eii(opt)
plot_detection(report)udwave/
├── src/
│ ├── __init__.py ← public API
│ ├── damage_indicators.py ← EMI, EDR, IDC, SDC, CSDI, MIEM
│ ├── select_di.py ← DI selection via overlap
│ ├── optimise_threshold.py ← EII-based threshold optimisation
│ ├── detect.py ← damage probability estimation
│ ├── signal_utils.py ← CSV loading, pulse extraction
│ └── plotting.py ← figures
├── tests/
│ └── test_udwave.py
├── examples/
│ ├── example_single_sensor.py
│ ├── example_dual_sensor.py
│ └── example_custom_di.py
├── docs/
│ └── api_reference.md
├── requirements.txt
├── setup.py
└── LICENSE
pip install pytest
pytest tests/ -vEach CSV file represents one measurement session:
| Column | Content |
|---|---|
| 0 | Trigger / reference channel (for pulse detection) |
| 1 | Sensor signal |
No header row. Use udwave.signal_utils to load:
from udwave.signal_utils import load_folder, signal_prepare, stack_sensors
# single-sensor
recordings = load_folder('path/to/csvs', start=410, end=500)
data = signal_prepare(recordings, s_length=90) # (N, 90)
# dual-sensor
s1 = load_folder('path/to/front', start=410, end=500)
s2 = load_folder('path/to/back', start=410, end=500)
data = stack_sensors(s1, s2, s_length=90) # (N, 90, 2)w1 controls the penalty for false positives (Type I error) and
w2 for missed detections (Type II error). Adjust based on the
consequences in your application:
# equal importance
opt = optimise_threshold(result, w1=0.5, w2=0.5)
# false positives are 3x more costly than missed detections
opt = optimise_threshold(result, w1=0.75, w2=0.25)
# missed detections are 3x more costly (safety-critical)
opt = optimise_threshold(result, w1=0.25, w2=0.75)MIT — see LICENSE.