Skip to content

Conversation

@d-chambers
Copy link
Contributor

@d-chambers d-chambers commented Sep 20, 2025

Description

This PR implements a thin patch wrapper around scipy's wiener filter. It also refactors some internals.

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes.
  • documented the new feature with docstrings or appropriate doc page.
  • included a test. See testing guidelines.
  • your name has been added to the contributors page (docs/contributors.md).
  • added the "ready_for_review" tag once the PR is ready to be reviewed.

Summary by CodeRabbit

  • New Features

    • Added Wiener filter for adaptive noise reduction with per-dimension window sizes and optional noise parameter; exposed via the processing package.
    • Introduced a window-size utility to compute per-dimension window sizes from unit- or sample-based inputs.
  • Refactor

    • Hampel filter now uses the new window-size utility and preserves integer output dtype.
    • Missing/ambiguous dimension errors now raise ParameterError with consolidated messaging.
  • Tests

    • Added comprehensive Wiener and window-size tests; updated tests to expect ParameterError.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 20, 2025

Walkthrough

Adds a new patch-level Wiener filter and re-exports it; centralizes per-dimension window-size logic via get_patch_window_size, refactors Hampel filter to use it, changes some error semantics to ParameterError, and updates/extends tests accordingly.

Changes

Cohort / File(s) Summary
Core wiring
dascore/core/patch.py
Added local alias wiener_filter = dascore.proc.wiener_filter inside Patch.select/iselect proc shortcuts.
Proc: Wiener filter
dascore/proc/wiener.py, dascore/proc/__init__.py
New wiener_filter(patch, *, noise=None, samples=False, **kwargs) implemented with scipy.signal.wiener and get_patch_window_size; re-exported in proc.__init__.
Proc: Hampel refactor
dascore/proc/hampel.py
Replaced custom window-size logic with get_patch_window_size, simplified data handling (use float64 view and cast back for integer inputs), kept public signature.
Utils: Window sizing & errors
dascore/utils/patch.py
Added get_patch_window_size(...) to compute per-dimension window sizes; changed get_dim_axis_value to raise ParameterError for invalid/extra dimension specs; added warnings usage and related validations (require_odd, min_samples, warn_above).
Tests: Proc filters
tests/test_proc/test_filter.py
Updated tests to expect ParameterError instead of PatchCoordinateError, adjusted messages and samples-related test renaming.
Tests: Hampel
tests/test_proc/test_hampel.py
Removed several window-size/validation-specific tests; added test ensuring integer input preserves integer output.
Tests: Wiener
tests/test_proc/test_wiener.py
New comprehensive test module covering Wiener filter behavior, parameter validation, dimension/window handling, metadata preservation, and multiple edge cases.
Tests: Utils
tests/test_utils/test_patch_utils.py
Added tests for get_patch_window_size; updated get_dim_axis_value tests to expect ParameterError; added coverage for require_odd, samples, min_samples, warnings, and invalid dims.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Implement Wiener Filter" is a short, single-sentence summary that directly names the primary change (adding a wiener_filter and associated wiring/tests) and contains no extraneous details or vague wording, so it accurately reflects the main change in the changeset.
Description Check ✅ Passed The PR description contains the required "Description" section and a Checklist matching the repository template and gives a concise summary of the work (a thin wrapper around scipy.signal.wiener plus internal refactors), but it leaves checklist items unchecked and does not reference any issue or supporting docs, so it is informative yet not fully documented for reviewers.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch wiener

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1dfb7f0 and 9b043a6.

📒 Files selected for processing (1)
  • tests/test_proc/test_wiener.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/test_proc/test_wiener.py (4)
dascore/exceptions.py (1)
  • ParameterError (26-27)
dascore/examples.py (1)
  • get_example_patch (659-706)
dascore/proc/wiener.py (1)
  • wiener_filter (15-82)
