Skip to content

add adaptive spectral filter#670

Open
d-chambers wants to merge 1 commit into
masterfrom
afk
Open

add adaptive spectral filter#670
d-chambers wants to merge 1 commit into
masterfrom
afk

Conversation

@d-chambers

@d-chambers d-chambers commented May 16, 2026

Copy link
Copy Markdown
Contributor

Description

This PR adds Patch.adaptive_spectral_filter, a windowed adaptive spectral filter for DASCore patches.

The new method supports:

  • 1D adaptive frequency-domain filtering over one selected dimension.
  • 2D adaptive frequency-wavenumber (f-k) filtering over two selected dimensions, equivalent to the AFK method described by Isken et al. (2022).
  • ND patches by batching over all non-selected dimensions.
  • A SciPy backend for 1D and 2D filtering.
  • An optional Numba/rocket-fft backend for the 2D path.

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes.
  • documented the new feature with docstrings and/or appropriate doc page.
  • included tests. See testing guidelines.
  • added the "ready_for_review" tag once the PR is ready to be reviewed.

Summary by CodeRabbit

  • New Features

    • Added adaptive spectral filtering to patches for spectral enhancement and denoising with configurable window sizes, overlap, and exponent parameters.
    • Optional high-performance backend available for 2D filtering when rocket-fft is installed.
  • Chores

    • Added rocket-fft as an optional dependency.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds adaptive spectral filtering for DASCore patches with 1D/2D windowed FFT-based weighting, tapered overlap-add reconstruction, and optional Numba acceleration. Includes dimension resolution, coordinate-to-sample conversion, engine selection, comprehensive tests, and Patch API integration.

Changes

Adaptive Spectral Filtering Implementation and Integration

Layer / File(s) Summary
Triangular plateau taper utilities
dascore/utils/signal.py
New _triangular_taper, _cached_triangular_taper, and _triangular_taper_1d functions generate 1D/2D cached float32 tapers with configurable plateau regions for overlap-add windowing.
Core filtering pipeline with SciPy backend
dascore/proc/adaptive_spectral_filter.py (lines 1–267)
Implements _validate_filter_inputs, _prepare_work_arrays, _finalize_output, tile extraction, overlap-add reconstruction, and _adaptive_spectral_filter_scipy that applies power-law weighting (|spec|^exponent) to FFT coefficients with optional normalization.
Dimension resolution and overlap normalization
dascore/proc/adaptive_spectral_filter.py (lines 269–371)
Adds _get_dim_axis_values to resolve patch dimensions to axes, _dim_values_to_samples to convert window/overlap from coordinate units to samples, _normalize_overlap for defaults, and _get_engine to select SciPy or optional Numba backend.
Public patch method and orchestration
dascore/proc/adaptive_spectral_filter.py (lines 373–487)
Implements public adaptive_spectral_filter(patch, *, overlap, exponent, normalize_power, samples, engine, **kwargs) that resolves dimensions, converts units, reshapes data for batching, dispatches per-batch filtering, and restores dtype and coordinate order.
Optional Numba/rocket-fft backend for 2D
dascore/proc/_adaptive_spectral_filter_numba.py
Accelerated 2D filtering using Numba-compiled parity-based tile iteration with prange, njit-compiled helpers for complex magnitude, spectral weighting and normalization, and overlap-add with fastmath; shares validation and finalization with core module.
Proc module re-export and Patch integration
dascore/proc/__init__.py, dascore/core/patch.py
Re-exports adaptive_spectral_filter from dascore.proc and wires it as a Patch.adaptive_spectral_filter method shortcut alongside other filter aliases.
Comprehensive test coverage
tests/test_proc/test_adaptive_spectral_filter.py, tests/test_proc/test_taper.py
Tests patch-level behavior (dtype/shape/dims preservation, dimension reordering, batching), core validation (window power-of-two, overlap bounds, exponent finiteness), SciPy/Numba engines, taper geometry and cache safety, and when Numba is available, equivalence of implementations and private helper behavior; also fixes taper test regex escaping.
Dependencies and documentation
pyproject.toml, docs/references.bib
Adds rocket-fft to extras optional dependencies and includes a BibTeX citation for adaptive denoising reference.

Possibly related PRs

  • DASDAE/dascore#522: Adds the triang window-function registry that the triangular plateau taper generation now depends on.

Suggested labels

