tc-cwi 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tc-cwi might be problematic. Click here for more details.
- tc_cwi-0.1.0/LICENSE +21 -0
- tc_cwi-0.1.0/PKG-INFO +173 -0
- tc_cwi-0.1.0/README.md +151 -0
- tc_cwi-0.1.0/pyproject.toml +88 -0
- tc_cwi-0.1.0/pyproject.toml.orig +71 -0
- tc_cwi-0.1.0/src/tc_cwi/__init__.py +20 -0
- tc_cwi-0.1.0/src/tc_cwi/core.py +114 -0
- tc_cwi-0.1.0/src/tc_cwi/matlab.py +163 -0
tc_cwi-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zensor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tc_cwi-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tc-cwi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python toolbox to apply EOC compensation algorithms to ultrasonic testing data.
|
|
5
|
+
Keywords: coda wave interferometry,structural health monitoring,temperature compensation,time-stretching,ultrasonic
|
|
6
|
+
Author: Arnaud Deraemaeker (ULB), Damiano Gianotti (Zensor), Jasmien Hassanin (IZFP), Jitendra Kumar Sharma (ULB), Luigi Trezza (Airbus)
|
|
7
|
+
Author-email: Arnaud Deraemaeker (ULB) <Arnaud.Deraemaeker@ulb.be>, Damiano Gianotti (Zensor) <damiano.gianotti@zensor.be>, Jitendra Kumar Sharma (ULB) <jitendra.kumar.sharma@ulb.be>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: scipy
|
|
16
|
+
Maintainer: Zensor
|
|
17
|
+
Maintainer-email: Zensor <support@zensor.be>
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Project-URL: Repository, https://github.com/Zensor-BE/tc-cwi
|
|
20
|
+
Project-URL: Issues, https://github.com/Zensor-BE/tc-cwi/issues
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# `tc-cwi`
|
|
24
|
+
|
|
25
|
+
[](https://www.python.org/downloads/)
|
|
26
|
+
[](https://github.com/astral-sh/uv)
|
|
27
|
+
[](https://github.com/Zensor-BE/tc-cwi/blob/main/LICENSE)
|
|
28
|
+
|
|
29
|
+
Lightweight Python toolbox for **Environmental and Operational Condition (EoC) compensation** in ultrasonic Structural Health Monitoring (SHM), using **Coda Wave Interferometry (CWI)** and the **Time-Stretching method**.
|
|
30
|
+
|
|
31
|
+
Temperature variations change the apparent wave velocity of ultrasonic guided waves, which can mask structural damage. `tc-cwi` estimates the stretch factor (`eps`) and/or time shift (`dt`) that best realign a measured signal with a reference, and reports the remaining decorrelation.
|
|
32
|
+
|
|
33
|
+
Developed as part of WP3, Deliverable D3.3.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
Requirements: **Python ≥ 3.11** (dependencies: `numpy`, `scipy`)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install tc-cwi
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Quickstart
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import numpy as np
|
|
51
|
+
from tc_cwi import tc_cwi
|
|
52
|
+
|
|
53
|
+
t = np.linspace(0, 1e-3, 1000)
|
|
54
|
+
reference = np.sin(2 * np.pi * 50e3 * t)
|
|
55
|
+
current = np.sin(2 * np.pi * 50e3 * t * (1 + 1e-4)) # slightly stretched
|
|
56
|
+
|
|
57
|
+
# Correlation coefficient without compensation (one value per signal)
|
|
58
|
+
baseline = tc_cwi("getccns", reference, current)
|
|
59
|
+
print(f"Baseline CC: {baseline.CC[0]:.4f}")
|
|
60
|
+
|
|
61
|
+
# Optimal stretch factor (Nelder-Mead from eps = 0) and remaining decorrelation
|
|
62
|
+
result = tc_cwi("findeps", reference, current, t, 0.0, 2)
|
|
63
|
+
print(f"eps: {result.eps:.6e}, decorrelation after stretching: {result.DC:.2e}")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Several signals can be processed at once by passing a 2D array with **one signal per column** (`n_samples × n_signals`):
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
signals = np.column_stack([current, current, current])
|
|
70
|
+
batch = tc_cwi("findeps", reference, signals, t, (-1e-2, 1e-2)) # bounded search
|
|
71
|
+
print(batch.EPSf, batch.DCsf)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## API
|
|
77
|
+
|
|
78
|
+
The package exposes two layers.
|
|
79
|
+
|
|
80
|
+
### MATLAB-compatible dispatch: `tc_cwi(cmd, *args, **kwargs)`
|
|
81
|
+
|
|
82
|
+
Mirrors the original MATLAB toolbox. Results are returned as a `Struct` (`types.SimpleNamespace`), so fields are accessed MATLAB-style with `out.field`.
|
|
83
|
+
|
|
84
|
+
| Command | Arguments | Output fields (single signal / multiple signals) |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `getccns` | `y0, y1, ind=None` | `CC`, `DC` |
|
|
87
|
+
| `getccs` | `y0, y1, t, eps, ind=None` | `CC`, `DC` |
|
|
88
|
+
| `getccdt` | `y0, y1, t, dt, ind=None` | `CC`, `DC` |
|
|
89
|
+
| `getccepsdt` | `y0, y1, t, eps, dt, ind=None` | `CC`, `DC` |
|
|
90
|
+
| `getdcs` / `getdcdt` / `getdcepsdt` | same as `getcc*` | `DC` array (returned directly) |
|
|
91
|
+
| `findeps` | `y1, y2, t, e0, method=1, ind=None` | `eps, DC, y, y0` / `EPSf, DCsf, ysf, y0` |
|
|
92
|
+
| `finddt` | `y1, y2, t, dt0, method=1, ind=None` | `dt, DC, y, y0` / `DTf, DCdtf, ydtf, y0` |
|
|
93
|
+
| `findepsdt` | `y1, y2, t, e0, dt0, method=2, ind=None` | `eps, dt, DC, y, y0` / `EPSf, DTf, DCsdtf, ysdtf, y0` |
|
|
94
|
+
| `findeps -ma` | `y1, y2, t, e0, method=1, ind=None, ma=5` | multiple signals only: `EPSma, DCma, ysma, y0, yma` |
|
|
95
|
+
| `stretch` | `y, t, eps` | stretched signal |
|
|
96
|
+
| `shift` | `y, t, dt, method="interp"` | shifted signal (`"point"`, `"interp"` or `"fft"`) |
|
|
97
|
+
| `stretchshift` | `y, t, eps, dt` | shifted, then stretched signal |
|
|
98
|
+
|
|
99
|
+
Conventions:
|
|
100
|
+
|
|
101
|
+
- **`CC`** is the absolute Pearson correlation coefficient, **`DC = 1 - CC`** the decorrelation.
|
|
102
|
+
- **`ind`** restricts the correlation to a window (index array or slice). The stretch/shift is always applied to the full signal.
|
|
103
|
+
- **`method`** selects the optimizer:
|
|
104
|
+
- `1`: bounded scalar search, the start value is an interval `(lower, upper)`.
|
|
105
|
+
- `2`: Nelder-Mead, the start value is a starting point. `findepsdt` only supports this method.
|
|
106
|
+
|
|
107
|
+
When processing several signals, each fit is seeded with the result of the previous one.
|
|
108
|
+
- **`eps > 0`** compresses the wave (`t → t·(1+eps)`), **`dt > 0`** moves it forward (`t → t−dt`).
|
|
109
|
+
- **`findeps -ma`** compares each signal against the mean of the last `ma` compensated signals instead of a fixed reference. `ma` is either a window size, or the `yma` returned by a previous call to continue the same window.
|
|
110
|
+
|
|
111
|
+
### Core functions
|
|
112
|
+
|
|
113
|
+
For direct use in Python code, without the MATLAB-style dispatch:
|
|
114
|
+
|
|
115
|
+
| Function | Description |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `correlation_coefficient(y0, y1)` | Absolute correlation coefficient between two signals |
|
|
118
|
+
| `stretch(y, t, eps)` | Stretch a signal (not-a-knot cubic spline, zero outside `t`) |
|
|
119
|
+
| `shift(y, t, dt, method="interp")` | Shift a signal by `dt` |
|
|
120
|
+
| `stretchshift(y, t, eps, dt)` | Shift, then stretch a signal |
|
|
121
|
+
| `find_optimum(y0, y, t, process, x0, method=1, ind=None, xatol=1e-8)` | Parameters of `process(y, t, *x)` minimizing the decorrelation with `y0`; returns `(x, decorrelation)` |
|
|
122
|
+
| `MovingAverageReference(n, y_init=None)` | Rolling window of the last `n` signals; `.reference` is their mean, `.update(y)` adds a signal |
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from tc_cwi import find_optimum, stretch
|
|
126
|
+
|
|
127
|
+
x, decorrelation = find_optimum(reference, current, t, stretch, (-1e-2, 1e-2))
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Development
|
|
133
|
+
|
|
134
|
+
This project uses [`uv`](https://github.com/astral-sh/uv) with the `uv_build` backend:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
git clone https://github.com/Zensor-BE/tc-cwi.git
|
|
138
|
+
cd tc-cwi
|
|
139
|
+
uv sync
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```text
|
|
143
|
+
├── src/tc_cwi/
|
|
144
|
+
│ ├── __init__.py # Public API
|
|
145
|
+
│ ├── core.py # Stretching, shifting and optimization
|
|
146
|
+
│ └── matlab.py # MATLAB-compatible tc_cwi() dispatch
|
|
147
|
+
├── tests/
|
|
148
|
+
│ ├── test_core.py # Core functions
|
|
149
|
+
│ ├── test_matlab.py # Every tc_cwi command: fields and error paths
|
|
150
|
+
│ └── test_package.py # Public API
|
|
151
|
+
└── pyproject.toml
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Tests, coverage and linting:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
uv run pytest
|
|
158
|
+
uv run pytest --cov # statements + branches, with the missing lines
|
|
159
|
+
uv run ruff check .
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Releasing to PyPI: bump `version` in `pyproject.toml`, then push a matching tag. The [publish workflow](https://github.com/Zensor-BE/tc-cwi/blob/main/.github/workflows/publish.yml) lints, builds, tests the built distributions and publishes them.
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
git tag -a v0.1.0 -m v0.1.0
|
|
166
|
+
git push origin v0.1.0
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
[MIT](https://github.com/Zensor-BE/tc-cwi/blob/main/LICENSE)
|
tc_cwi-0.1.0/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# `tc-cwi`
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org/downloads/)
|
|
4
|
+
[](https://github.com/astral-sh/uv)
|
|
5
|
+
[](https://github.com/Zensor-BE/tc-cwi/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
Lightweight Python toolbox for **Environmental and Operational Condition (EoC) compensation** in ultrasonic Structural Health Monitoring (SHM), using **Coda Wave Interferometry (CWI)** and the **Time-Stretching method**.
|
|
8
|
+
|
|
9
|
+
Temperature variations change the apparent wave velocity of ultrasonic guided waves, which can mask structural damage. `tc-cwi` estimates the stretch factor (`eps`) and/or time shift (`dt`) that best realign a measured signal with a reference, and reports the remaining decorrelation.
|
|
10
|
+
|
|
11
|
+
Developed as part of WP3, Deliverable D3.3.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Requirements: **Python ≥ 3.11** (dependencies: `numpy`, `scipy`)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install tc-cwi
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Quickstart
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import numpy as np
|
|
29
|
+
from tc_cwi import tc_cwi
|
|
30
|
+
|
|
31
|
+
t = np.linspace(0, 1e-3, 1000)
|
|
32
|
+
reference = np.sin(2 * np.pi * 50e3 * t)
|
|
33
|
+
current = np.sin(2 * np.pi * 50e3 * t * (1 + 1e-4)) # slightly stretched
|
|
34
|
+
|
|
35
|
+
# Correlation coefficient without compensation (one value per signal)
|
|
36
|
+
baseline = tc_cwi("getccns", reference, current)
|
|
37
|
+
print(f"Baseline CC: {baseline.CC[0]:.4f}")
|
|
38
|
+
|
|
39
|
+
# Optimal stretch factor (Nelder-Mead from eps = 0) and remaining decorrelation
|
|
40
|
+
result = tc_cwi("findeps", reference, current, t, 0.0, 2)
|
|
41
|
+
print(f"eps: {result.eps:.6e}, decorrelation after stretching: {result.DC:.2e}")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Several signals can be processed at once by passing a 2D array with **one signal per column** (`n_samples × n_signals`):
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
signals = np.column_stack([current, current, current])
|
|
48
|
+
batch = tc_cwi("findeps", reference, signals, t, (-1e-2, 1e-2)) # bounded search
|
|
49
|
+
print(batch.EPSf, batch.DCsf)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## API
|
|
55
|
+
|
|
56
|
+
The package exposes two layers.
|
|
57
|
+
|
|
58
|
+
### MATLAB-compatible dispatch: `tc_cwi(cmd, *args, **kwargs)`
|
|
59
|
+
|
|
60
|
+
Mirrors the original MATLAB toolbox. Results are returned as a `Struct` (`types.SimpleNamespace`), so fields are accessed MATLAB-style with `out.field`.
|
|
61
|
+
|
|
62
|
+
| Command | Arguments | Output fields (single signal / multiple signals) |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| `getccns` | `y0, y1, ind=None` | `CC`, `DC` |
|
|
65
|
+
| `getccs` | `y0, y1, t, eps, ind=None` | `CC`, `DC` |
|
|
66
|
+
| `getccdt` | `y0, y1, t, dt, ind=None` | `CC`, `DC` |
|
|
67
|
+
| `getccepsdt` | `y0, y1, t, eps, dt, ind=None` | `CC`, `DC` |
|
|
68
|
+
| `getdcs` / `getdcdt` / `getdcepsdt` | same as `getcc*` | `DC` array (returned directly) |
|
|
69
|
+
| `findeps` | `y1, y2, t, e0, method=1, ind=None` | `eps, DC, y, y0` / `EPSf, DCsf, ysf, y0` |
|
|
70
|
+
| `finddt` | `y1, y2, t, dt0, method=1, ind=None` | `dt, DC, y, y0` / `DTf, DCdtf, ydtf, y0` |
|
|
71
|
+
| `findepsdt` | `y1, y2, t, e0, dt0, method=2, ind=None` | `eps, dt, DC, y, y0` / `EPSf, DTf, DCsdtf, ysdtf, y0` |
|
|
72
|
+
| `findeps -ma` | `y1, y2, t, e0, method=1, ind=None, ma=5` | multiple signals only: `EPSma, DCma, ysma, y0, yma` |
|
|
73
|
+
| `stretch` | `y, t, eps` | stretched signal |
|
|
74
|
+
| `shift` | `y, t, dt, method="interp"` | shifted signal (`"point"`, `"interp"` or `"fft"`) |
|
|
75
|
+
| `stretchshift` | `y, t, eps, dt` | shifted, then stretched signal |
|
|
76
|
+
|
|
77
|
+
Conventions:
|
|
78
|
+
|
|
79
|
+
- **`CC`** is the absolute Pearson correlation coefficient, **`DC = 1 - CC`** the decorrelation.
|
|
80
|
+
- **`ind`** restricts the correlation to a window (index array or slice). The stretch/shift is always applied to the full signal.
|
|
81
|
+
- **`method`** selects the optimizer:
|
|
82
|
+
- `1`: bounded scalar search, the start value is an interval `(lower, upper)`.
|
|
83
|
+
- `2`: Nelder-Mead, the start value is a starting point. `findepsdt` only supports this method.
|
|
84
|
+
|
|
85
|
+
When processing several signals, each fit is seeded with the result of the previous one.
|
|
86
|
+
- **`eps > 0`** compresses the wave (`t → t·(1+eps)`), **`dt > 0`** moves it forward (`t → t−dt`).
|
|
87
|
+
- **`findeps -ma`** compares each signal against the mean of the last `ma` compensated signals instead of a fixed reference. `ma` is either a window size, or the `yma` returned by a previous call to continue the same window.
|
|
88
|
+
|
|
89
|
+
### Core functions
|
|
90
|
+
|
|
91
|
+
For direct use in Python code, without the MATLAB-style dispatch:
|
|
92
|
+
|
|
93
|
+
| Function | Description |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `correlation_coefficient(y0, y1)` | Absolute correlation coefficient between two signals |
|
|
96
|
+
| `stretch(y, t, eps)` | Stretch a signal (not-a-knot cubic spline, zero outside `t`) |
|
|
97
|
+
| `shift(y, t, dt, method="interp")` | Shift a signal by `dt` |
|
|
98
|
+
| `stretchshift(y, t, eps, dt)` | Shift, then stretch a signal |
|
|
99
|
+
| `find_optimum(y0, y, t, process, x0, method=1, ind=None, xatol=1e-8)` | Parameters of `process(y, t, *x)` minimizing the decorrelation with `y0`; returns `(x, decorrelation)` |
|
|
100
|
+
| `MovingAverageReference(n, y_init=None)` | Rolling window of the last `n` signals; `.reference` is their mean, `.update(y)` adds a signal |
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from tc_cwi import find_optimum, stretch
|
|
104
|
+
|
|
105
|
+
x, decorrelation = find_optimum(reference, current, t, stretch, (-1e-2, 1e-2))
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
This project uses [`uv`](https://github.com/astral-sh/uv) with the `uv_build` backend:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
git clone https://github.com/Zensor-BE/tc-cwi.git
|
|
116
|
+
cd tc-cwi
|
|
117
|
+
uv sync
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
├── src/tc_cwi/
|
|
122
|
+
│ ├── __init__.py # Public API
|
|
123
|
+
│ ├── core.py # Stretching, shifting and optimization
|
|
124
|
+
│ └── matlab.py # MATLAB-compatible tc_cwi() dispatch
|
|
125
|
+
├── tests/
|
|
126
|
+
│ ├── test_core.py # Core functions
|
|
127
|
+
│ ├── test_matlab.py # Every tc_cwi command: fields and error paths
|
|
128
|
+
│ └── test_package.py # Public API
|
|
129
|
+
└── pyproject.toml
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Tests, coverage and linting:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
uv run pytest
|
|
136
|
+
uv run pytest --cov # statements + branches, with the missing lines
|
|
137
|
+
uv run ruff check .
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Releasing to PyPI: bump `version` in `pyproject.toml`, then push a matching tag. The [publish workflow](https://github.com/Zensor-BE/tc-cwi/blob/main/.github/workflows/publish.yml) lints, builds, tests the built distributions and publishes them.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
git tag -a v0.1.0 -m v0.1.0
|
|
144
|
+
git push origin v0.1.0
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
[MIT](https://github.com/Zensor-BE/tc-cwi/blob/main/LICENSE)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.13.1,<0.14.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tc-cwi"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python toolbox to apply EOC compensation algorithms to ultrasonic testing data."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
keywords = [
|
|
13
|
+
"coda wave interferometry",
|
|
14
|
+
"structural health monitoring",
|
|
15
|
+
"temperature compensation",
|
|
16
|
+
"time-stretching",
|
|
17
|
+
"ultrasonic",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Intended Audience :: Science/Research",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
24
|
+
]
|
|
25
|
+
requires-python = ">=3.11"
|
|
26
|
+
dependencies = [
|
|
27
|
+
"numpy",
|
|
28
|
+
"scipy",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[[project.authors]]
|
|
32
|
+
name = "Arnaud Deraemaeker (ULB)"
|
|
33
|
+
email = "Arnaud.Deraemaeker@ulb.be"
|
|
34
|
+
|
|
35
|
+
[[project.authors]]
|
|
36
|
+
name = "Damiano Gianotti (Zensor)"
|
|
37
|
+
email = "damiano.gianotti@zensor.be"
|
|
38
|
+
|
|
39
|
+
[[project.authors]]
|
|
40
|
+
name = "Jasmien Hassanin (IZFP)"
|
|
41
|
+
|
|
42
|
+
[[project.authors]]
|
|
43
|
+
name = "Jitendra Kumar Sharma (ULB)"
|
|
44
|
+
email = "jitendra.kumar.sharma@ulb.be"
|
|
45
|
+
|
|
46
|
+
[[project.authors]]
|
|
47
|
+
name = "Luigi Trezza (Airbus)"
|
|
48
|
+
|
|
49
|
+
[[project.maintainers]]
|
|
50
|
+
name = "Zensor"
|
|
51
|
+
email = "support@zensor.be"
|
|
52
|
+
|
|
53
|
+
[project.urls]
|
|
54
|
+
Repository = "https://github.com/Zensor-BE/tc-cwi"
|
|
55
|
+
Issues = "https://github.com/Zensor-BE/tc-cwi/issues"
|
|
56
|
+
|
|
57
|
+
[dependency-groups]
|
|
58
|
+
dev = [
|
|
59
|
+
"pytest",
|
|
60
|
+
"pytest-cov",
|
|
61
|
+
"ruff",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
target-version = "py311"
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint]
|
|
68
|
+
select = [
|
|
69
|
+
"E4",
|
|
70
|
+
"E7",
|
|
71
|
+
"E9",
|
|
72
|
+
"F",
|
|
73
|
+
"B",
|
|
74
|
+
"I",
|
|
75
|
+
"UP",
|
|
76
|
+
"C4",
|
|
77
|
+
"FURB122",
|
|
78
|
+
"RUF015",
|
|
79
|
+
"RUF022",
|
|
80
|
+
"RUF046",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[tool.coverage.run]
|
|
84
|
+
source = ["tc_cwi"]
|
|
85
|
+
branch = true
|
|
86
|
+
|
|
87
|
+
[tool.coverage.report]
|
|
88
|
+
show_missing = true
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.13.1,<0.14.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tc-cwi"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python toolbox to apply EOC compensation algorithms to ultrasonic testing data."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [
|
|
13
|
+
{ name="Arnaud Deraemaeker (ULB)", email="Arnaud.Deraemaeker@ulb.be" },
|
|
14
|
+
{ name="Damiano Gianotti (Zensor)", email="damiano.gianotti@zensor.be" },
|
|
15
|
+
{ name="Jasmien Hassanin (IZFP)" },
|
|
16
|
+
{ name="Jitendra Kumar Sharma (ULB)", email="jitendra.kumar.sharma@ulb.be" },
|
|
17
|
+
{ name="Luigi Trezza (Airbus)" },
|
|
18
|
+
]
|
|
19
|
+
maintainers = [
|
|
20
|
+
{ name="Zensor", email="support@zensor.be" },
|
|
21
|
+
]
|
|
22
|
+
keywords = [
|
|
23
|
+
"coda wave interferometry",
|
|
24
|
+
"structural health monitoring",
|
|
25
|
+
"temperature compensation",
|
|
26
|
+
"time-stretching",
|
|
27
|
+
"ultrasonic",
|
|
28
|
+
]
|
|
29
|
+
classifiers = [
|
|
30
|
+
"Intended Audience :: Science/Research",
|
|
31
|
+
"Operating System :: OS Independent",
|
|
32
|
+
"Programming Language :: Python :: 3",
|
|
33
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
34
|
+
]
|
|
35
|
+
requires-python = ">=3.11"
|
|
36
|
+
dependencies = [
|
|
37
|
+
"numpy",
|
|
38
|
+
"scipy",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Repository = "https://github.com/Zensor-BE/tc-cwi"
|
|
43
|
+
Issues = "https://github.com/Zensor-BE/tc-cwi/issues"
|
|
44
|
+
|
|
45
|
+
[dependency-groups]
|
|
46
|
+
dev = [
|
|
47
|
+
"pytest",
|
|
48
|
+
"pytest-cov",
|
|
49
|
+
"ruff",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
# Explicit rule set: ruff's implicit defaults grow between releases, so this gate
|
|
53
|
+
# spells out what it checks and must not start failing on an unrelated `uv sync`.
|
|
54
|
+
# SIM is kept out on purpose (it prefers `T[-1] < T * 1.2` over `T * 1.2 > T[-1]`).
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
target-version = "py311"
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint]
|
|
59
|
+
select = [
|
|
60
|
+
"E4", "E7", "E9", "F", # pycodestyle (errors) + pyflakes
|
|
61
|
+
"B", # bugbear
|
|
62
|
+
"I", "UP", "C4", # import order, modern syntax, comprehensions
|
|
63
|
+
"FURB122", "RUF015", "RUF022", "RUF046", # the few specific rules worth their noise
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
[tool.coverage.run]
|
|
67
|
+
source = ["tc_cwi"]
|
|
68
|
+
branch = true
|
|
69
|
+
|
|
70
|
+
[tool.coverage.report]
|
|
71
|
+
show_missing = true
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .core import (
|
|
2
|
+
MovingAverageReference,
|
|
3
|
+
correlation_coefficient,
|
|
4
|
+
find_optimum,
|
|
5
|
+
shift,
|
|
6
|
+
stretch,
|
|
7
|
+
stretchshift,
|
|
8
|
+
)
|
|
9
|
+
from .matlab import Struct, tc_cwi
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"MovingAverageReference",
|
|
13
|
+
"Struct",
|
|
14
|
+
"correlation_coefficient",
|
|
15
|
+
"find_optimum",
|
|
16
|
+
"shift",
|
|
17
|
+
"stretch",
|
|
18
|
+
"stretchshift",
|
|
19
|
+
"tc_cwi",
|
|
20
|
+
]
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from collections import deque
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from scipy.interpolate import interp1d
|
|
7
|
+
from scipy.optimize import minimize, minimize_scalar
|
|
8
|
+
|
|
9
|
+
FloatDType = np.dtype[np.float64]
|
|
10
|
+
|
|
11
|
+
Array1D = np.ndarray[tuple[int], FloatDType]
|
|
12
|
+
Array2D = np.ndarray[tuple[int, int], FloatDType]
|
|
13
|
+
IndexArray = np.ndarray[tuple[int], np.dtype[np.int_]]
|
|
14
|
+
|
|
15
|
+
ShiftMethodOptions = Literal["point", "interp", "fft"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _interp1d(y: Array1D, t: Array1D, t_new: Array1D) -> Array1D:
|
|
19
|
+
"""Not-a-knot cubic spline (MATLAB 'spline'), 0 outside of t."""
|
|
20
|
+
return interp1d(
|
|
21
|
+
t,
|
|
22
|
+
y,
|
|
23
|
+
kind="cubic",
|
|
24
|
+
bounds_error=False,
|
|
25
|
+
fill_value=0.0,
|
|
26
|
+
assume_sorted=True,
|
|
27
|
+
)(t_new)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def correlation_coefficient(y0: Array1D, y1: Array1D) -> float:
|
|
31
|
+
return abs(np.corrcoef(y0, y1)[0, 1])
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def stretch(y: Array1D, t: Array1D, eps: float) -> Array1D:
|
|
35
|
+
"""Stretch the full signal, t -> t*(1+eps). eps > 0 compresses the wave."""
|
|
36
|
+
return _interp1d(y, t, t * (1 + eps))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def shift(
|
|
40
|
+
y: Array1D, t: Array1D, dt: float, method: ShiftMethodOptions = "interp"
|
|
41
|
+
) -> Array1D:
|
|
42
|
+
"""Shift the full signal, t -> t-dt. dt > 0 moves the wave forward."""
|
|
43
|
+
if method == "point": # round half away from zero, like MATLAB
|
|
44
|
+
n = int(np.copysign(np.floor(abs(dt / (t[1] - t[0])) + 0.5), dt))
|
|
45
|
+
return np.roll(y, n)
|
|
46
|
+
if method == "interp":
|
|
47
|
+
return _interp1d(y, t, t - dt)
|
|
48
|
+
if method == "fft":
|
|
49
|
+
freqs = np.fft.fftfreq(len(y), d=t[1] - t[0])
|
|
50
|
+
return np.fft.ifft(np.fft.fft(y) * np.exp(-2j * np.pi * freqs * dt)).real
|
|
51
|
+
raise ValueError(f"Unknown shift method: {method!r}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def stretchshift(y: Array1D, t: Array1D, eps: float, dt: float) -> Array1D:
|
|
55
|
+
"""Shift (interp) then stretch."""
|
|
56
|
+
return stretch(shift(y, t, dt), t, eps)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def find_optimum(
|
|
60
|
+
y0: Array1D,
|
|
61
|
+
y: Array1D,
|
|
62
|
+
t: Array1D,
|
|
63
|
+
process: Callable[..., Array1D],
|
|
64
|
+
x0: float | Array1D,
|
|
65
|
+
method: Literal[1, 2] = 1,
|
|
66
|
+
ind: IndexArray | slice | None = None,
|
|
67
|
+
xatol: float = 1e-8,
|
|
68
|
+
) -> tuple[Array1D, float]:
|
|
69
|
+
"""
|
|
70
|
+
Find the parameters x minimizing the decorrelation (1 - CC) between y0 and
|
|
71
|
+
process(y, t, *x). Correlation is computed over `ind` only; process is
|
|
72
|
+
applied to the full signal. Returns (x, decorrelation).
|
|
73
|
+
|
|
74
|
+
- method 1: bounded scalar minimization, x0 = (lower, upper)
|
|
75
|
+
- method 2: Nelder-Mead, x0 = starting point (one value per parameter)
|
|
76
|
+
"""
|
|
77
|
+
ind = slice(None) if ind is None else ind
|
|
78
|
+
|
|
79
|
+
def decorrelation(x) -> float:
|
|
80
|
+
processed = process(y, t, *np.atleast_1d(x))
|
|
81
|
+
return 1 - correlation_coefficient(y0[ind], processed[ind])
|
|
82
|
+
|
|
83
|
+
if method == 1:
|
|
84
|
+
res = minimize_scalar(
|
|
85
|
+
decorrelation,
|
|
86
|
+
bounds=tuple(x0),
|
|
87
|
+
method="bounded",
|
|
88
|
+
options={"xatol": xatol},
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
res = minimize(
|
|
92
|
+
decorrelation,
|
|
93
|
+
np.atleast_1d(x0),
|
|
94
|
+
method="Nelder-Mead",
|
|
95
|
+
options={"xatol": xatol, "fatol": 1e-4},
|
|
96
|
+
)
|
|
97
|
+
return np.atleast_1d(res.x), res.fun
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class MovingAverageReference:
|
|
101
|
+
"""Rolling window of the last `n` signals; their mean is the reference."""
|
|
102
|
+
|
|
103
|
+
def __init__(self, n: int, y_init: Array1D | None = None):
|
|
104
|
+
self.n = n
|
|
105
|
+
self.signals: deque[Array1D] = deque(maxlen=n)
|
|
106
|
+
if y_init is not None:
|
|
107
|
+
self.signals.append(y_init)
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def reference(self) -> Array1D:
|
|
111
|
+
return np.mean(self.signals, axis=0)
|
|
112
|
+
|
|
113
|
+
def update(self, y: Array1D) -> None:
|
|
114
|
+
self.signals.append(y) # the oldest signal is dropped when full
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tc_cwi: MATLAB-compatible API
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from types import SimpleNamespace as Struct
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from .core import (
|
|
10
|
+
MovingAverageReference,
|
|
11
|
+
correlation_coefficient,
|
|
12
|
+
find_optimum,
|
|
13
|
+
shift,
|
|
14
|
+
stretch,
|
|
15
|
+
stretchshift,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _getcc(process, y0, y1, *params, ind=None) -> Struct:
|
|
20
|
+
"""out.CC / out.DC; process(column, *params) is applied on the full signal,
|
|
21
|
+
correlation on ind only; params may be scalars or one value per column."""
|
|
22
|
+
ind = slice(None) if ind is None else ind
|
|
23
|
+
columns = y1[:, None] if y1.ndim == 1 else y1
|
|
24
|
+
params = [np.broadcast_to(p, columns.shape[1]) for p in params]
|
|
25
|
+
cc = np.array(
|
|
26
|
+
[
|
|
27
|
+
correlation_coefficient(
|
|
28
|
+
y0[ind], process(columns[:, i], *(p[i] for p in params))[ind]
|
|
29
|
+
)
|
|
30
|
+
for i in range(columns.shape[1])
|
|
31
|
+
]
|
|
32
|
+
)
|
|
33
|
+
return Struct(CC=cc, DC=1 - cc)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def getccns(y0, y1, ind=None):
|
|
37
|
+
return _getcc(lambda y: y, y0, y1, ind=ind)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def getccs(y0, y1, t, eps, ind=None):
|
|
41
|
+
return _getcc(lambda y, e: stretch(y, t, e), y0, y1, eps, ind=ind)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def getccdt(y0, y1, t, dt, ind=None):
|
|
45
|
+
return _getcc(lambda y, d: shift(y, t, d), y0, y1, dt, ind=ind)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def getccepsdt(y0, y1, t, eps, dt, ind=None):
|
|
49
|
+
return _getcc(lambda y, e, d: stretchshift(y, t, e, d), y0, y1, eps, dt, ind=ind)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def getdcs(y0, y1, t, eps, ind=None):
|
|
53
|
+
return getccs(y0, y1, t, eps, ind).DC
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def getdcdt(y0, y1, t, dt, ind=None):
|
|
57
|
+
return getccdt(y0, y1, t, dt, ind).DC
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def getdcepsdt(y0, y1, t, eps, dt, ind=None):
|
|
61
|
+
return getccepsdt(y0, y1, t, eps, dt, ind).DC
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _find(y1, y2, t, process, x0, method, ind, xatol):
|
|
65
|
+
"""Fit one signal or each column of y2 in turn. The starting point of each
|
|
66
|
+
fit comes from the previous one (method 1: interval re-centered on it).
|
|
67
|
+
Returns xs (n, n_params), DCs (n,) and processed signals (N, n)."""
|
|
68
|
+
x0 = np.asarray(x0, dtype=float)
|
|
69
|
+
xs, dcs, ys = [], [], []
|
|
70
|
+
for y in (y2[:, None] if y2.ndim == 1 else y2).T:
|
|
71
|
+
x, dc = find_optimum(y1, y, t, process, x0, method, ind, xatol)
|
|
72
|
+
x0 = x0 - x0.mean() + x if method == 1 else x
|
|
73
|
+
xs.append(x), dcs.append(dc), ys.append(process(y, t, *x))
|
|
74
|
+
return np.array(xs), np.array(dcs), np.array(ys).T
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def findeps(y1, y2, t, e0, method=1, ind=None):
|
|
78
|
+
xs, dcs, ys = _find(y1, y2, t, stretch, e0, method, ind, 1e-8)
|
|
79
|
+
if y2.ndim == 1:
|
|
80
|
+
return Struct(eps=xs[0, 0], DC=dcs[0], y=ys[:, 0], y0=y1)
|
|
81
|
+
return Struct(DCsf=dcs, EPSf=xs[:, 0], ysf=ys, y0=y1)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def finddt(y1, y2, t, dt0, method=1, ind=None):
|
|
85
|
+
xs, dcs, ys = _find(y1, y2, t, shift, dt0, method, ind, 1e-10)
|
|
86
|
+
if y2.ndim == 1:
|
|
87
|
+
return Struct(dt=xs[0, 0], DC=dcs[0], y=ys[:, 0], y0=y1)
|
|
88
|
+
return Struct(DCdtf=dcs, DTf=xs[:, 0], ydtf=ys, y0=y1)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def findepsdt(y1, y2, t, e0, dt0, method=2, ind=None):
|
|
92
|
+
if method == 1:
|
|
93
|
+
raise ValueError("fminbnd is single variable, use method 2.")
|
|
94
|
+
x0 = [e0, dt0]
|
|
95
|
+
xs, dcs, ys = _find(y1, y2, t, stretchshift, x0, method, ind, 1e-8)
|
|
96
|
+
if y2.ndim == 1:
|
|
97
|
+
return Struct(eps=xs[0, 0], dt=xs[0, 1], DC=dcs[0], y=ys[:, 0], y0=y1)
|
|
98
|
+
return Struct(DCsdtf=dcs, DTf=xs[:, 1], EPSf=xs[:, 0], ysdtf=ys, y0=y1)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def findeps_ma(y1, y2, t, e0, method=1, ind=None, ma: MovingAverageReference | int = 5):
|
|
102
|
+
"""
|
|
103
|
+
ma: window size n (MATLAB ma.init=true, window seeded with y1) or the
|
|
104
|
+
out.yma of a previous call (ma.init=false; y1 is then the first reference).
|
|
105
|
+
"""
|
|
106
|
+
if y2.ndim == 1:
|
|
107
|
+
raise ValueError("not for single signal")
|
|
108
|
+
if isinstance(ma, int):
|
|
109
|
+
ma = MovingAverageReference(ma, y1)
|
|
110
|
+
|
|
111
|
+
x = np.asarray(e0, dtype=float)
|
|
112
|
+
dc, eps = np.zeros(y2.shape[1]), np.zeros(y2.shape[1])
|
|
113
|
+
ys, refs = np.zeros_like(y2), np.zeros_like(y2)
|
|
114
|
+
y0 = y1
|
|
115
|
+
for i in range(y2.shape[1]):
|
|
116
|
+
res = findeps(y0, y2[:, i], t, x, method, ind)
|
|
117
|
+
eps[i], dc[i], ys[:, i] = res.eps, res.DC, res.y
|
|
118
|
+
x = x - x.mean() + eps[i] if method == 1 else np.atleast_1d(eps[i])
|
|
119
|
+
ma.update(ys[:, i])
|
|
120
|
+
y0 = refs[:, i] = ma.reference
|
|
121
|
+
return Struct(DCma=dc, EPSma=eps, ysma=ys, y0=refs, yma=ma)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def tc_cwi(cmd: str, *args, **kwargs):
|
|
125
|
+
"""
|
|
126
|
+
Time-Stretching and Coda Wave Interferometry (CWI) command dispatch.
|
|
127
|
+
|
|
128
|
+
Returns a Struct (SimpleNamespace) supporting attribute access (out.field),
|
|
129
|
+
mirroring MATLAB struct behavior.
|
|
130
|
+
|
|
131
|
+
Commands
|
|
132
|
+
--------
|
|
133
|
+
'getccns' - Cross-correlation without stretching.
|
|
134
|
+
'getccs' - Cross-correlation after stretching.
|
|
135
|
+
'getccdt' - Cross-correlation after time shift.
|
|
136
|
+
'getccepsdt' - Cross-correlation after stretch and time shift.
|
|
137
|
+
'findeps' - Find optimal stretch factor eps minimising decorrelation.
|
|
138
|
+
'findeps -ma' - Find optimal stretch factor eps using moving-average reference.
|
|
139
|
+
'finddt' - Find optimal time shift dt minimising decorrelation.
|
|
140
|
+
'findepsdt' - Joint optimization for eps and dt.
|
|
141
|
+
'stretch' - Apply stretch factor eps to signal(s).
|
|
142
|
+
'shift' - Apply time shift dt to signal(s).
|
|
143
|
+
'stretchshift' - Apply time shift then stretch to signal(s).
|
|
144
|
+
"""
|
|
145
|
+
dispatch = {
|
|
146
|
+
"finddt": finddt,
|
|
147
|
+
"findeps": findeps,
|
|
148
|
+
"findeps -ma": findeps_ma,
|
|
149
|
+
"findepsdt": findepsdt,
|
|
150
|
+
"getccdt": getccdt,
|
|
151
|
+
"getccepsdt": getccepsdt,
|
|
152
|
+
"getccns": getccns,
|
|
153
|
+
"getccs": getccs,
|
|
154
|
+
"getdcdt": getdcdt,
|
|
155
|
+
"getdcepsdt": getdcepsdt,
|
|
156
|
+
"getdcs": getdcs,
|
|
157
|
+
"shift": shift,
|
|
158
|
+
"stretch": stretch,
|
|
159
|
+
"stretchshift": stretchshift,
|
|
160
|
+
}
|
|
161
|
+
if cmd not in dispatch:
|
|
162
|
+
raise ValueError(f"Unknown command: '{cmd}'")
|
|
163
|
+
return dispatch[cmd](*args, **kwargs)
|