dascore/core/coordmanager.py (1)
  • step (1036-1038)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
  • GitHub Check: test_code (macos-latest, 3.13)
  • GitHub Check: test_code (windows-latest, 3.10)
  • GitHub Check: test_code (macos-latest, 3.12)
  • GitHub Check: test_code (windows-latest, 3.13)
  • GitHub Check: test_code (windows-latest, 3.11)
  • GitHub Check: test_code (macos-latest, 3.10)
  • GitHub Check: test_code (windows-latest, 3.12)
  • GitHub Check: test_code_min_deps (macos-latest, 3.12)
  • GitHub Check: test_code (ubuntu-latest, 3.11)
  • GitHub Check: test_code (ubuntu-latest, 3.10)
  • GitHub Check: test_code (ubuntu-latest, 3.13)
  • GitHub Check: test_code (ubuntu-latest, 3.12)
  • GitHub Check: test_code_min_deps (windows-latest, 3.13)
  • GitHub Check: test_code_min_deps (windows-latest, 3.12)
  • GitHub Check: test_code_min_deps (ubuntu-latest, 3.12)
  • GitHub Check: test_code_min_deps (ubuntu-latest, 3.13)
  • GitHub Check: test_code_min_deps (macos-latest, 3.13)
🔇 Additional comments (19)
tests/test_proc/test_wiener.py (19)

1-12: LGTM!

The imports and module structure are appropriate for testing the Wiener filter functionality. The import of ParameterError from dascore.exceptions aligns with the updated error semantics mentioned in the AI summary.


14-15: LGTM!

The test class structure is well-organized and follows pytest conventions.


17-25: LGTM!

The noisy_patch fixture is well-implemented with proper random seed for reproducibility. The noise level (0.1 standard deviation) is reasonable for testing filter effectiveness.


27-32: LGTM!

The uniform_patch fixture correctly creates a patch with constant data, which is useful for testing edge cases where the Wiener filter may encounter zero variance conditions.


34-39: LGTM!

The zero_patch fixture appropriately creates a patch with all-zero data for testing edge cases.


41-55: LGTM!

The basic functionality test is comprehensive, validating shape preservation, noise reduction (variance decrease), and return type. The assertions are appropriate and the test logic is sound.


57-69: LGTM!

This test effectively validates dimension-specific filtering along individual dimensions and combined dimensions. The shape preservation checks are appropriate for all scenarios.


71-85: LGTM!

This test properly validates the coordinate units vs samples functionality. The conversion logic using time_step * 5 is correct, and the shape comparison is appropriate since exact equality may not be expected due to floating-point precision.


87-100: LGTM!

This test appropriately validates different noise parameter values, ensuring both default and explicit noise values produce different results while maintaining shape consistency.


102-106: LGTM!

The parameter validation test correctly verifies that ParameterError is raised when no window size parameters are provided, which aligns with the implementation in dascore/proc/wiener.py.


108-117: LGTM!

This test appropriately handles the edge case of uniform data where the Wiener filter may produce NaN values due to zero variance. The comment correctly explains this expected behavior from SciPy's implementation.


119-128: LGTM!

Similar to the uniform data test, this appropriately handles zero data edge cases with proper expectations about potential NaN results.


130-141: LGTM!

This test thoroughly validates that coordinate preservation works correctly by checking both dimensions and coordinate arrays using np.testing.assert_array_equal for precise comparison.


143-156: LGTM!

This test properly validates metadata preservation while correctly excluding the history attribute, which gets updated by the patch_function decorator. The approach of removing history from both dictionaries before comparison is sound.


158-168: LGTM!

This test effectively validates various window sizes and includes a reasonable assertion that larger windows should provide smoothing (variance reduction).


170-179: LGTM!

This test appropriately validates multi-dimensional filtering with different window sizes per dimension and confirms noise reduction effectiveness.


181-188: LGTM!

This test correctly handles the edge case of filtering a patch reduced to essentially 1D by selecting a single distance sample. The test logic is appropriate.


190-215: LGTM!

