torchwavelets 0.1.0__py3-none-any.whl
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.
- torchwavelets/__init__.py +62 -0
- torchwavelets/_version.py +1 -0
- torchwavelets/compat.py +136 -0
- torchwavelets/core/__init__.py +13 -0
- torchwavelets/core/filters.py +211 -0
- torchwavelets/core/modes.py +109 -0
- torchwavelets/dtcwt/__init__.py +24 -0
- torchwavelets/dtcwt/_autocast.py +24 -0
- torchwavelets/dtcwt/coeffs.py +76 -0
- torchwavelets/dtcwt/data/__init__.py +5 -0
- torchwavelets/dtcwt/data/antonini.npz +0 -0
- torchwavelets/dtcwt/data/farras.npz +0 -0
- torchwavelets/dtcwt/data/legall.npz +0 -0
- torchwavelets/dtcwt/data/near_sym_a.npz +0 -0
- torchwavelets/dtcwt/data/near_sym_a2.npz +0 -0
- torchwavelets/dtcwt/data/near_sym_b.npz +0 -0
- torchwavelets/dtcwt/data/near_sym_b_bp.npz +0 -0
- torchwavelets/dtcwt/data/qshift_06.npz +0 -0
- torchwavelets/dtcwt/data/qshift_32.npz +0 -0
- torchwavelets/dtcwt/data/qshift_a.npz +0 -0
- torchwavelets/dtcwt/data/qshift_b.npz +0 -0
- torchwavelets/dtcwt/data/qshift_b_bp.npz +0 -0
- torchwavelets/dtcwt/data/qshift_c.npz +0 -0
- torchwavelets/dtcwt/data/qshift_d.npz +0 -0
- torchwavelets/dtcwt/lowlevel.py +296 -0
- torchwavelets/dtcwt/transform1d.py +253 -0
- torchwavelets/dtcwt/transform2d.py +298 -0
- torchwavelets/dtcwt/transform3d.py +420 -0
- torchwavelets/dtcwt/transform_funcs.py +93 -0
- torchwavelets/dwt/__init__.py +6 -0
- torchwavelets/dwt/_validation.py +59 -0
- torchwavelets/dwt/lowlevel.py +153 -0
- torchwavelets/dwt/transform1d.py +175 -0
- torchwavelets/dwt/transform2d.py +195 -0
- torchwavelets/packets.py +439 -0
- torchwavelets/py.typed +0 -0
- torchwavelets/scatter/__init__.py +6 -0
- torchwavelets/scatter/lowlevel.py +221 -0
- torchwavelets/scatter/network.py +204 -0
- torchwavelets/swt/__init__.py +14 -0
- torchwavelets/swt/lowlevel.py +53 -0
- torchwavelets/swt/transform.py +386 -0
- torchwavelets-0.1.0.dist-info/METADATA +303 -0
- torchwavelets-0.1.0.dist-info/RECORD +47 -0
- torchwavelets-0.1.0.dist-info/WHEEL +5 -0
- torchwavelets-0.1.0.dist-info/licenses/LICENSE +29 -0
- torchwavelets-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""2-D dual-tree complex wavelet transform (functional and module API).
|
|
2
|
+
|
|
3
|
+
The bandpass coefficients are native complex tensors (``torch.complex64`` /
|
|
4
|
+
``torch.complex128``): ``yh[j]`` has shape ``(N, C, 6, H, W)`` with the 6
|
|
5
|
+
orientations (15, 45, 75, 105, 135, 165 degrees) along dim 2.
|
|
6
|
+
|
|
7
|
+
Precision follows the input: float32 -> complex64, float64 -> complex128.
|
|
8
|
+
Half inputs (float16/bfloat16) are computed in float32 and return complex64
|
|
9
|
+
(torch has no usable complex16 kernels). Boundary handling follows
|
|
10
|
+
Kingsbury's symmetric extension (the DTCWT is designed for it); other modes
|
|
11
|
+
are not supported.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
import torch
|
|
18
|
+
import torch.nn as nn
|
|
19
|
+
|
|
20
|
+
from ..dwt._validation import check_spatial
|
|
21
|
+
from ._autocast import autocast_off
|
|
22
|
+
from .coeffs import biort as _biort
|
|
23
|
+
from .coeffs import qshift as _qshift
|
|
24
|
+
from .transform_funcs import fwd_j1, fwd_j2plus, get_dimensions5, inv_j1, inv_j2plus
|
|
25
|
+
|
|
26
|
+
__all__ = ["dtcwt2d", "idtcwt2d", "DTCWT2D", "IDTCWT2D"]
|
|
27
|
+
|
|
28
|
+
_SUPPORTED_BIORTS = ("antonini", "legall", "near_sym_a", "near_sym_b")
|
|
29
|
+
_SUPPORTED_QSHIFTS = ("qshift_06", "qshift_32", "qshift_a", "qshift_b", "qshift_c", "qshift_d")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _validate_names(biort: str, qshift: str) -> None:
|
|
33
|
+
if biort not in _SUPPORTED_BIORTS:
|
|
34
|
+
if biort in ("farras", "near_sym_a2"):
|
|
35
|
+
raise ValueError(
|
|
36
|
+
f"biort {biort!r} is not usable as a level-1 bank: the shipped "
|
|
37
|
+
"data only contains its two-tree (level >= 2) filters. Use "
|
|
38
|
+
"one of the supported level-1 banks: "
|
|
39
|
+
f"{_SUPPORTED_BIORTS}."
|
|
40
|
+
)
|
|
41
|
+
raise ValueError(
|
|
42
|
+
f"Unknown biort {biort!r}; supported: {_SUPPORTED_BIORTS} "
|
|
43
|
+
"(near_sym_b_bp needs the rotationally-symmetric variant, not "
|
|
44
|
+
"implemented yet)"
|
|
45
|
+
)
|
|
46
|
+
if qshift not in _SUPPORTED_QSHIFTS:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"Unknown qshift {qshift!r}; supported: {_SUPPORTED_QSHIFTS} "
|
|
49
|
+
"(qshift_b_bp not implemented yet)"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _prep_filt(h, dtype: torch.dtype, device: torch.device) -> torch.Tensor:
|
|
54
|
+
"""Prepare a filter for conv2d: reversed, shaped ``(1, 1, L, 1)``
|
|
55
|
+
(column orientation; ``rowfilter`` transposes internally)."""
|
|
56
|
+
h = np.asarray(h[::-1], dtype=np.float64).ravel()
|
|
57
|
+
h = h[:, None][None, None, :]
|
|
58
|
+
return torch.tensor(np.ascontiguousarray(h), dtype=dtype, device=device)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def resolve_dtcwt_filters(biort: str, qshift: str, dtype: torch.dtype, device: torch.device):
|
|
62
|
+
"""All analysis/synthesis filters for the DTCWT, shaped for conv2d.
|
|
63
|
+
|
|
64
|
+
Returns a dict with keys ``h0o, h1o, h0a, h1a, h0b, h1b, g0o, g1o,
|
|
65
|
+
g0a, g1a, g0b, g1b`` (column orientation, ``(1, 1, L, 1)``; ``rowfilter``
|
|
66
|
+
transposes internally).
|
|
67
|
+
"""
|
|
68
|
+
_validate_names(biort, qshift)
|
|
69
|
+
h0o, g0o, h1o, g1o = _biort(biort)
|
|
70
|
+
h0a, h0b, g0a, g0b, h1a, h1b, g1a, g1b = _qshift(qshift)
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"h0o": _prep_filt(h0o, dtype, device),
|
|
74
|
+
"g0o": _prep_filt(g0o, dtype, device),
|
|
75
|
+
"h1o": _prep_filt(h1o, dtype, device),
|
|
76
|
+
"g1o": _prep_filt(g1o, dtype, device),
|
|
77
|
+
"h0a": _prep_filt(h0a, dtype, device),
|
|
78
|
+
"h0b": _prep_filt(h0b, dtype, device),
|
|
79
|
+
"h1a": _prep_filt(h1a, dtype, device),
|
|
80
|
+
"h1b": _prep_filt(h1b, dtype, device),
|
|
81
|
+
"g0a": _prep_filt(g0a, dtype, device),
|
|
82
|
+
"g0b": _prep_filt(g0b, dtype, device),
|
|
83
|
+
"g1a": _prep_filt(g1a, dtype, device),
|
|
84
|
+
"g1b": _prep_filt(g1b, dtype, device),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _compute_dtype(x: torch.Tensor) -> torch.dtype:
|
|
89
|
+
"""float32 for half inputs (no usable complex16 kernels), else input dtype."""
|
|
90
|
+
if x.dtype in (torch.float16, torch.bfloat16):
|
|
91
|
+
return torch.float32
|
|
92
|
+
if x.dtype not in (torch.float32, torch.float64):
|
|
93
|
+
raise ValueError(f"DTCWT expects a float32 or float64 input; got {x.dtype}")
|
|
94
|
+
return x.dtype
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _norm_flags(flag, J: int) -> list[bool]:
|
|
98
|
+
if isinstance(flag, (list, tuple, np.ndarray)):
|
|
99
|
+
return list(flag)
|
|
100
|
+
return [flag] * J
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _dtcwt2d_impl(x, f, J, skip_hps, include_scale, o_dim):
|
|
104
|
+
o_dim, _, _ = get_dimensions5(o_dim)
|
|
105
|
+
if J == 0:
|
|
106
|
+
return x, None
|
|
107
|
+
scales = [x.new_zeros([])] * J
|
|
108
|
+
r, c = x.shape[2:]
|
|
109
|
+
if r % 2 != 0:
|
|
110
|
+
x = torch.cat((x, x[:, :, -1:]), dim=2)
|
|
111
|
+
if c % 2 != 0:
|
|
112
|
+
x = torch.cat((x, x[:, :, :, -1:]), dim=3)
|
|
113
|
+
|
|
114
|
+
low, highs = fwd_j1(x, f["h0o"], f["h1o"], skip_hps[0], o_dim)
|
|
115
|
+
yh = [highs]
|
|
116
|
+
if include_scale[0]:
|
|
117
|
+
scales[0] = low
|
|
118
|
+
for j in range(1, J):
|
|
119
|
+
r, c = low.shape[2:]
|
|
120
|
+
if r % 4 != 0:
|
|
121
|
+
low = torch.cat((low[:, :, 0:1], low, low[:, :, -1:]), dim=2)
|
|
122
|
+
if c % 4 != 0:
|
|
123
|
+
low = torch.cat((low[:, :, :, 0:1], low, low[:, :, :, -1:]), dim=3)
|
|
124
|
+
low, highs = fwd_j2plus(low, f["h0a"], f["h1a"], f["h0b"], f["h1b"], skip_hps[j], o_dim)
|
|
125
|
+
yh.append(highs)
|
|
126
|
+
if include_scale[j]:
|
|
127
|
+
scales[j] = low
|
|
128
|
+
if True in include_scale:
|
|
129
|
+
return scales, yh
|
|
130
|
+
return low, yh
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _idtcwt2d_impl(yl, yh, f, o_dim):
|
|
134
|
+
J = len(yh)
|
|
135
|
+
o_dim, h_dim, w_dim = get_dimensions5(o_dim)
|
|
136
|
+
low = yl
|
|
137
|
+
for j in range(J - 1, 0, -1):
|
|
138
|
+
s = yh[j]
|
|
139
|
+
if s is not None:
|
|
140
|
+
r, c = low.shape[2:]
|
|
141
|
+
r1, c1 = s.shape[h_dim], s.shape[w_dim]
|
|
142
|
+
if r != r1 * 2:
|
|
143
|
+
low = low[:, :, 1:-1]
|
|
144
|
+
if c != c1 * 2:
|
|
145
|
+
low = low[:, :, :, 1:-1]
|
|
146
|
+
low = inv_j2plus(low, s, f["g0a"], f["g1a"], f["g0b"], f["g1b"], o_dim, h_dim, w_dim)
|
|
147
|
+
s = yh[0]
|
|
148
|
+
if s is not None:
|
|
149
|
+
r, c = low.shape[2:]
|
|
150
|
+
r1, c1 = s.shape[h_dim], s.shape[w_dim]
|
|
151
|
+
if r != r1 * 2:
|
|
152
|
+
low = low[:, :, 1:-1]
|
|
153
|
+
if c != c1 * 2:
|
|
154
|
+
low = low[:, :, :, 1:-1]
|
|
155
|
+
return inv_j1(low, s, f["g0o"], f["g1o"], o_dim, h_dim, w_dim)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def dtcwt2d(
|
|
159
|
+
x: torch.Tensor,
|
|
160
|
+
biort: str = "near_sym_a",
|
|
161
|
+
qshift: str = "qshift_a",
|
|
162
|
+
J: int = 3,
|
|
163
|
+
skip_hps=False,
|
|
164
|
+
include_scale=False,
|
|
165
|
+
o_dim: int = 2,
|
|
166
|
+
) -> tuple[torch.Tensor, list[torch.Tensor]]:
|
|
167
|
+
"""Forward 2-D dual-tree complex wavelet transform.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
x (torch.Tensor): ``(N, C, H, W)`` input.
|
|
171
|
+
biort (str): filter bank name (see
|
|
172
|
+
:mod:`torchwavelets.dtcwt.coeffs`).
|
|
173
|
+
qshift (str): filter bank name.
|
|
174
|
+
J (int): number of levels (``0`` returns ``(x, None)``).
|
|
175
|
+
skip_hps (bool or list[bool]): one per level, to skip bandpasses.
|
|
176
|
+
include_scale (bool or list[bool]): one per level, to also return the
|
|
177
|
+
intermediate lowpasses.
|
|
178
|
+
o_dim (int): dimension for the 6 orientations (default 2).
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
tuple[torch.Tensor, list[torch.Tensor]]: ``(yl, yh)``: lowpass
|
|
182
|
+
``(N, C, H', W')`` and a list of ``J`` complex bandpasses
|
|
183
|
+
``(N, C, 6, H'', W'')`` (finest first).
|
|
184
|
+
"""
|
|
185
|
+
if x.ndim != 4:
|
|
186
|
+
raise ValueError(f"dtcwt2d expects a 4-D input (N, C, H, W); got shape {tuple(x.shape)}")
|
|
187
|
+
check_spatial(x.shape[2:], ("H", "W"), "dtcwt2d")
|
|
188
|
+
if isinstance(J, bool) or not isinstance(J, int) or J < 0:
|
|
189
|
+
raise ValueError(f"J must be a non-negative int; got {J!r}")
|
|
190
|
+
dtype = _compute_dtype(x)
|
|
191
|
+
f = resolve_dtcwt_filters(biort, qshift, dtype, x.device)
|
|
192
|
+
x = x.to(dtype)
|
|
193
|
+
with autocast_off(x.device):
|
|
194
|
+
return _dtcwt2d_impl(
|
|
195
|
+
x, f, J, _norm_flags(skip_hps, J), _norm_flags(include_scale, J), o_dim
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def idtcwt2d(
|
|
200
|
+
coeffs, biort: str = "near_sym_a", qshift: str = "qshift_a", o_dim: int = 2
|
|
201
|
+
) -> torch.Tensor:
|
|
202
|
+
"""Inverse 2-D DTCWT.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
coeffs (tuple): ``(yl, yh)`` as returned by :func:`dtcwt2d` (any
|
|
206
|
+
bandpass may be ``None``, treated as zeros).
|
|
207
|
+
biort (str): as in :func:`dtcwt2d`.
|
|
208
|
+
qshift (str): as in :func:`dtcwt2d`.
|
|
209
|
+
o_dim (int): as in :func:`dtcwt2d`.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
torch.Tensor: reconstructed ``(N, C, H, W)`` tensor.
|
|
213
|
+
"""
|
|
214
|
+
yl, yh = coeffs
|
|
215
|
+
if yl is None:
|
|
216
|
+
raise ValueError("The lowpass coefficient tensor yl cannot be None")
|
|
217
|
+
dtype = _compute_dtype(yl)
|
|
218
|
+
f = resolve_dtcwt_filters(biort, qshift, dtype, yl.device)
|
|
219
|
+
with autocast_off(yl.device):
|
|
220
|
+
return _idtcwt2d_impl(yl.to(dtype), yh, f, o_dim)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class DTCWT2D(nn.Module):
|
|
224
|
+
"""2-D DTCWT module (see :func:`dtcwt2d`).
|
|
225
|
+
|
|
226
|
+
Filters are resolved eagerly at construction; the forward pass is pure
|
|
227
|
+
torch ops, so the module is ``torch.compile``-compatible.
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
def __init__(
|
|
231
|
+
self,
|
|
232
|
+
biort="near_sym_a",
|
|
233
|
+
qshift="qshift_a",
|
|
234
|
+
J=3,
|
|
235
|
+
skip_hps=False,
|
|
236
|
+
include_scale=False,
|
|
237
|
+
o_dim=2,
|
|
238
|
+
):
|
|
239
|
+
super().__init__()
|
|
240
|
+
if isinstance(J, bool) or not isinstance(J, int) or J < 0:
|
|
241
|
+
raise ValueError(f"J must be a non-negative int; got {J!r}")
|
|
242
|
+
self.biort = biort
|
|
243
|
+
self.qshift = qshift
|
|
244
|
+
self.J = J
|
|
245
|
+
self.o_dim = o_dim
|
|
246
|
+
self.skip_hps = _norm_flags(skip_hps, J)
|
|
247
|
+
self.include_scale = _norm_flags(include_scale, J)
|
|
248
|
+
|
|
249
|
+
filts = resolve_dtcwt_filters(biort, qshift, torch.float64, torch.device("cpu"))
|
|
250
|
+
for name in ("h0o", "h1o", "h0a", "h1a", "h0b", "h1b"):
|
|
251
|
+
self.register_buffer(name, filts[name])
|
|
252
|
+
|
|
253
|
+
def forward(self, x: torch.Tensor):
|
|
254
|
+
if x.ndim != 4:
|
|
255
|
+
raise ValueError(f"DTCWT2D expects (N, C, H, W); got {tuple(x.shape)}")
|
|
256
|
+
check_spatial(x.shape[2:], ("H", "W"), "DTCWT2D")
|
|
257
|
+
dtype = _compute_dtype(x)
|
|
258
|
+
f = {
|
|
259
|
+
name: getattr(self, name).to(dtype=dtype, device=x.device)
|
|
260
|
+
for name in ("h0o", "h1o", "h0a", "h1a", "h0b", "h1b")
|
|
261
|
+
}
|
|
262
|
+
with autocast_off(x.device):
|
|
263
|
+
return _dtcwt2d_impl(
|
|
264
|
+
x.to(dtype), f, self.J, self.skip_hps, self.include_scale, self.o_dim
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
def extra_repr(self) -> str:
|
|
268
|
+
return f"biort={self.biort!r}, qshift={self.qshift!r}, J={self.J}"
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class IDTCWT2D(nn.Module):
|
|
272
|
+
"""2-D inverse DTCWT module (see :func:`idtcwt2d`)."""
|
|
273
|
+
|
|
274
|
+
def __init__(self, biort="near_sym_a", qshift="qshift_a", o_dim=2):
|
|
275
|
+
super().__init__()
|
|
276
|
+
self.biort = biort
|
|
277
|
+
self.qshift = qshift
|
|
278
|
+
self.o_dim = o_dim
|
|
279
|
+
filts = resolve_dtcwt_filters(biort, qshift, torch.float64, torch.device("cpu"))
|
|
280
|
+
for name in ("g0o", "g1o", "g0a", "g1a", "g0b", "g1b"):
|
|
281
|
+
self.register_buffer(name, filts[name])
|
|
282
|
+
|
|
283
|
+
def forward(self, coeffs):
|
|
284
|
+
yl, _ = coeffs
|
|
285
|
+
if yl is None:
|
|
286
|
+
raise ValueError("The lowpass coefficient tensor yl cannot be None")
|
|
287
|
+
if yl.ndim != 4:
|
|
288
|
+
raise ValueError(f"IDTCWT2D expects 4-D coefficients; got shape {tuple(yl.shape)}")
|
|
289
|
+
dtype = _compute_dtype(yl)
|
|
290
|
+
f = {
|
|
291
|
+
name: getattr(self, name).to(dtype=dtype, device=yl.device)
|
|
292
|
+
for name in ("g0o", "g1o", "g0a", "g1a", "g0b", "g1b")
|
|
293
|
+
}
|
|
294
|
+
with autocast_off(yl.device):
|
|
295
|
+
return _idtcwt2d_impl(yl.to(dtype), coeffs[1], f, self.o_dim)
|
|
296
|
+
|
|
297
|
+
def extra_repr(self) -> str:
|
|
298
|
+
return f"biort={self.biort!r}, qshift={self.qshift!r}"
|