proc, patch, documentation

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding an adaptive spectral filter feature to the DASCore package.
Description check ✅ Passed The description includes the feature overview, supported capabilities, implementation details, and a completed checklist indicating documentation and tests were added.
Docstring Coverage ✅ Passed Docstring coverage is 97.50% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch afk

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added documentation Improvements or additions to documentation proc Related to processing module patch related to Patch class labels May 16, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dascore/proc/adaptive_spectral_filter.py`:
- Around line 79-85: The isinstance checks in the loop over window_size and
overlap use tuple syntax which Ruff disallows; update both checks to use union
type syntax: change isinstance(window, (int, np.integer)) to isinstance(window,
int | np.integer) and isinstance(axis_overlap, (int, np.integer)) to
isinstance(axis_overlap, int | np.integer) in the loop that iterates over axis,
(window, axis_overlap) (variables: window_size, overlap, window, axis_overlap,
axis).

In `@docs/references.bib`:
- Around line 71-78: Update the BibTeX record for isken2022denoising to include
missing volume and issue and correct the pages: add volume={231}, issue={2}, and
replace pages={ggac229} with the actual page range pages={944--949} while
keeping the rest of the fields (title, author, journal, year, doi) unchanged.

In `@tests/test_proc/test_adaptive_spectral_filter.py`:
- Around line 27-33: The default argument for time_step in _patch uses a runtime
call np.timedelta64(4, "ms") which triggers Ruff B008; change the signature to
accept time_step: Optional[...] = None (or a sentinel) and inside the _patch
function set time_step = np.timedelta64(4, "ms") if time_step is None, ensuring
the np.timedelta64 construction happens at call time rather than at function
definition time.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a69e9e8b-b37b-4392-adad-4ff01815a878

📥 Commits

Reviewing files that changed from the base of the PR and between e0b43ad and 6db1dd6.

📒 Files selected for processing (9)
  • dascore/core/patch.py
  • dascore/proc/__init__.py
  • dascore/proc/_adaptive_spectral_filter_numba.py
  • dascore/proc/adaptive_spectral_filter.py
  • dascore/utils/signal.py
  • docs/references.bib
  • pyproject.toml
  • tests/test_proc/test_adaptive_spectral_filter.py
  • tests/test_proc/test_taper.py

Comment on lines +79 to +85
for axis, (window, axis_overlap) in enumerate(zip(window_size, overlap)):
if not isinstance(window, (int, np.integer)):
msg = f"window_size[{axis}] must be an integer; got {window!r}."
raise ValueError(msg)
if not isinstance(axis_overlap, (int, np.integer)):
msg = f"overlap[{axis}] must be an integer; got {axis_overlap!r}."
raise ValueError(msg)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix isinstance syntax to resolve pipeline failure.

The CI is failing because Ruff UP038 requires using X | Y union syntax in isinstance calls instead of tuple syntax (X, Y).

Proposed fix
     for axis, (window, axis_overlap) in enumerate(zip(window_size, overlap)):
-        if not isinstance(window, (int, np.integer)):
+        if not isinstance(window, int | np.integer):
             msg = f"window_size[{axis}] must be an integer; got {window!r}."
             raise ValueError(msg)
-        if not isinstance(axis_overlap, (int, np.integer)):
+        if not isinstance(axis_overlap, int | np.integer):
             msg = f"overlap[{axis}] must be an integer; got {axis_overlap!r}."
             raise ValueError(msg)
🧰 Tools
🪛 GitHub Actions: LintCode / 0_lint_code.txt

[error] 80-80: ruff (hook id: ruff) failed: UP038 Use X | Y in isinstance call instead of (X, Y)


[error] 83-83: ruff (hook id: ruff) failed: UP038 Use X | Y in isinstance call instead of (X, Y)

🪛 GitHub Actions: LintCode / lint_code

[error] 80-83: ruff (hook id: ruff) failed with exit code 1. UP038: Use X | Y in isinstance call instead of (X, Y) (2 occurrences).

🪛 Ruff (0.15.12)

[warning] 79-79: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dascore/proc/adaptive_spectral_filter.py` around lines 79 - 85, The
isinstance checks in the loop over window_size and overlap use tuple syntax
which Ruff disallows; update both checks to use union type syntax: change
isinstance(window, (int, np.integer)) to isinstance(window, int | np.integer)
and isinstance(axis_overlap, (int, np.integer)) to isinstance(axis_overlap, int
| np.integer) in the loop that iterates over axis, (window, axis_overlap)
(variables: window_size, overlap, window, axis_overlap, axis).