This test provides a comprehensive validation of noise reduction effectiveness using a controlled scenario with known signal and noise characteristics. The use of a fixed random seed ensures reproducibility, and the variance comparison appropriately validates the filter's effectiveness.


217-231: LGTM!

This edge case test appropriately handles very small patches, which could be problematic for some filtering algorithms. The test uses appropriate window size (3) for the small data (3x3) and validates basic functionality.


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.

@d-chambers d-chambers added patch related to Patch class proc Related to processing module labels Sep 20, 2025
@codecov
Copy link

codecov bot commented Sep 20, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.92%. Comparing base (4e87a7b) to head (9b043a6).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #548   +/-   ##
=======================================
  Coverage   99.92%   99.92%           
=======================================
  Files         125      126    +1     
  Lines       10435    10455   +20     
=======================================
+ Hits        10427    10447   +20     
  Misses          8        8           
Flag Coverage Δ
unittests 99.92% <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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
dascore/utils/patch.py (2)

628-633: Clarify error message and tailor to allow_multiple

Message reads “one dimension name(s)”. Make it precise and reflect allow_multiple for better UX.

Apply this diff:

-        msg = (
-            "You must specify one dimension name(s) in args or kwargs. "
-            f"You passed the following kwargs: {kwargs} args: {args} "
-            f"to a patch with dimensions {patch.dims}"
-        )
+        expect = "at least one" if allow_multiple else "exactly one"
+        msg = (
+            f"You must specify {expect} dimension name in args or kwargs. "
+            f"You passed the following kwargs: {kwargs} args: {args} "
+            f"to a patch with dimensions {patch.dims}"
+        )

676-753: Optional: cap windows that exceed coordinate length (guard rails)

Consider an opt-in enforce_lt_coord to reject windows larger than coord length (using Coord.get_sample_count(..., enforce_lt_coord=True)). Keeps surprises down in very small arrays.

Example diff:

