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.
Files changed (42) hide show
  1. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/CHANGELOG.md +8 -0
  2. {wdm_transform-0.2.0/src/wdm_transform.egg-info → wdm_transform-0.3.0}/PKG-INFO +1 -1
  3. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/mkdocs.yml +1 -3
  4. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/benchmarking.py +59 -4
  5. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/transforms/__init__.py +81 -2
  6. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/transforms/_subband.py +35 -3
  7. {wdm_transform-0.2.0 → wdm_transform-0.3.0/src/wdm_transform.egg-info}/PKG-INFO +1 -1
  8. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_roundtrip.py +10 -14
  9. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_subband.py +2 -6
  10. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/.gitmodules +0 -0
  11. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/MANIFEST.in +0 -0
  12. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/README.md +0 -0
  13. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/pyproject.toml +0 -0
  14. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/setup.cfg +0 -0
  15. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/__init__.py +0 -0
  16. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/backends/__init__.py +0 -0
  17. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/backends/base.py +0 -0
  18. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/backends/cupy_backend.py +0 -0
  19. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/backends/jax_backend.py +0 -0
  20. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/backends/numpy_backend.py +0 -0
  21. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/datatypes/__init__.py +0 -0
  22. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/datatypes/series.py +0 -0
  23. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/datatypes/wdm.py +0 -0
  24. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/plotting.py +0 -0
  25. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/signal_processing.py +0 -0
  26. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/transforms/jax_backend.py +0 -0
  27. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/transforms/jax_subband.py +0 -0
  28. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/transforms/xp_backend.py +0 -0
  29. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/transforms/xp_subband.py +0 -0
  30. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform/windows.py +0 -0
  31. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform.egg-info/SOURCES.txt +0 -0
  32. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform.egg-info/dependency_links.txt +0 -0
  33. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform.egg-info/entry_points.txt +0 -0
  34. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform.egg-info/requires.txt +0 -0
  35. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/src/wdm_transform.egg-info/top_level.txt +0 -0
  36. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/conftest.py +0 -0
  37. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_api_helpers.py +0 -0
  38. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_benchmarking.py +0 -0
  39. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_docs.py +0 -0
  40. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_signal_processing.py +0 -0
  41. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_snr_consistency.py +0 -0
  42. {wdm_transform-0.2.0 → wdm_transform-0.3.0}/tests/test_validation.py +0 -0
@@ -1,3 +1,11 @@
1
+ ## v0.3.0 (2026-05-01)
2
+
3
+ ### fix
4
+
5
+ - workflows for pypi
6
+ - pytest fixes
7
+ - add gpu and cpu tracking on benchmarking
8
+
1
9
  ## v0.2.0 (2026-04-29)
2
10
 
3
11
  ### feat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wdm-transform
3
- Version: 0.2.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.md
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=DEFAULT_BACKENDS,
633
+ default=None,
605
634
  choices=["numpy", "jax", "cupy"],
606
- help="Backends to benchmark (default: numpy jax)",
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=args.backends,
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=args.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.2.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="jax")
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(test_outdir / "1_original.png", dpi=140)
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(test_outdir / "2_frequency.png", dpi=140)
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(test_outdir / "3_periodogram.png", dpi=140)
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(test_outdir / "4_spectrogram.png", dpi=140)
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(test_outdir / "5_wdm.png", dpi=140)
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(test_outdir / "6_wdm_fallback.png", dpi=140)
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(test_outdir / "7_reconstruction.png", dpi=140)
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(test_outdir / "8_residual_hist.png", dpi=140)
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(test_outdir / "subband_diagnostics_vertical.png", dpi=140)
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