Comment thread docs/references.bib
Comment on lines +71 to +78
@article{isken2022denoising,
title={De-noising distributed acoustic sensing data using an adaptive frequency-wavenumber filter},
author={Isken, Marius Paul and Vasyura-Bathke, Hannes and Dahm, Torsten and Heimann, Sebastian},
journal={Geophysical Journal International},
pages={ggac229},
year={2022},
doi={10.1093/gji/ggac229}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What are the correct bibliographic details for DOI 10.1093/gji/ggac229?

💡 Result:

The bibliographic details for the article associated with DOI 10.1093/gji/ggac229 are as follows: Authors: Isken, M. P., Vasyura-Bathke, H., Dahm, T., and Heimann, S. [1] Title: De-noising distributed acoustic sensing data using an adaptive frequency–wavenumber filter [1] Journal: Geophysical Journal International [2][1] Volume: 231 [2][1] Issue: 2 [2][1] Pages: 944–949 [2][1] Publication Date: 17 June 2022 (Advance Access) [3][2] DOI: 10.1093/gji/ggac229 [2][1]

Citations:


Add volume, issue, and correct page numbers to the BibTeX entry.

The DOI resolves correctly and most metadata matches the published article. However, the entry is missing the volume (231) and issue (2) numbers, and the pages field contains the article ID (ggac229) instead of the actual page range (944–949). Update the entry:

Corrected BibTeX entry
`@article`{isken2022denoising,
  title={De-noising distributed acoustic sensing data using an adaptive frequency-wavenumber filter},
  author={Isken, Marius Paul and Vasyura-Bathke, Hannes and Dahm, Torsten and Heimann, Sebastian},
  journal={Geophysical Journal International},
  volume={231},
  issue={2},
  pages={944--949},
  year={2022},
  doi={10.1093/gji/ggac229}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/references.bib` around lines 71 - 78, Update the BibTeX record for
isken2022denoising to include missing volume and issue and correct the pages:
add volume={231}, issue={2}, and replace pages={ggac229} with the actual page
range pages={944--949} while keeping the rest of the fields (title, author,
journal, year, doi) unchanged.

Comment on lines +27 to +33
def _patch(
shape: tuple[int, ...],
dims: tuple[str, ...],
*,
dtype=np.float32,
time_step=np.timedelta64(4, "ms"),
distance_step=1.0,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Move np.timedelta64 construction out of the default argument (Ruff B008).

Line 32 evaluates a function call at definition time, which can fail lint gates configured with Ruff B008.

💡 Proposed fix
+DEFAULT_TIME_STEP = np.timedelta64(4, "ms")
+
 def _patch(
     shape: tuple[int, ...],
     dims: tuple[str, ...],
     *,
     dtype=np.float32,
-    time_step=np.timedelta64(4, "ms"),
+    time_step=DEFAULT_TIME_STEP,
     distance_step=1.0,
 ) -> dc.Patch:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _patch(
shape: tuple[int, ...],
dims: tuple[str, ...],
*,
dtype=np.float32,
time_step=np.timedelta64(4, "ms"),
distance_step=1.0,
DEFAULT_TIME_STEP = np.timedelta64(4, "ms")
def _patch(
shape: tuple[int, ...],
dims: tuple[str, ...],
*,
dtype=np.float32,
time_step=DEFAULT_TIME_STEP,
distance_step=1.0,
🧰 Tools
🪛 Ruff (0.15.12)

[warning] 32-32: Do not perform function call np.timedelta64 in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_proc/test_adaptive_spectral_filter.py` around lines 27 - 33, The
default argument for time_step in _patch uses a runtime call np.timedelta64(4,
"ms") which triggers Ruff B008; change the signature to accept time_step:
Optional[...] = None (or a sentinel) and inside the _patch function set
time_step = np.timedelta64(4, "ms") if time_step is None, ensuring the
np.timedelta64 construction happens at call time rather than at function
definition time.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6db1dd6db9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

msg = f"overlap contains dimensions not being filtered: {sorted(extra)}"
raise ParameterError(msg)
return defaults | dict(overlap), frozenset(set(dims) - set(overlap))
return {dim: int(overlap) for dim in dims}, frozenset()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve unit-valued scalar overlap when samples=False

Do not coerce scalar overlap to int here. When users call adaptive_spectral_filter(..., samples=False, overlap=<unit value>), this path forces overlap into a Python integer before coordinate conversion, which raises for time-like values (e.g., np.timedelta64) and silently changes non-integer unit values. That breaks the documented coordinate-unit behavior for scalar overlap and causes valid 1D/2D calls with unit overlaps to fail before filtering starts.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented May 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.93%. Comparing base (e0b43ad) to head (6db1dd6).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff            @@
##           master     #670    +/-   ##
========================================
  Coverage   99.93%   99.93%            
========================================
  Files         135      137     +2     
  Lines       11663    11998   +335     
========================================
+ Hits        11655    11990   +335     
  Misses          8        8            
Flag Coverage Δ
unittests 99.93% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown

✅ Documentation built:
👉 Download
Note: You must be logged in to github and a DASDAE member to access the link.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation patch related to Patch class proc Related to processing module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant