wdm-transform 0.2.0__tar.gz → 0.3.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.
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/CHANGELOG.md +8 -0
- {wdm_transform-0.2.0/src/wdm_transform.egg-info → wdm_transform-0.3.0}/PKG-INFO +1 -1
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/mkdocs.yml +1 -3
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/benchmarking.py +59 -4
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/transforms/__init__.py +81 -2
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/transforms/_subband.py +35 -3
- {wdm_transform-0.2.0 → wdm_transform-0.3.0/src/wdm_transform.egg-info}/PKG-INFO +1 -1
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_roundtrip.py +10 -14
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_subband.py +2 -6
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/.gitmodules +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/MANIFEST.in +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/README.md +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/pyproject.toml +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/setup.cfg +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/__init__.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/backends/__init__.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/backends/base.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/backends/cupy_backend.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/backends/jax_backend.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/backends/numpy_backend.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/datatypes/__init__.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/datatypes/series.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/datatypes/wdm.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/plotting.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/signal_processing.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/transforms/jax_backend.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/transforms/jax_subband.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/transforms/xp_backend.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/transforms/xp_subband.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/windows.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform.egg-info/SOURCES.txt +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform.egg-info/dependency_links.txt +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform.egg-info/entry_points.txt +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform.egg-info/requires.txt +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform.egg-info/top_level.txt +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/conftest.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_api_helpers.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_benchmarking.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_docs.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_signal_processing.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_snr_consistency.py +0 -0
- {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_validation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wdm-transform
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Object-oriented WDM transforms for sampled one-dimensional signals.
|
|
5
5
|
Project-URL: Homepage, https://github.com/pywavelet/wdm_transform
|
|
6
6
|
Project-URL: Repository, https://github.com/pywavelet/wdm_transform
|
|
@@ -7,9 +7,7 @@ docs_dir: docs
|
|
|
7
7
|
site_dir: site
|
|
8
8
|
|
|
9
9
|
exclude_docs: |
|
|
10
|
-
studies/**
|
|
11
10
|
manuscript/**
|
|
12
|
-
reference/api.md
|
|
13
11
|
dev-notes.md
|
|
14
12
|
|
|
15
13
|
theme:
|
|
@@ -102,4 +100,4 @@ nav:
|
|
|
102
100
|
- API Reference:
|
|
103
101
|
- API Overview: guide/api-overview.md
|
|
104
102
|
- Package Layout: guide/package-layout.md
|
|
105
|
-
- Benchmarks: benchmarks.
|
|
103
|
+
- Benchmarks: benchmarks.ipynb
|
|
@@ -16,6 +16,8 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import argparse
|
|
18
18
|
import json
|
|
19
|
+
import os
|
|
20
|
+
import shutil
|
|
19
21
|
import time
|
|
20
22
|
from pathlib import Path
|
|
21
23
|
from typing import Any, Callable
|
|
@@ -36,6 +38,22 @@ DEFAULT_JSON_NAME = "benchmark_results.json"
|
|
|
36
38
|
DEFAULT_PLOT_NAME = "benchmark_runtime.png"
|
|
37
39
|
FIXED_PARAMS = {"a": 0.4, "d": 1, "dt": 1.0}
|
|
38
40
|
|
|
41
|
+
def _resolve_device(requested: str) -> str:
|
|
42
|
+
"""Resolve 'cpu'/'gpu'/'auto' and configure JAX platform env vars.
|
|
43
|
+
|
|
44
|
+
Must be called before any backend is imported.
|
|
45
|
+
"""
|
|
46
|
+
if requested == "auto":
|
|
47
|
+
device = "gpu" if shutil.which("nvidia-smi") is not None else "cpu"
|
|
48
|
+
else:
|
|
49
|
+
device = requested
|
|
50
|
+
|
|
51
|
+
jax_platform = "cuda" if device == "gpu" else "cpu"
|
|
52
|
+
os.environ.setdefault("JAX_PLATFORMS", jax_platform)
|
|
53
|
+
os.environ.setdefault("JAX_PLATFORM_NAME", jax_platform)
|
|
54
|
+
return device
|
|
55
|
+
|
|
56
|
+
|
|
39
57
|
OPERATION_TITLES = {
|
|
40
58
|
"from_freq": "Forward Kernel: from_freq_to_wdm",
|
|
41
59
|
"to_freq": "Inverse Kernel: from_wdm_to_freq",
|
|
@@ -494,6 +512,7 @@ def plot_results( # pragma: no cover
|
|
|
494
512
|
backend_styles = {
|
|
495
513
|
"numpy": {"color": "tab:blue"},
|
|
496
514
|
"jax": {"color": "tab:orange"},
|
|
515
|
+
"cupy": {"color": "tab:green"},
|
|
497
516
|
}
|
|
498
517
|
for column, operation in enumerate(operations):
|
|
499
518
|
runtime_ax = axes[0, column]
|
|
@@ -598,12 +617,25 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
598
617
|
parser = argparse.ArgumentParser(
|
|
599
618
|
description="Benchmark low-level WDM kernels across backends and input sizes.",
|
|
600
619
|
)
|
|
620
|
+
parser.add_argument(
|
|
621
|
+
"--device",
|
|
622
|
+
choices=["cpu", "gpu", "auto"],
|
|
623
|
+
default="auto",
|
|
624
|
+
help=(
|
|
625
|
+
"Target device for JAX (and CuPy). "
|
|
626
|
+
"'auto' detects GPU via nvidia-smi. "
|
|
627
|
+
"Forces JAX platform."
|
|
628
|
+
),
|
|
629
|
+
)
|
|
601
630
|
parser.add_argument(
|
|
602
631
|
"--backends",
|
|
603
632
|
nargs="+",
|
|
604
|
-
default=
|
|
633
|
+
default=None,
|
|
605
634
|
choices=["numpy", "jax", "cupy"],
|
|
606
|
-
help=
|
|
635
|
+
help=(
|
|
636
|
+
"Backends to benchmark. "
|
|
637
|
+
"Default: numpy+jax on CPU, numpy+jax+cupy on GPU."
|
|
638
|
+
),
|
|
607
639
|
)
|
|
608
640
|
parser.add_argument(
|
|
609
641
|
"--pow2",
|
|
@@ -651,6 +683,15 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
651
683
|
def main(argv: list[str] | None = None) -> None: # pragma: no cover
|
|
652
684
|
"""CLI entry point for benchmarking."""
|
|
653
685
|
args = _build_parser().parse_args(argv)
|
|
686
|
+
|
|
687
|
+
# Resolve device and configure JAX platform BEFORE any backend is imported.
|
|
688
|
+
device = _resolve_device(args.device)
|
|
689
|
+
print(f"Device: {device.upper()}")
|
|
690
|
+
|
|
691
|
+
backends_to_test = args.backends or (
|
|
692
|
+
["numpy", "jax", "cupy"] if device == "gpu" else DEFAULT_BACKENDS
|
|
693
|
+
)
|
|
694
|
+
|
|
654
695
|
if args.batch_size < 1:
|
|
655
696
|
raise SystemExit("--batch-size must be at least 1.")
|
|
656
697
|
pow2_range = tuple(args.pow2) if args.pow2 is not None else None
|
|
@@ -659,18 +700,32 @@ def main(argv: list[str] | None = None) -> None: # pragma: no cover
|
|
|
659
700
|
except ValueError as exc:
|
|
660
701
|
raise SystemExit(str(exc)) from exc
|
|
661
702
|
|
|
703
|
+
# Log which JAX devices are in use before running (confirms CPU vs GPU).
|
|
704
|
+
_jax = None
|
|
705
|
+
try:
|
|
706
|
+
import jax as _jax # noqa: PLC0415
|
|
707
|
+
|
|
708
|
+
print(f"JAX devices: {_jax.devices()}")
|
|
709
|
+
except Exception:
|
|
710
|
+
pass
|
|
711
|
+
|
|
662
712
|
results = run_benchmarks(
|
|
663
|
-
backends_to_test=
|
|
713
|
+
backends_to_test=backends_to_test,
|
|
664
714
|
n_values=n_values,
|
|
665
715
|
num_runs=args.runs,
|
|
666
716
|
batch_size=args.batch_size,
|
|
667
717
|
)
|
|
718
|
+
|
|
719
|
+
if _jax is not None:
|
|
720
|
+
results["metadata"]["jax_devices"] = [str(d) for d in _jax.devices()]
|
|
721
|
+
|
|
668
722
|
print_summary(results)
|
|
669
723
|
|
|
670
724
|
output_json = args.output_json or (args.outdir / DEFAULT_JSON_NAME)
|
|
671
725
|
output_plot = args.output_plot or (args.outdir / DEFAULT_PLOT_NAME)
|
|
726
|
+
plot_title = args.plot_title + f" ({device.upper()})"
|
|
672
727
|
json_path = save_results(results, output_json)
|
|
673
|
-
plot_path = plot_results(results, output_plot, title=
|
|
728
|
+
plot_path = plot_results(results, output_plot, title=plot_title)
|
|
674
729
|
|
|
675
730
|
print(f"\nSaved JSON results to {json_path}")
|
|
676
731
|
print(f"Saved benchmark plot to {plot_path}")
|
|
@@ -154,7 +154,36 @@ def from_freq_to_wdm_subband(
|
|
|
154
154
|
d: float = 1.0,
|
|
155
155
|
backend: str | Backend | None = None,
|
|
156
156
|
) -> tuple[Any, int]:
|
|
157
|
-
"""Compute the overlapping WDM sub-band from a compact Fourier span.
|
|
157
|
+
"""Compute the overlapping WDM sub-band from a compact Fourier span.
|
|
158
|
+
|
|
159
|
+
Use this when the input is already cropped in the one-sided Fourier
|
|
160
|
+
domain. The function evaluates only the WDM frequency channels whose
|
|
161
|
+
window support intersects the supplied Fourier span, rather than
|
|
162
|
+
materializing the full WDM grid.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
data: One-dimensional one-sided Fourier samples for the compact span
|
|
166
|
+
``[kmin, kmin + len(data))``.
|
|
167
|
+
df: Frequency spacing of the full one-sided Fourier grid.
|
|
168
|
+
nfreqs_fourier: Number of samples in the full one-sided Fourier grid,
|
|
169
|
+
including DC and Nyquist.
|
|
170
|
+
kmin: Starting Fourier-bin index of ``data`` in the full one-sided
|
|
171
|
+
grid.
|
|
172
|
+
nfreqs_wdm: Number of positive WDM frequency intervals in the full
|
|
173
|
+
transform. A full WDM coefficient grid has ``nfreqs_wdm + 1``
|
|
174
|
+
frequency channels.
|
|
175
|
+
ntimes_wdm: Number of WDM time bins.
|
|
176
|
+
a: Window roll-off parameter.
|
|
177
|
+
d: Window order parameter.
|
|
178
|
+
backend: Backend name or object. If omitted, the default backend is
|
|
179
|
+
used.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
A tuple ``(coeffs, mmin)``. ``coeffs`` has shape
|
|
183
|
+
``(ntimes_wdm, nf_sub_wdm)`` and stores the touched WDM channels.
|
|
184
|
+
``mmin`` is the first WDM frequency-channel index represented by
|
|
185
|
+
``coeffs[:, 0]``.
|
|
186
|
+
"""
|
|
158
187
|
resolved_backend = get_backend(backend)
|
|
159
188
|
module = _get_subband_transform_module(resolved_backend)
|
|
160
189
|
return module.forward_wdm_subband(
|
|
@@ -190,6 +219,29 @@ def from_freq_to_wdm_band(
|
|
|
190
219
|
minimal touched WDM span, then returns only the requested compact block
|
|
191
220
|
``mmin : mmin + nf_sub_wdm``. The requested block must lie inside the
|
|
192
221
|
touched span of the supplied Fourier support.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
data: One-dimensional one-sided Fourier samples for the compact span
|
|
225
|
+
``[kmin, kmin + len(data))``.
|
|
226
|
+
df: Frequency spacing of the full one-sided Fourier grid.
|
|
227
|
+
nfreqs_fourier: Number of samples in the full one-sided Fourier grid,
|
|
228
|
+
including DC and Nyquist.
|
|
229
|
+
kmin: Starting Fourier-bin index of ``data`` in the full one-sided
|
|
230
|
+
grid.
|
|
231
|
+
nfreqs_wdm: Number of positive WDM frequency intervals in the full
|
|
232
|
+
transform. A full WDM coefficient grid has ``nfreqs_wdm + 1``
|
|
233
|
+
frequency channels.
|
|
234
|
+
ntimes_wdm: Number of WDM time bins.
|
|
235
|
+
mmin: First WDM frequency-channel index to return.
|
|
236
|
+
nf_sub_wdm: Number of WDM frequency channels to return.
|
|
237
|
+
a: Window roll-off parameter.
|
|
238
|
+
d: Window order parameter.
|
|
239
|
+
backend: Backend name or object. If omitted, the default backend is
|
|
240
|
+
used.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
The requested coefficient block with shape
|
|
244
|
+
``(ntimes_wdm, nf_sub_wdm)``.
|
|
193
245
|
"""
|
|
194
246
|
coeffs, touched_mmin = from_freq_to_wdm_subband(
|
|
195
247
|
data,
|
|
@@ -225,7 +277,34 @@ def from_wdm_to_freq_subband(
|
|
|
225
277
|
d: float = 1.0,
|
|
226
278
|
backend: str | Backend | None = None,
|
|
227
279
|
) -> tuple[Any, int]:
|
|
228
|
-
"""Reconstruct the touched Fourier span from a compact WDM block.
|
|
280
|
+
"""Reconstruct the touched Fourier span from a compact WDM block.
|
|
281
|
+
|
|
282
|
+
This is the inverse of the compact sub-band transform. It expands a block
|
|
283
|
+
of adjacent WDM frequency channels into the one-sided Fourier bins touched
|
|
284
|
+
by those channels' window support.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
coeffs: Two-dimensional WDM coefficient block with shape
|
|
288
|
+
``(ntimes_wdm, nf_sub_wdm)``.
|
|
289
|
+
df: Frequency spacing of the full one-sided Fourier grid.
|
|
290
|
+
nfreqs_fourier: Number of samples in the full one-sided Fourier grid,
|
|
291
|
+
including DC and Nyquist.
|
|
292
|
+
mmin: First WDM frequency-channel index represented by
|
|
293
|
+
``coeffs[:, 0]``.
|
|
294
|
+
nfreqs_wdm: Number of positive WDM frequency intervals in the full
|
|
295
|
+
transform. A full WDM coefficient grid has ``nfreqs_wdm + 1``
|
|
296
|
+
frequency channels.
|
|
297
|
+
ntimes_wdm: Number of WDM time bins.
|
|
298
|
+
a: Window roll-off parameter.
|
|
299
|
+
d: Window order parameter.
|
|
300
|
+
backend: Backend name or object. If omitted, the default backend is
|
|
301
|
+
used.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
A tuple ``(spectrum, kmin)``. ``spectrum`` contains the reconstructed
|
|
305
|
+
one-sided Fourier span, and ``kmin`` is the starting Fourier-bin index
|
|
306
|
+
of ``spectrum[0]`` in the full one-sided grid.
|
|
307
|
+
"""
|
|
229
308
|
resolved_backend = get_backend(backend)
|
|
230
309
|
module = _get_subband_transform_module(resolved_backend)
|
|
231
310
|
return module.inverse_wdm_subband(
|
|
@@ -93,7 +93,23 @@ def wdm_span_from_fourier_span(
|
|
|
93
93
|
kmin: int,
|
|
94
94
|
lendata: int,
|
|
95
95
|
) -> tuple[int, int]:
|
|
96
|
-
"""Map a compact one-sided Fourier span onto the touched WDM channels.
|
|
96
|
+
"""Map a compact one-sided Fourier span onto the touched WDM channels.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
nfreqs_fourier: Number of samples in the full one-sided Fourier grid,
|
|
100
|
+
including DC and Nyquist.
|
|
101
|
+
nfreqs_wdm: Number of positive WDM frequency intervals in the full
|
|
102
|
+
transform. A full WDM coefficient grid has ``nfreqs_wdm + 1``
|
|
103
|
+
frequency channels.
|
|
104
|
+
ntimes_wdm: Number of WDM time bins.
|
|
105
|
+
kmin: Starting Fourier-bin index of the compact Fourier span.
|
|
106
|
+
lendata: Number of Fourier bins in the compact span.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
A tuple ``(mmin, nf_sub_wdm)``. ``mmin`` is the first touched WDM
|
|
110
|
+
frequency-channel index, and ``nf_sub_wdm`` is the number of adjacent
|
|
111
|
+
WDM channels touched by the Fourier span.
|
|
112
|
+
"""
|
|
97
113
|
validate_subband_grid(
|
|
98
114
|
nfreqs_fourier=nfreqs_fourier,
|
|
99
115
|
nfreqs_wdm=nfreqs_wdm,
|
|
@@ -123,7 +139,24 @@ def fourier_span_from_wdm_span(
|
|
|
123
139
|
mmin: int,
|
|
124
140
|
nf_sub_wdm: int,
|
|
125
141
|
) -> tuple[int, int]:
|
|
126
|
-
"""Map a compact WDM channel span onto the touched one-sided Fourier bins.
|
|
142
|
+
"""Map a compact WDM channel span onto the touched one-sided Fourier bins.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
nfreqs_fourier: Number of samples in the full one-sided Fourier grid,
|
|
146
|
+
including DC and Nyquist.
|
|
147
|
+
nfreqs_wdm: Number of positive WDM frequency intervals in the full
|
|
148
|
+
transform. A full WDM coefficient grid has ``nfreqs_wdm + 1``
|
|
149
|
+
frequency channels.
|
|
150
|
+
ntimes_wdm: Number of WDM time bins.
|
|
151
|
+
mmin: First WDM frequency-channel index in the compact WDM span.
|
|
152
|
+
nf_sub_wdm: Number of adjacent WDM frequency channels in the compact
|
|
153
|
+
span.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
A tuple ``(kmin, lendata)``. ``kmin`` is the first touched one-sided
|
|
157
|
+
Fourier-bin index, and ``lendata`` is the number of touched Fourier
|
|
158
|
+
bins.
|
|
159
|
+
"""
|
|
127
160
|
validate_subband_grid(
|
|
128
161
|
nfreqs_fourier=nfreqs_fourier,
|
|
129
162
|
nfreqs_wdm=nfreqs_wdm,
|
|
@@ -143,4 +176,3 @@ def fourier_span_from_wdm_span(
|
|
|
143
176
|
nyquist = int(nfreqs_wdm) * half
|
|
144
177
|
kmax = nyquist if mmax == int(nfreqs_wdm) else (mmax + 1) * half - 1
|
|
145
178
|
return kmin, kmax - kmin + 1
|
|
146
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wdm-transform
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Object-oriented WDM transforms for sampled one-dimensional signals.
|
|
5
5
|
Project-URL: Homepage, https://github.com/pywavelet/wdm_transform
|
|
6
6
|
Project-URL: Repository, https://github.com/pywavelet/wdm_transform
|
|
@@ -8,6 +8,7 @@ import pytest
|
|
|
8
8
|
from wdm_transform import FrequencySeries, TimeSeries, WDM
|
|
9
9
|
from wdm_transform.backends import Backend, get_backend
|
|
10
10
|
from wdm_transform.plotting import plot_periodogram, plot_spectrogram
|
|
11
|
+
import matplotlib.pyplot as plt
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
def _chirplet(times: np.ndarray, duration: float) -> np.ndarray:
|
|
@@ -253,7 +254,7 @@ def test_batched_plot_methods_create_one_axis_per_batch_element() -> None:
|
|
|
253
254
|
|
|
254
255
|
time_series = TimeSeries(np.zeros((2, 16)), dt=0.25)
|
|
255
256
|
freq_series = FrequencySeries(np.zeros((2, 16), dtype=complex), df=0.25)
|
|
256
|
-
wdm = WDM(np.zeros((2, 4, 5)), dt=0.25, backend="
|
|
257
|
+
wdm = WDM(np.zeros((2, 4, 5)), dt=0.25, backend="numpy")
|
|
257
258
|
|
|
258
259
|
fig, axes = time_series.plot()
|
|
259
260
|
assert len(np.ravel(axes)) == 2
|
|
@@ -294,9 +295,6 @@ def test_singleton_plot_methods_return_one_element_axes_arrays() -> None:
|
|
|
294
295
|
|
|
295
296
|
|
|
296
297
|
def test_wdm_roundtrip_diagnostics_plots(outdir: Path) -> None:
|
|
297
|
-
matplotlib = pytest.importorskip("matplotlib")
|
|
298
|
-
matplotlib.use("Agg", force=True)
|
|
299
|
-
plt = pytest.importorskip("matplotlib.pyplot")
|
|
300
298
|
|
|
301
299
|
series, nt, _ = _example_series()
|
|
302
300
|
coeffs = WDM.from_time_series(series, nt=nt, a=1.0 / 3.0)
|
|
@@ -305,15 +303,13 @@ def test_wdm_roundtrip_diagnostics_plots(outdir: Path) -> None:
|
|
|
305
303
|
reconstructed = coeffs.to_time_series()
|
|
306
304
|
|
|
307
305
|
residual = reconstructed.data - series.data
|
|
308
|
-
test_outdir = outdir / "test_roundtrip"
|
|
309
|
-
test_outdir.mkdir(parents=True, exist_ok=True)
|
|
310
306
|
|
|
311
307
|
fig, ax = plt.subplots(figsize=(8, 3.5))
|
|
312
308
|
series.plot(ax=ax, color="C0", label="input")
|
|
313
309
|
ax.set_title("Original time series")
|
|
314
310
|
assert ax.get_xlabel() == "Time [min]"
|
|
315
311
|
assert ax.get_ylabel() == "Amplitude"
|
|
316
|
-
fig.savefig(
|
|
312
|
+
fig.savefig(outdir / "1_original.png", dpi=140)
|
|
317
313
|
plt.close(fig)
|
|
318
314
|
|
|
319
315
|
fig, ax = plt.subplots(figsize=(8, 3.5))
|
|
@@ -327,7 +323,7 @@ def test_wdm_roundtrip_diagnostics_plots(outdir: Path) -> None:
|
|
|
327
323
|
ax.set_title("Complex FFT coefficients")
|
|
328
324
|
assert ax.get_xlabel() == "Frequency [Hz]"
|
|
329
325
|
assert ax.get_ylabel() == "Value"
|
|
330
|
-
fig.savefig(
|
|
326
|
+
fig.savefig(outdir / "2_frequency.png", dpi=140)
|
|
331
327
|
plt.close(fig)
|
|
332
328
|
|
|
333
329
|
fig, ax = plt.subplots(figsize=(7, 3.5))
|
|
@@ -335,7 +331,7 @@ def test_wdm_roundtrip_diagnostics_plots(outdir: Path) -> None:
|
|
|
335
331
|
ax.set_title("Periodogram")
|
|
336
332
|
assert ax.get_xlabel() == "Frequency [Hz]"
|
|
337
333
|
assert ax.get_ylabel() == "Periodogram"
|
|
338
|
-
fig.savefig(
|
|
334
|
+
fig.savefig(outdir / "3_periodogram.png", dpi=140)
|
|
339
335
|
plt.close(fig)
|
|
340
336
|
|
|
341
337
|
fig, ax = plt.subplots(figsize=(8, 4.5))
|
|
@@ -348,7 +344,7 @@ def test_wdm_roundtrip_diagnostics_plots(outdir: Path) -> None:
|
|
|
348
344
|
ax.set_title("Spectrogram")
|
|
349
345
|
assert ax.get_xlabel() == "Time [min]"
|
|
350
346
|
assert ax.get_ylabel() == "Frequency [Hz]"
|
|
351
|
-
fig.savefig(
|
|
347
|
+
fig.savefig(outdir / "4_spectrogram.png", dpi=140)
|
|
352
348
|
plt.close(fig)
|
|
353
349
|
|
|
354
350
|
fig, ax = plt.subplots(figsize=(8, 4.5))
|
|
@@ -365,7 +361,7 @@ def test_wdm_roundtrip_diagnostics_plots(outdir: Path) -> None:
|
|
|
365
361
|
assert "Time bins" in ax.get_xlabel()
|
|
366
362
|
assert "Frequency bins" in ax.get_ylabel()
|
|
367
363
|
assert any("coeffs" in text.get_text() for text in ax.texts)
|
|
368
|
-
fig.savefig(
|
|
364
|
+
fig.savefig(outdir / "5_wdm.png", dpi=140)
|
|
369
365
|
plt.close(fig)
|
|
370
366
|
|
|
371
367
|
with pytest.warns(UserWarning, match="Falling back to default linear normalization"):
|
|
@@ -378,7 +374,7 @@ def test_wdm_roundtrip_diagnostics_plots(outdir: Path) -> None:
|
|
|
378
374
|
whiten_by=np.full((coeffs.nf + 1, coeffs.nt), np.nan),
|
|
379
375
|
)
|
|
380
376
|
assert len(fig.axes) == 1
|
|
381
|
-
fig.savefig(
|
|
377
|
+
fig.savefig(outdir / "6_wdm_fallback.png", dpi=140)
|
|
382
378
|
plt.close(fig)
|
|
383
379
|
|
|
384
380
|
fig, ax = plt.subplots(figsize=(8, 3.5))
|
|
@@ -386,7 +382,7 @@ def test_wdm_roundtrip_diagnostics_plots(outdir: Path) -> None:
|
|
|
386
382
|
ax.set_title("Reconstruction")
|
|
387
383
|
assert ax.get_xlabel() == "Time [min]"
|
|
388
384
|
assert ax.get_ylabel() == "Amplitude"
|
|
389
|
-
fig.savefig(
|
|
385
|
+
fig.savefig(outdir / "7_reconstruction.png", dpi=140)
|
|
390
386
|
plt.close(fig)
|
|
391
387
|
|
|
392
388
|
fig, ax = plt.subplots(figsize=(7, 3.5))
|
|
@@ -395,7 +391,7 @@ def test_wdm_roundtrip_diagnostics_plots(outdir: Path) -> None:
|
|
|
395
391
|
ax.set_xlabel("reconstruction - original")
|
|
396
392
|
ax.set_ylabel("count")
|
|
397
393
|
fig.tight_layout()
|
|
398
|
-
fig.savefig(
|
|
394
|
+
fig.savefig(outdir / "8_residual_hist.png", dpi=140)
|
|
399
395
|
plt.close(fig)
|
|
400
396
|
|
|
401
397
|
np.testing.assert_allclose(reconstructed.data, series.data, atol=1e-10, rtol=1e-10)
|
|
@@ -18,6 +18,7 @@ from wdm_transform.transforms import (
|
|
|
18
18
|
wdm_span_from_fourier_span,
|
|
19
19
|
)
|
|
20
20
|
from wdm_transform.windows import cnm, phi_unit
|
|
21
|
+
import matplotlib.pyplot as plt
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
NT = 32
|
|
@@ -691,11 +692,6 @@ def test_chirping_binary_subband_roundtrip_jax() -> None:
|
|
|
691
692
|
|
|
692
693
|
|
|
693
694
|
def test_chirping_binary_subband_diagnostics_plot(outdir) -> None:
|
|
694
|
-
matplotlib = pytest.importorskip("matplotlib")
|
|
695
|
-
matplotlib.use("Agg", force=True)
|
|
696
|
-
plt = pytest.importorskip("matplotlib.pyplot")
|
|
697
|
-
test_outdir = outdir / "test_subband"
|
|
698
|
-
test_outdir.mkdir(parents=True, exist_ok=True)
|
|
699
695
|
|
|
700
696
|
signal, one_sided, kmin, lendata = _chirping_binary_subband_case()
|
|
701
697
|
full_runtime_ms = _mean_runtime_ms(
|
|
@@ -871,5 +867,5 @@ def test_chirping_binary_subband_diagnostics_plot(outdir) -> None:
|
|
|
871
867
|
axes[3].legend(loc="upper right")
|
|
872
868
|
_annotate_metrics(axes[3], mf_snr=fourier_mf_snr)
|
|
873
869
|
|
|
874
|
-
fig.savefig(
|
|
870
|
+
fig.savefig(outdir / "subband_diagnostics_vertical.png", dpi=140)
|
|
875
871
|
plt.close(fig)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|