Pure NumPy Haar wavelet transforms: decomposition, reconstruction, denoising.
DWTResult
dataclass
Multi-level Haar decomposition of a finite series.
Source code in src/cds2/wavelets.py
| @dataclass(frozen=True)
class DWTResult:
"""Multi-level Haar decomposition of a finite series."""
approximations: list[FloatArray]
details: list[FloatArray]
original_length: int
|
DenoiseResult
dataclass
Outcome of wavelet shrinkage denoising.
Source code in src/cds2/wavelets.py
109
110
111
112
113
114
115 | @dataclass(frozen=True)
class DenoiseResult:
"""Outcome of wavelet shrinkage denoising."""
data: FloatArray
thresholded_counts: int
levels: int
|
haar_dwt(signal, normalize=True)
Single-level Haar decomposition into pairwise averages and differences.
Source code in src/cds2/wavelets.py
33
34
35
36
37
38
39
40
41
42
43
44
45 | def haar_dwt(
signal: Sequence[float] | FloatArray, normalize: bool = True
) -> tuple[FloatArray, FloatArray]:
"""Single-level Haar decomposition into pairwise averages and differences."""
series = _as_signal(signal)
if series.size % 2 != 0:
msg = "signal length must be even"
raise ValueError(msg)
pairs = series.reshape(-1, 2)
scale = np.sqrt(2.0) if normalize else 2.0
approx = np.asarray((pairs[:, 0] + pairs[:, 1]) / scale)
detail = np.asarray((pairs[:, 0] - pairs[:, 1]) / scale)
return approx, detail
|
haar_idwt(approx, detail, normalize=True)
Single-level Haar inverse transform restoring the paired samples.
Source code in src/cds2/wavelets.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 | def haar_idwt(
approx: Sequence[float] | FloatArray,
detail: Sequence[float] | FloatArray,
normalize: bool = True,
) -> FloatArray:
"""Single-level Haar inverse transform restoring the paired samples."""
averages = np.asarray(approx, dtype=float)
differences = np.asarray(detail, dtype=float)
if averages.ndim != 1 or differences.ndim != 1 or averages.size < 1 or differences.size < 1:
msg = "approximation and detail must be non-empty 1-D sequences"
raise ValueError(msg)
if averages.shape != differences.shape:
msg = "approximation and detail must have the same length"
raise ValueError(msg)
scale = np.sqrt(2.0) if normalize else 1.0
restored = np.empty(averages.size * 2, dtype=float)
restored[0::2] = (averages + differences) / scale
restored[1::2] = (averages - differences) / scale
return restored
|
dwt_levels(signal, levels, normalize=True)
Multi-level Haar decomposition applying haar_dwt to each approximation.
Source code in src/cds2/wavelets.py
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 | def dwt_levels(
signal: Sequence[float] | FloatArray, levels: int, normalize: bool = True
) -> DWTResult:
"""Multi-level Haar decomposition applying ``haar_dwt`` to each approximation."""
if levels < 1:
msg = "levels must be at least 1"
raise ValueError(msg)
series = _as_signal(signal)
if series.size < 2**levels:
msg = "signal too short"
raise ValueError(msg)
approximations: list[FloatArray] = []
details: list[FloatArray] = []
current = series
for _ in range(levels):
current, detail = haar_dwt(current, normalize=normalize)
approximations.append(current)
details.append(detail)
return DWTResult(
approximations=approximations, details=details, original_length=int(series.size)
)
|
idwt_levels(result, normalize=True)
Reconstruct the original signal from a :class:DWTResult.
Source code in src/cds2/wavelets.py
| def idwt_levels(result: DWTResult, normalize: bool = True) -> FloatArray:
"""Reconstruct the original signal from a :class:`DWTResult`."""
current = result.approximations[-1]
for detail in reversed(result.details):
current = haar_idwt(current, detail, normalize=normalize)
return current
|
wavelet_denoise(signal, threshold_factor=3.0, levels=2, normalize=True)
Zero detail coefficients below threshold_factor standard deviations per level.
Source code in src/cds2/wavelets.py
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143 | def wavelet_denoise(
signal: Sequence[float] | FloatArray,
threshold_factor: float = 3.0,
levels: int = 2,
normalize: bool = True,
) -> DenoiseResult:
"""Zero detail coefficients below ``threshold_factor`` standard deviations per level."""
decomposition = dwt_levels(signal, levels, normalize=normalize)
filtered: list[FloatArray] = []
zeroed = 0
for detail in decomposition.details:
sigma = float(np.median(np.abs(detail)) / 0.6745)
cutoff = threshold_factor * sigma
keep = np.abs(detail) >= cutoff
zeroed += int((~keep).sum())
filtered.append(np.asarray(np.where(keep, detail, 0.0)))
reconstructed = DWTResult(
approximations=decomposition.approximations,
details=filtered,
original_length=decomposition.original_length,
)
return DenoiseResult(
data=idwt_levels(reconstructed, normalize=normalize),
thresholded_counts=zeroed,
levels=levels,
)
|