-def get_patch_window_size(
+def get_patch_window_size(
     patch: PatchType,
     kwargs: dict,
     samples: bool = False,
     *,
     require_odd: bool = False,
     warn_above: int | None = None,
     min_samples: int = 1,
+    enforce_lt_coord: bool = False,
 ) -> tuple[int, ...]:
@@
-        samps = coord.get_sample_count(val, samples=samples)
+        samps = coord.get_sample_count(
+            val, samples=samples, enforce_lt_coord=enforce_lt_coord
+        )
dascore/proc/hampel.py (1)

138-149: Avoid integer median quantization; compute med/MAD in float

Running median/MAD on integer arrays can truncate medians and subtly change thresholds. Do calculations in float, then cast final result back.

Apply this diff:

-    data = patch.data
+    data = patch.data
@@
-    if separable and sum(s > 1 for s in size) >= 2:
+    dataf = data.astype(np.float64, copy=False)
+    if separable and sum(s > 1 for s in size) >= 2:
@@
-        med = _separable_median(data, size, mode)
-        abs_med_diff = np.abs(data - med)
+        med = _separable_median(dataf, size, mode)
+        abs_med_diff = np.abs(dataf - med)
         mad = _separable_median(abs_med_diff, size, mode)
     else:
-        med, abs_med_diff, mad = _calculate_standard_median_and_mad(data, size, mode)
+        med, abs_med_diff, mad = _calculate_standard_median_and_mad(dataf, size, mode)
@@
-    out = np.where(thresholded > threshold, med, data)
+    out = np.where(thresholded > threshold, med, dataf)
     out = out.astype(data.dtype, copy=False)

Also applies to: 143-144

tests/test_utils/test_patch_utils.py (1)

812-814: Avoid catching broad Exception in tests

Assert specific exceptions to prevent masking real failures.

Apply these diffs:

-        with pytest.raises(Exception):  # Should raise during get_dim_axis_value
+        with pytest.raises(PatchCoordinateError):
             get_patch_window_size(simple_patch, {"invalid_dim": 5})
-        with pytest.raises(Exception):  # Should raise during require_evenly_sampled
+        with pytest.raises(CoordError):
             get_patch_window_size(irregular_patch, {"time": 0.5})

Also add the missing import (outside this hunk):

from dascore.exceptions import CoordError  # at the existing exceptions import

Also applies to: 827-829

tests/test_proc/test_wiener.py (1)

190-195: Remove unused fixture argument

noisy_patch isn’t used in this test.

Apply this diff:

-    def test_noise_reduction_effectiveness(self, noisy_patch):
+    def test_noise_reduction_effectiveness(self):
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f098be and 9d73b83.

📒 Files selected for processing (9)
  • dascore/core/patch.py (1 hunks)
  • dascore/proc/__init__.py (1 hunks)
  • dascore/proc/hampel.py (2 hunks)
  • dascore/proc/wiener.py (1 hunks)
  • dascore/utils/patch.py (3 hunks)
  • tests/test_proc/test_filter.py (3 hunks)
  • tests/test_proc/test_hampel.py (0 hunks)
  • tests/test_proc/test_wiener.py (1 hunks)
  • tests/test_utils/test_patch_utils.py (4 hunks)
💤 Files with no reviewable changes (1)
  • tests/test_proc/test_hampel.py
🧰 Additional context used
🧬 Code graph analysis (8)
tests/test_proc/test_wiener.py (5)
dascore/exceptions.py (1)
  • ParameterError (26-27)
dascore/examples.py (1)
  • get_example_patch (659-706)
dascore/core/patch.py (2)
  • data (237-239)
  • Patch (28-442)
dascore/proc/wiener.py (1)
  • wiener_filter (14-74)
dascore/core/coordmanager.py (1)
  • step (1036-1038)
dascore/proc/hampel.py (1)
dascore/utils/patch.py (1)
  • get_patch_window_size (676-752)
tests/test_proc/test_filter.py (3)
dascore/exceptions.py (1)
  • ParameterError (26-27)
dascore/proc/filter.py (2)
  • median_filter (201-249)
  • savgol_filter (321-378)
dascore/core/patch.py (2)
  • Patch (28-442)
  • data (237-239)
dascore/proc/wiener.py (1)
dascore/utils/patch.py (2)
  • get_patch_window_size (676-752)
  • patch_function (180-286)
dascore/core/patch.py (1)
dascore/proc/wiener.py (1)
  • wiener_filter (14-74)
dascore/proc/__init__.py (1)
dascore/proc/wiener.py (1)
  • wiener_filter (14-74)
tests/test_utils/test_patch_utils.py (4)
dascore/utils/patch.py (1)
  • get_patch_window_size (676-752)
dascore/exceptions.py (1)
  • ParameterError (26-27)
dascore/examples.py (1)
  • get_example_patch (659-706)
dascore/proc/coords.py (1)
  • get_coord (87-139)
dascore/utils/patch.py (4)
dascore/core/patch.py (4)
  • dims (212-214)
  • size (247-249)
  • data (237-239)
  • ndim (217-219)
dascore/exceptions.py (1)
  • ParameterError (26-27)
dascore/core/coords.py (5)
  • size (473-475)
  • data (606-608)
  • ndim (468-470)
  • get_coord (1515-1703)
  • get_sample_count (753-789)
dascore/proc/coords.py (1)
  • get_coord (87-139)
🪛 Ruff (0.13.1)
tests/test_proc/test_wiener.py

190-190: Unused method argument: noisy_patch

(ARG002)

tests/test_utils/test_patch_utils.py

812-812: Do not assert blind exception: Exception

(B017)


827-827: Do not assert blind exception: Exception

(B017)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
  • GitHub Check: test_code (windows-latest, 3.12)
  • GitHub Check: test_code (windows-latest, 3.11)
  • GitHub Check: test_code (windows-latest, 3.13)
  • GitHub Check: test_code (macos-latest, 3.10)
  • GitHub Check: test_code (macos-latest, 3.12)
  • GitHub Check: test_code (macos-latest, 3.13)
  • GitHub Check: test_code (windows-latest, 3.10)
  • GitHub Check: test_code (ubuntu-latest, 3.12)
  • GitHub Check: test_code (ubuntu-latest, 3.11)
  • GitHub Check: test_code (ubuntu-latest, 3.10)
  • GitHub Check: test_code (ubuntu-latest, 3.13)
  • GitHub Check: test_code_min_deps (windows-latest, 3.12)
  • GitHub Check: test_code_min_deps (ubuntu-latest, 3.12)
  • GitHub Check: test_code_min_deps (windows-latest, 3.13)
  • GitHub Check: test_code_min_deps (macos-latest, 3.12)
  • GitHub Check: test_code_min_deps (ubuntu-latest, 3.13)
  • GitHub Check: test_code_min_deps (macos-latest, 3.13)
🔇 Additional comments (5)
dascore/utils/patch.py (1)

635-637: Consistent exception type for input validation paths?

Ambiguous/missing dims raise ParameterError, while extra inputs raise PatchCoordinateError. If intentional, fine; otherwise consider standardizing on ParameterError for user input issues.

dascore/proc/hampel.py (1)

131-133: Good centralization of window sizing

Switching to get_patch_window_size simplifies validation and makes behavior consistent across filters.

dascore/proc/__init__.py (1)

18-19: Expose wiener_filter in proc package

Public re-export looks good and aligns with Patch method wiring.

dascore/core/patch.py (1)

349-350: Patch method alias for wiener_filter

Binding is consistent with other filters. No issues.

dascore/proc/wiener.py (1)

72-74: Thin wrapper is correct; consider dtype expectations

scipy.signal.wiener returns float; downstream code expecting float is fine, but confirm this matches other filters’ dtype behavior. If you need integer-preserving behavior, cast at the very end (with care re: precision).

@coderabbitai coderabbitai bot added the ready_for_review PR is ready for review label Sep 20, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
dascore/proc/wiener.py (1)

80-81: Consider warning on overly large windows for parity with Hampel.

Using warn_above mirrors Hampel’s UX and prevents accidental slow configs.

Apply this diff:

-    size = get_patch_window_size(patch, kwargs, samples, min_samples=1)
+    size = get_patch_window_size(patch, kwargs, samples, min_samples=1, warn_above=10)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d73b83 and 1dfb7f0.

📒 Files selected for processing (7)
  • dascore/proc/hampel.py (3 hunks)
  • dascore/proc/wiener.py (1 hunks)
  • dascore/utils/patch.py (3 hunks)
  • tests/test_proc/test_filter.py (4 hunks)
  • tests/test_proc/test_hampel.py (1 hunks)
  • tests/test_proc/test_wiener.py (1 hunks)
  • tests/test_utils/test_patch_utils.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_proc/test_filter.py
🧰 Additional context used
🧬 Code graph analysis (6)
dascore/proc/wiener.py (2)
dascore/exceptions.py (1)
  • ParameterError (26-27)
dascore/utils/patch.py (2)
  • get_patch_window_size (677-762)
  • patch_function (180-286)
tests/test_proc/test_hampel.py (2)
dascore/core/patch.py (2)
  • data (237-239)
  • dtype (252-254)
dascore/proc/hampel.py (1)
  • hampel_filter (39-157)
tests/test_proc/test_wiener.py (4)
dascore/exceptions.py (1)
  • ParameterError (26-27)
dascore/examples.py (1)
  • get_example_patch (659-706)
dascore/core/patch.py (2)
  • data (237-239)
  • Patch (28-442)
dascore/proc/wiener.py (1)
  • wiener_filter (15-82)
dascore/utils/patch.py (3)
dascore/exceptions.py (1)
  • ParameterError (26-27)
dascore/core/coords.py (3)
  • ndim (468-470)
  • get_coord (1515-1703)
  • get_sample_count (753-789)
dascore/proc/coords.py (1)
  • get_coord (87-139)
tests/test_utils/test_patch_utils.py (4)
dascore/exceptions.py (2)
  • CoordError (38-39)
  • ParameterError (26-27)
dascore/utils/patch.py (1)
  • get_patch_window_size (677-762)
dascore/examples.py (1)
  • get_example_patch (659-706)
dascore/proc/coords.py (2)
  • update_coords (218-243)
  • get_coord (87-139)
dascore/proc/hampel.py (2)
dascore/utils/patch.py (1)
  • get_patch_window_size (677-762)
dascore/core/patch.py (3)
  • size (247-249)
  • data (237-239)
  • dtype (252-254)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
  • GitHub Check: test_code (windows-latest, 3.10)
  • GitHub Check: test_code (windows-latest, 3.12)
  • GitHub Check: test_code (windows-latest, 3.13)
  • GitHub Check: test_code (ubuntu-latest, 3.11)
  • GitHub Check: test_code (windows-latest, 3.11)
  • GitHub Check: test_code (macos-latest, 3.12)
  • GitHub Check: test_code (ubuntu-latest, 3.12)
  • GitHub Check: test_code (ubuntu-latest, 3.13)
  • GitHub Check: test_code (macos-latest, 3.11)
  • GitHub Check: test_code (ubuntu-latest, 3.10)
  • GitHub Check: test_code (macos-latest, 3.13)
  • GitHub Check: test_code_min_deps (windows-latest, 3.13)
  • GitHub Check: test_code_min_deps (windows-latest, 3.12)
  • GitHub Check: test_code_min_deps (ubuntu-latest, 3.12)
  • GitHub Check: test_code_min_deps (macos-latest, 3.12)
  • GitHub Check: test_code_min_deps (ubuntu-latest, 3.13)
  • GitHub Check: test_code_min_deps (macos-latest, 3.13)
🔇 Additional comments (10)
tests/test_utils/test_patch_utils.py (5)

207-209: Good change: error type/message clarified for missing dim.

Switching to ParameterError with the “You must specify …” message aligns with utils.get_dim_axis_value behavior.


239-241: Good change: consistent error for multiple dims when not allowed.

ParameterError with the same message pattern keeps expectations consistent.


763-777: Odd-window handling tests look solid.

Covers both samples=False auto-bump and samples=True hard error paths.


785-804: Nice coverage for performance warnings.

Warn threshold behavior and the no-warning path are both exercised.


816-829: Even sampling requirement is validated correctly.

Expecting CoordError for non-even sampling matches patch.get_coord(require_evenly_sampled=True).

tests/test_proc/test_hampel.py (1)

391-397: Integer dtype preservation test is valuable.

Confirms round-and-cast behavior without over-constraining values.

dascore/proc/hampel.py (2)

131-137: Centralized window sizing via get_patch_window_size is the right move.

require_odd=True, min_samples=3, and warn_above=10 encode sensible defaults and simplify validation.


153-157: Safe dtype restoration.

Using float64 for ops, rint for integer inputs, then casting back avoids overflow and preserves original dtype.

tests/test_proc/test_wiener.py (1)

103-107: Test expectation depends on capitalized “Must specify”.

With the proposed wiener_filter message change, this will pass.

If the message is adjusted differently, update the regex here accordingly.

dascore/utils/patch.py (1)

629-635: Improved guidance in “You must specify …” message.

Clearer direction for callers; aligns with updated tests.

Comment on lines +73 to +79
if not kwargs:
msg = (
"To use wiener_filter you must specify dimension-specific window "
"sizes via kwargs (e.g., time=5, distance=3)"
)
raise ParameterError(msg)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix error-message to satisfy test expectation.

tests/test_proc/test_wiener.py expects “Must specify …”. Current message is lower‑case and will fail the regex.

Apply this diff:

@@
-    if not kwargs:
-        msg = (
-            "To use wiener_filter you must specify dimension-specific window "
-            "sizes via kwargs (e.g., time=5, distance=3)"
-        )
-        raise ParameterError(msg)
+    if not kwargs:
+        msg = (
+            "Must specify at least one dimension-specific window size via kwargs "
+            "(e.g., time=5, distance=3) to use wiener_filter."
+        )
+        raise ParameterError(msg)
📝 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
if not kwargs:
msg = (
"To use wiener_filter you must specify dimension-specific window "
"sizes via kwargs (e.g., time=5, distance=3)"
)
raise ParameterError(msg)
if not kwargs:
msg = (
"Must specify at least one dimension-specific window size via kwargs "
"(e.g., time=5, distance=3) to use wiener_filter."
)
raise ParameterError(msg)
🤖 Prompt for AI Agents
In dascore/proc/wiener.py around lines 73 to 79, the raised ParameterError
message starts with a lowercase "To use..." but tests expect the message to
begin with "Must specify ...". Update the message string so it starts with "Must
specify" and otherwise conveys the same guidance (e.g., "Must specify
dimension-specific window sizes via kwargs (e.g., time=5, distance=3)"). Ensure
the new message replaces the existing msg variable before raising
ParameterError.

Comment on lines +637 to 639
msg = f"The following input dimensions are not found in the patch. {remaining}"
raise PatchCoordinateError(msg)
# Ensure order is preserved (eg args, then kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Leave PatchCoordinateError here; normalize at the window API boundary.

Keeping this as PatchCoordinateError preserves existing behaviors. The re-raise in get_patch_window_size will satisfy new tests without breaking others.

🤖 Prompt for AI Agents
In dascore/utils/patch.py around lines 637 to 639, do not change the current
PatchCoordinateError raise here — keep raising PatchCoordinateError with the
message about missing input dimensions so existing behavior remains; instead,
update the window API boundary (specifically get_patch_window_size) to catch
this PatchCoordinateError and normalize/translate it there (re-raise or convert
to the new expected error form at that boundary) so new tests pass without
altering behavior elsewhere.

Comment on lines +677 to +763
def get_patch_window_size(
patch: PatchType,
kwargs: dict,
samples: bool = False,
*,
require_odd: bool = False,
warn_above: int | None = None,
min_samples: int = 1,
enforce_lt_coord: bool = False,
) -> tuple[int, ...]:
"""
Get window sizes for patch processing operations.
Parameters
----------
patch
The input patch.
kwargs
Keyword arguments specifying dimension names and their window sizes.
samples
If True, kwargs values are in samples; if False, in coordinate units.
require_odd
If True, require odd window sizes. When samples=False, even sizes
are adjusted to be odd. When samples=True, even sizes raise ParameterError.
warn_above
If specified, warn when any dimension window size exceeds this value.
min_samples
Minimum number of samples required per dimension.
enforce_lt_coord
If True, reject windows larger than coordinate length.
Returns
-------
Tuple of window sizes for each dimension of the patch data.
Raises
------
ParameterError
If window sizes are too small, or if require_odd=True and samples=True
but window size is even.
"""
# Handle empty kwargs case - return all ones
if not kwargs:
return tuple([1] * patch.data.ndim)

aggs = get_dim_axis_value(patch, kwargs=kwargs, allow_multiple=True)
size = [1] * patch.data.ndim

for name, axis, val in aggs:
coord = patch.get_coord(name, require_evenly_sampled=True)
samps = coord.get_sample_count(
val, samples=samples, enforce_lt_coord=enforce_lt_coord
)

# Check minimum samples requirement
if samps < min_samples:
msg = (
f"Window must have at least {min_samples} samples along each "
f"dimension. {name} has {samps} samples. Try increasing its value."
)
raise ParameterError(msg)

# Handle odd number requirement
if require_odd and (samps % 2 != 1):
if not samples:
# Adjust even sizes to odd when samples=False
samps += 1
else:
# Raise error when samples=True and size is even
msg = (
f"For clean median calculation, dimension windows must be odd "
f"but {name} has a value of {samps} samples."
)
raise ParameterError(msg)

# Issue warning for large window sizes
if warn_above is not None and samps > warn_above:
msg = (
f"Large window size ({samps} samples) in dimension '{name}' "
f"may result in slow performance. Consider reducing the window size."
)
warnings.warn(msg, UserWarning, stacklevel=3)

size[axis] = samps

return tuple(size)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

get_patch_window_size: add normalization of invalid-dim errors.

To satisfy new tests (invalid-dim → ParameterError) and keep lower-level semantics intact, wrap get_dim_axis_value in try/except and re-raise ParameterError. See suggested diff in tests/test_utils/test_patch_utils.py comment.

Additionally, minor doc nit: mention that when samples=True, kwargs values must be integers (enforced downstream).

🤖 Prompt for AI Agents
In dascore/utils/patch.py around lines 677 to 763, wrap the
get_dim_axis_value(...) call in a try/except that catches the library's
invalid-dim exception (e.g., InvalidDimError or whatever specific exception that
function raises), and re-raise a ParameterError with the same message (preserve
the original exception as the cause if desired) so tests expecting invalid-dim →
ParameterError pass; also update the function docstring to note that when
samples=True the kwargs values must be integers (this is enforced downstream).

Comment on lines +811 to +815
def test_invalid_dimension_raises(self, simple_patch):
"""Test invalid dimension name raises error."""
with pytest.raises(ParameterError):
get_patch_window_size(simple_patch, {"invalid_dim": 5})

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Mismatch: get_patch_window_size raises PatchCoordinateError on invalid dims.

This test expects ParameterError, but get_dim_axis_value currently raises PatchCoordinateError for extra/unknown dims and get_patch_window_size doesn’t normalize it. Wrap and re-raise as ParameterError in get_patch_window_size.

Apply this diff in dascore/utils/patch.py to normalize the exception:

@@
-    aggs = get_dim_axis_value(patch, kwargs=kwargs, allow_multiple=True)
+    try:
+        aggs = get_dim_axis_value(patch, kwargs=kwargs, allow_multiple=True)
+    except PatchCoordinateError as e:
+        # Normalize invalid/unknown dimension errors for window-sizing API.
+        raise ParameterError(str(e)) from e
📝 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 test_invalid_dimension_raises(self, simple_patch):
"""Test invalid dimension name raises error."""
with pytest.raises(ParameterError):
get_patch_window_size(simple_patch, {"invalid_dim": 5})
try:
aggs = get_dim_axis_value(patch, kwargs=kwargs, allow_multiple=True)
except PatchCoordinateError as e:
# Normalize invalid/unknown dimension errors for window-sizing API.
raise ParameterError(str(e)) from e
🤖 Prompt for AI Agents
In dascore/utils/patch.py around the get_patch_window_size function,
get_patch_window_size currently lets PatchCoordinateError bubble up when
unknown/extra dimension names are passed; update the function to catch
PatchCoordinateError raised during dimension normalization/lookup and re-raise
it as a ParameterError (preserving the original error message or including it)
so callers expecting ParameterError get a consistent exception; ensure other
exceptions are not swallowed and maintain existing return behavior.

@d-chambers d-chambers merged commit 603cff8 into master Sep 20, 2025
22 checks passed
@d-chambers d-chambers deleted the wiener branch September 20, 2025 20:06
@coderabbitai coderabbitai bot mentioned this pull request Sep 27, 2025
5 tasks
@coderabbitai coderabbitai bot mentioned this pull request Oct 4, 2025
4 tasks
@coderabbitai coderabbitai bot mentioned this pull request Oct 27, 2025
4 tasks
@coderabbitai coderabbitai bot mentioned this pull request Dec 18, 2025
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch related to Patch class proc Related to processing module ready_for_review PR is ready for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants