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.
Files changed (47) hide show
  1. torchwavelets/__init__.py +62 -0
  2. torchwavelets/_version.py +1 -0
  3. torchwavelets/compat.py +136 -0
  4. torchwavelets/core/__init__.py +13 -0
  5. torchwavelets/core/filters.py +211 -0
  6. torchwavelets/core/modes.py +109 -0
  7. torchwavelets/dtcwt/__init__.py +24 -0
  8. torchwavelets/dtcwt/_autocast.py +24 -0
  9. torchwavelets/dtcwt/coeffs.py +76 -0
  10. torchwavelets/dtcwt/data/__init__.py +5 -0
  11. torchwavelets/dtcwt/data/antonini.npz +0 -0
  12. torchwavelets/dtcwt/data/farras.npz +0 -0
  13. torchwavelets/dtcwt/data/legall.npz +0 -0
  14. torchwavelets/dtcwt/data/near_sym_a.npz +0 -0
  15. torchwavelets/dtcwt/data/near_sym_a2.npz +0 -0
  16. torchwavelets/dtcwt/data/near_sym_b.npz +0 -0
  17. torchwavelets/dtcwt/data/near_sym_b_bp.npz +0 -0
  18. torchwavelets/dtcwt/data/qshift_06.npz +0 -0
  19. torchwavelets/dtcwt/data/qshift_32.npz +0 -0
  20. torchwavelets/dtcwt/data/qshift_a.npz +0 -0
  21. torchwavelets/dtcwt/data/qshift_b.npz +0 -0
  22. torchwavelets/dtcwt/data/qshift_b_bp.npz +0 -0
  23. torchwavelets/dtcwt/data/qshift_c.npz +0 -0
  24. torchwavelets/dtcwt/data/qshift_d.npz +0 -0
  25. torchwavelets/dtcwt/lowlevel.py +296 -0
  26. torchwavelets/dtcwt/transform1d.py +253 -0
  27. torchwavelets/dtcwt/transform2d.py +298 -0
  28. torchwavelets/dtcwt/transform3d.py +420 -0
  29. torchwavelets/dtcwt/transform_funcs.py +93 -0
  30. torchwavelets/dwt/__init__.py +6 -0
  31. torchwavelets/dwt/_validation.py +59 -0
  32. torchwavelets/dwt/lowlevel.py +153 -0
  33. torchwavelets/dwt/transform1d.py +175 -0
  34. torchwavelets/dwt/transform2d.py +195 -0
  35. torchwavelets/packets.py +439 -0
  36. torchwavelets/py.typed +0 -0
  37. torchwavelets/scatter/__init__.py +6 -0
  38. torchwavelets/scatter/lowlevel.py +221 -0
  39. torchwavelets/scatter/network.py +204 -0
  40. torchwavelets/swt/__init__.py +14 -0
  41. torchwavelets/swt/lowlevel.py +53 -0
  42. torchwavelets/swt/transform.py +386 -0
  43. torchwavelets-0.1.0.dist-info/METADATA +303 -0
  44. torchwavelets-0.1.0.dist-info/RECORD +47 -0
  45. torchwavelets-0.1.0.dist-info/WHEEL +5 -0
  46. torchwavelets-0.1.0.dist-info/licenses/LICENSE +29 -0
  47. 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}"