Skip to content

Conversation

@d-chambers
Copy link
Contributor

@d-chambers d-chambers commented Aug 30, 2025

Description

This PR adds support for using percent (as in from dascore.units) in coord select and taper. It is interpreted as % of the coord range in these contexts.

Also added close option for Patch.equals so np.allclose is used for data comparison rather than strict equality.

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

    • Percentage-based selections and taper ranges using a percent unit (e.g., 10%).
    • Optional approximate equality for patches via a close flag.
    • Expanded taper window options and aliases (more window types available).
  • Changes

    • Coordinate range calculation now supports non-even coordinates and an optional "extend" behavior that alters range semantics.
    • Documentation and examples updated to show percent and absolute-unit usage and new window options.
  • Tests

    • Added tests for percentage selections, uneven coordinates, window aliases, and close-equality.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 30, 2025

Walkthrough

Adds percent-based value handling and a non-extending coord_range mode, centralizes window functions into a new signal utils module and updates taper to use it (including percent/uneven coords), introduces an approximate Patch.equals option, small import tweaks, and corresponding tests.

Changes

Cohort / File(s) Summary of Changes
Compat import adjustment
dascore/compat.py
Added ndarray to numpy imports; no behavioral changes.
Coords: coord_range and percent handling
dascore/core/coords.py
BaseCoord.coord_range(self, extend: bool = True) adds extend param. When extend=True requires evenly sampled coords and returns (max - min) + step; when extend=False allows non-even coords and returns (max - min). _get_compatible_value now recognizes percent (imports percent from dascore.units) and converts percent-valued inputs to absolute using coord_range(extend=False), marking them as relative.
Patch equality tolerance
dascore/proc/basic.py
equals(..., close=False) added; when close=True uses np.allclose for data comparison, otherwise uses exact equality. Docstring updated.
Taper via centralized signal utils
dascore/proc/taper.py
Replaced local window mapping/lookup with WINDOW_FUNCTIONS and _get_window_function from dascore.utils.signal; removed local mapping and SciPy windows import. Duration calculation uses coord.coord_range(extend=False). Docstrings/examples updated to reference WINDOW_FUNCTIONS and percent/absolute lengths.
Signal utilities module
dascore/utils/signal.py
New module introducing WINDOW_FUNCTIONS (mapping to scipy.signal.windows, including aliases) and _get_window_function(window_type) which returns the callable or raises ParameterError listing valid options.
Core tests: coords and patch
tests/test_core/*
Tests updated/added to import and use percent, to assert coord.coord_range(extend=False) behavior on uneven coords, and to validate equals(close=True/False) tolerance semantics.
Proc tests: taper
tests/test_proc/test_taper.py
Tests switched to WINDOW_FUNCTIONS, added percent-based taper equivalence tests, added fixtures/tests for uneven time coords, and updated imports.
Doc/example tweaks
dascore/proc/correlate.py, tests/test_proc/test_correlate.py
Example/test taper duration shortened from time=0.5 to time=0.05 (docstring and test change only).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller
  participant Coord as BaseCoord
  note over Coord: coord_range(extend=True|False)

  Caller->>Coord: coord_range(extend=True)
  alt extend=True
    Coord->>Coord: validate evenly sampled
    Coord-->>Caller: (max - min) + step
  else extend=False
    Coord-->>Caller: (max - min)
  end

  rect rgba(200,230,255,0.25)
  note right of Caller: Percent handling in value conversion
  Caller->>Coord: _get_compatible_value(value = x * percent)
  Coord->>Coord: coord_range(extend=False)
  Coord-->>Caller: absolute = (x/100) * range (marked relative)
  end
Loading
sequenceDiagram
  autonumber
  actor User
  participant Taper as proc.taper
  participant Signal as utils.signal
  participant Registry as WINDOW_FUNCTIONS
  participant Coord as BaseCoord

  User->>Taper: taper(patch, window_type, ...)
  Taper->>Signal: _get_window_function(window_type)
  Signal->>Registry: lookup window_type
  alt known
    Registry-->>Signal: window_fn
    Signal-->>Taper: window_fn
  else unknown
    Signal-->>User: raise ParameterError (list options)
  end
  Taper->>Coord: coord_range(extend=False)
  Coord-->>Taper: duration
  Taper-->>User: return tapered Patch
Loading
sequenceDiagram
  autonumber
  actor Caller
  participant Patch as Patch.equals
  participant Other as Other Patch

  Caller->>Patch: equals(Other, close)
  Patch->>Patch: compare coords & attrs
  alt close=True
    Patch->>Patch: np.allclose(self.data, other.data)
  else close=False
    Patch->>Patch: (self.data == other.data).all()
  end
  Patch-->>Caller: bool
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

proc

Poem

I twitch my whiskers at windows anew,
Percent of a range? I nibble right through.
Uneven trails, yet smooth I hop,
Close-enough carrots? I’ll call it a prop.
With tapered time and gentle flair—
Thump! Code’s trimmed with rabbit care. 🥕🐇


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b82fee3 and ed20ba7.

📒 Files selected for processing (1)
  • tests/test_core/test_patch.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_core/test_patch.py
⏰ 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). (15)
  • GitHub Check: test_code_min_deps (windows-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 (ubuntu-latest, 3.12)
  • GitHub Check: test_code_min_deps (macos-latest, 3.13)
  • GitHub Check: test_code (windows-latest, 3.12)
  • GitHub Check: test_code (macos-latest, 3.12)
  • GitHub Check: test_code (windows-latest, 3.10)
  • GitHub Check: test_code (macos-latest, 3.11)
  • GitHub Check: test_code (windows-latest, 3.11)
  • GitHub Check: test_code (ubuntu-latest, 3.10)
  • GitHub Check: test_code (macos-latest, 3.10)
  • GitHub Check: test_code (ubuntu-latest, 3.12)
  • GitHub Check: test_code (ubuntu-latest, 3.11)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch taper_percent

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
dascore/proc/taper.py (2)

25-27: Don’t use assert for runtime validation.

assert can be stripped with -O, turning bad input into silent misbehavior. Raise ParameterError instead.

-    if isinstance(value, Sequence | np.ndarray):
-        assert len(value) == 2, "Length 2 sequence required."
+    if isinstance(value, Sequence | np.ndarray):
+        if len(value) != 2:
+            raise ParameterError("Length-2 sequence required for taper start/stop.")

76-77: Docstring typos and style.

Fix capitalization and a typo to keep user-facing docs clean.

-        The type of window to use For tapering. Supported Options are:
+        The type of window to use for tapering. Supported options are:
-        If True, the values specified in kwargs are relateive to the
+        If True, the values specified in kwargs are relative to the
-    >>> # Apply an Hanning taper to 5% of each end for time dimension.
+    >>> # Apply a Hann taper to 5% of each end for time dimension.

Also applies to: 211-213, 220-222, 97-99

🧹 Nitpick comments (9)
dascore/proc/basic.py (1)

111-125: Consider configurable tolerance and NaN handling for equals(close=True)

Optional, but adds control and aligns NaN treatment with attrs. Also safely falls back for non-numeric dtypes.

Apply:

-def equals(self: PatchType, other: Any, only_required_attrs=True, close=False) -> bool:
+def equals(
+    self: PatchType,
+    other: Any,
+    only_required_attrs=True,
+    close=False,
+    *,
+    rtol: float = 1e-08,
+    atol: float = 1e-08,
+    equal_nan: bool = False,
+) -> bool:
@@
-    close
-        If True, the data can be "close" using np.allclose, otherwise
-        all data must be equal.
+    close
+        If True, use a tolerance-based comparison for data.
+    rtol, atol
+        Relative and absolute tolerances passed to numpy.allclose.
+    equal_nan
+        If True, NaNs compare equal when close=True.
@@
-    if close and not np.allclose(self.data, other.data):
-        return False
-    elif not close and not np.equal(self.data, other.data).all():
-        return False
+    if close:
+        # Guard for non-numeric dtypes.
+        if np.issubdtype(self.data.dtype, np.number) and np.issubdtype(other.data.dtype, np.number):
+            if not np.allclose(self.data, other.data, rtol=rtol, atol=atol, equal_nan=equal_nan):
+                return False
+        else:
+            if not np.array_equal(self.data, other.data):
+                return False
+    else:
+        if not np.equal(self.data, other.data).all():
+            return False

Also applies to: 151-156

tests/test_core/test_coords.py (1)

752-757: Tighten the percentage-length assertion

Use an absolute tolerance of ~1 sample for evenly sampled coords.

-        if coord.evenly_sampled:
-            assert abs((len(out) / len(coord)) - 0.70) < len(coord) / 100.0
+        if coord.evenly_sampled:
+            import pytest
+            assert len(out) == pytest.approx(0.70 * len(coord), rel=0, abs=1)
tests/test_proc/test_taper.py (2)

43-49: Minor: preserve original time range when crafting uneven times

Optional: derive uneven, sorted times from the existing time coord to keep range/units comparable across tests.

Example (conceptual):

  • take orig = patch.get_coord("time").values
  • add small, cumulative jitter to orig in its native units
  • sort, then update_coords(time=new_values)

143-147: Uneven-time taper smoke test

Covers the non-even sampling path; consider also asserting shape equality for completeness.

dascore/proc/taper.py (2)

30-35: Percent length calc now correct for uneven coords; one nit.

Using coord.coord_range(exact=False) fixes off-by-one for evenly sampled coords and enables percent semantics on irregular coords. Minor: consider including np.datetime64 in clses to avoid accidental scaling if absolute datetimes are ever passed.

-    clses = (Quantity, np.timedelta64)
+    clses = (Quantity, np.timedelta64, np.datetime64)

114-126: Micro: reuse window indexer.

window_inds is identical in both branches; compute once.

-    if samps[0] is not None:
+    window_inds = broadcast_for_index(n_dims, axis, slice(None), fill=None)
+    if samps[0] is not None:
         ...
-        window_inds = broadcast_for_index(n_dims, axis, slice(None), fill=None)
         out[data_inds] = out[data_inds] * window[window_inds]
     if samps[1] is not None:
         ...
-        window_inds = broadcast_for_index(n_dims, axis, slice(None), fill=None)
         out[data_inds] = out[data_inds] * window[window_inds]
dascore/utils/signal.py (2)

9-22: Window registry looks solid; consider making it immutable.

Freezing the mapping prevents accidental mutation at runtime.

+from types import MappingProxyType
 ...
-WINDOW_FUNCTIONS = dict(
+_WINDOW_FUNCTIONS = dict(
     barthann=windows.barthann,
     bartlett=windows.bartlett,
     blackman=windows.blackman,
     blackmanharris=windows.blackmanharris,
     bohman=windows.bohman,
     hamming=windows.hamming,
     hann=windows.hann,
     cos=windows.hann,
     nuttall=windows.nuttall,
     parzen=windows.parzen,
     triang=windows.triang,
     ramp=windows.triang,
 )
+WINDOW_FUNCTIONS = MappingProxyType(_WINDOW_FUNCTIONS)

25-35: Add type hints and tighten error message.

Minor ergonomics: hint the callable signature and list options as a comma-separated string.

-from dascore.exceptions import ParameterError
+from dascore.exceptions import ParameterError
+from collections.abc import Callable
+import numpy as np
 ...
-def _get_window_function(window_type):
+def _get_window_function(window_type: str) -> Callable[[int], np.ndarray]:
@@
-        msg = (
-            f"'{window_type}' is not a known window type. "
-            f"Options are: {sorted(WINDOW_FUNCTIONS)}"
-        )
+        opts = ", ".join(sorted(WINDOW_FUNCTIONS))
+        msg = f"'{window_type}' is not a known window type. Options are: {opts}"
dascore/core/coords.py (1)

618-624: Percent handling looks right; one edge-case to confirm.

If self.units is None and a non-percent Quantity is passed, convert_units will raise. Is that desired? If not, consider interpreting it as “set units” or raising ParameterError with a clearer message.

-            else:
-                value = convert_units(value.magnitude, self.units, value.units)
+            else:
+                if self.units is None:
+                    raise ParameterError("Coordinate has no units; cannot convert input.")
+                value = convert_units(mag, self.units, unit)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5edff1a and d75879e.

📒 Files selected for processing (8)
  • dascore/compat.py (1 hunks)
  • dascore/core/coords.py (3 hunks)
  • dascore/proc/basic.py (3 hunks)
  • dascore/proc/taper.py (4 hunks)
  • dascore/utils/signal.py (1 hunks)
  • tests/test_core/test_coords.py (3 hunks)
  • tests/test_core/test_patch.py (1 hunks)
  • tests/test_proc/test_taper.py (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
tests/test_core/test_patch.py (3)
tests/conftest.py (1)
  • random_patch (282-284)
dascore/core/coordmanager.py (1)
  • equals (729-739)
dascore/proc/basic.py (1)
  • equals (111-156)
dascore/utils/signal.py (1)
dascore/exceptions.py (1)
  • ParameterError (26-27)
dascore/proc/basic.py (3)
dascore/core/coordmanager.py (1)
  • equals (729-739)
dascore/core/coords.py (1)
  • data (605-607)
dascore/core/patch.py (1)
  • data (213-215)
tests/test_proc/test_taper.py (3)
dascore/utils/misc.py (1)
  • broadcast_for_index (136-157)
dascore/core/coords.py (8)
  • sorted (483-485)
  • sorted (1239-1241)
  • sorted (1460-1462)
  • get_coord (1471-1659)
  • sort (531-532)
  • sort (930-932)
  • sort (1132-1145)
  • sort (1304-1310)
dascore/proc/coords.py (2)
  • get_coord (87-139)
  • update_coords (218-243)
dascore/proc/taper.py (2)
dascore/utils/signal.py (1)
  • _get_window_function (25-35)
dascore/core/coords.py (4)
  • coord_range (509-528)
  • sorted (483-485)
  • sorted (1239-1241)
  • sorted (1460-1462)
tests/test_core/test_coords.py (2)
dascore/units.py (1)
  • get_quantity (70-94)
dascore/core/coords.py (9)
  • coord_range (509-528)
  • max (443-445)
  • min (438-440)
  • select (323-329)
  • select (953-973)
  • select (1105-1130)
  • select (1276-1302)
  • select (1387-1412)
  • evenly_sampled (478-480)
dascore/core/coords.py (2)
dascore/core/coordmanager.py (2)
  • coord_range (1031-1033)
  • convert_units (795-803)
dascore/units.py (1)
  • convert_units (120-155)
⏰ 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). (15)
  • GitHub Check: test_code (windows-latest, 3.10)
  • GitHub Check: test_code (macos-latest, 3.11)
  • GitHub Check: test_code (macos-latest, 3.10)
  • GitHub Check: test_code (windows-latest, 3.12)
  • GitHub Check: test_code (macos-latest, 3.12)
  • GitHub Check: test_code (windows-latest, 3.11)
  • GitHub Check: test_code_min_deps (windows-latest, 3.13)
  • GitHub Check: test_code_min_deps (windows-latest, 3.12)
  • GitHub Check: test_code (ubuntu-latest, 3.10)
  • GitHub Check: test_code (ubuntu-latest, 3.11)
  • GitHub Check: test_code (ubuntu-latest, 3.12)
  • GitHub Check: test_code_min_deps (macos-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 (11)
dascore/compat.py (1)

13-13: Importing numpy.ndarray via compat looks good

This cleanly exposes ndarray alongside floor and interp with no functional side effects.

tests/test_core/test_patch.py (1)

404-409: Nice addition validating equals(close=...) behavior

The test robustly exercises both strict and tolerance-based equality.

tests/test_core/test_coords.py (1)

360-367: LGTM: coord_range(exact=False) expectation on uneven coords

This correctly asserts max - min when step is undefined.

tests/test_proc/test_taper.py (3)

10-14: Imports updated to new window registry and units: good

Switch to taper, WINDOW_FUNCTIONS, and percent aligns with implementation changes.


25-31: Deterministic parametrization over window functions

Using sorted(WINDOW_FUNCTIONS) ensures stable test ordering.


137-142: Good parity check for percent vs fraction inputs

Validates new percent handling without over-constraining numerical tolerance.

dascore/proc/taper.py (2)

17-17: Good move: centralize window resolution.

Importing WINDOW_FUNCTIONS/_get_window_function from a shared utility avoids drift and duplication across modules.


158-173: Half-window sizing differs from taper(); confirm intent.

here: func(samps2+1) vs taper() using func(2val). The +1 shifts symmetry/peak by one sample. If unintentional, align both.

Would you like me to align the two implementations and update tests accordingly?

dascore/utils/signal.py (2)

1-3: Module docstring LGTM.

Clear purpose and scope.


25-35: No stale TAPER_FUNCTIONS references remain — grep confirms zero occurrences of TAPER_FUNCTIONS, and _get_window_function is defined solely in utils/signal.py (its calls in proc/taper.py are expected).

dascore/core/coords.py (1)

33-34: Public percent import: nice.

Exposes clear semantics for percentage-based inputs.

Comment on lines 509 to 529
def coord_range(self, exact: bool = True):
"""
Return a scaler value for the coordinate range (e.g., number of seconds).
Parameters
----------
exact
If true, only exact ranges are returned. This accounts for
spacing at the end of each sample. Consequently, exact is only
possible for evenly sampled coords. If false, just disregard
this if coord isnt't evenly sampled.
"""
if not self.evenly_sampled and exact:
raise CoordError("coord_range has to be called on an evenly sampled data.")
return self.max() - self.min() + self.step
step = getattr(self, "step", None)
coord_range = self.max() - self.min()
if step is not None:
coord_range += step
return coord_range

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

Bug: coord_range adds signed step; should add absolute step.

For reverse-sorted evenly sampled coords, adding a negative step shrinks the range (eg, 9 + (-1) -> 8). Use abs(step) so the inclusive range is correct. Also fix doc typos.

-        """
-        Return a scaler value for the coordinate range (e.g., number of seconds).
+        """
+        Return a scalar value for the coordinate range (e.g., number of seconds).
@@
-            possible for evenly sampled coords. If false, just disregard
-            this if coord isnt't evenly sampled.
+            possible for evenly sampled coords. If False, disregard this
+            when the coordinate isn't evenly sampled.
@@
-        step = getattr(self, "step", None)
+        step = getattr(self, "step", None)
         coord_range = self.max() - self.min()
-        if step is not None:
-            coord_range += step
+        if step is not None:
+            coord_range += np.abs(step)
         return coord_range
📝 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 coord_range(self, exact: bool = True):
"""
Return a scaler value for the coordinate range (e.g., number of seconds).
Parameters
----------
exact
If true, only exact ranges are returned. This accounts for
spacing at the end of each sample. Consequently, exact is only
possible for evenly sampled coords. If false, just disregard
this if coord isnt't evenly sampled.
"""
if not self.evenly_sampled and exact:
raise CoordError("coord_range has to be called on an evenly sampled data.")
return self.max() - self.min() + self.step
step = getattr(self, "step", None)
coord_range = self.max() - self.min()
if step is not None:
coord_range += step
return coord_range
def coord_range(self, exact: bool = True):
"""
Return a scalar value for the coordinate range (e.g., number of seconds).
Parameters
----------
exact
If True, only exact ranges are returned. This accounts for
spacing at the end of each sample. Consequently, exact is only
possible for evenly sampled coords. If False, disregard this
when the coordinate isn't evenly sampled.
"""
if not self.evenly_sampled and exact:
raise CoordError("coord_range has to be called on an evenly sampled data.")
step = getattr(self, "step", None)
coord_range = self.max() - self.min()
if step is not None:
coord_range += np.abs(step)
return coord_range
🤖 Prompt for AI Agents
In dascore/core/coords.py around lines 509 to 529, coord_range currently adds
the signed step which shrinks the range for reverse-sorted coords; change the
code to add abs(step) instead of step so the inclusive range uses the magnitude
of the spacing regardless of direction, and correct the docstring typos
("scaler" -> "scalar", "coord isnt't" -> "coord isn't"); keep the even-sampled
guard and behavior otherwise unchanged.

)
from dascore.exceptions import CoordError, ParameterError
from dascore.units import get_quantity
from dascore.units import get_quantity, percent
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify percent is exported from dascore.units

Ensure percent is a public symbol to avoid import errors.


🏁 Script executed:

#!/bin/bash
# Confirm 'percent' is defined/exported.
rg -nP 'percent' dascore/units.py
rg -nP 'from .*dascore\.units.* import .*percent' -S

Length of output: 240


Export percent in dascore/units.py: add an import or definition of percent (and include it in __all__ if used) so that from dascore.units import percent in tests doesn’t error.

🤖 Prompt for AI Agents
In dascore/units.py (top-level exports), tests import percent but it is not
exported; add a definition or import for percent (e.g., import or alias the
existing percent quantity/function from wherever it's defined) and ensure
percent is included in the module's __all__ so that from dascore.units import
percent works without error.

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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
dascore/proc/taper.py (2)

30-38: Regression: coord_range(exact=False) inflates duration by one step → overlapping tapers.

coord_range currently adds the final step regardless of exact, so using it here makes the left+right taper lengths exceed the dimension on some inputs, tripping “Taper windows cannot overlap” (seen in CI). For percent/decimal inputs, the duration should be the geometric span (max − min), not the step-inclusive range.

Fix:

-    dur = coord.coord_range(exact=False)
+    # Use geometric span (max - min) so two fractional tapers that sum to 1.0
+    # meet but do not overlap. coord_range includes the final step, which
+    # causes off-by-one overlaps in small arrays and fails CI.
+    dur = coord.max() - coord.min()

This restores previous non-overlapping behavior and keeps uneven-coord support (selection still happens via coord.select). If you want to keep using coord_range later, it likely needs an option to exclude the terminal step.


76-77: Fix user-facing doc typos/wording (critical polish).

A few small text issues in public docs/examples.

Apply:

@@
-    window_type
-        The type of window to use For tapering. Supported Options are:
+    window_type
+        The type of window to use for tapering. Supported options are:
@@
-        Used to specify the dimension along which to taper and the percentage
-        of total length of the dimension (if a decimal or percente, see examples),
-        or absolute units. If a single value is passed, the taper will be applied
-        to both ends. A length two tuple can specify different values for each
-        end, or no taper on one end.
+        Used to specify the dimension along which to taper and the percentage
+        of the dimension’s total length (if a decimal or percent; see examples),
+        or absolute units. If a single value is passed, the taper is applied
+        to both ends. A length-2 tuple can specify different values for each
+        end, or no taper on one end.
@@
-    >>> # Apply an Hanning taper to 5% of each end for time dimension.
+    >>> # Apply a Hann taper to 5% of each end of the time dimension.
@@
-    >>> # Apply a triangular taper to 10% of the start of the distance dimension.
+    >>> # Apply a triangular taper to 10% at the start of the distance dimension.
@@
-    window_type
-        The type of window to use For tapering. Supported Options are:
+    window_type
+        The type of window to use for tapering. Supported options are:
@@
-        If True, the values specified in kwargs are relateive to the
+        If True, the values specified in kwargs are relative to the
@@
-        # [2] and [3] are tapered and values inside [1] and [2] are
-        # not effected.
+        # [2] and [3] are tapered and values inside [1] and [2] are
+        # not affected.

Also applies to: 80-83, 98-103, 104-107, 110-111, 216-218, 225-227, 263-266

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

32-35: Percent Quantity handling may bypass scaling; confirm intended behavior.

20percent is a Quantity and will skip the dur scaling path here, relying on coord.select(relative=True) to interpret percent. Please confirm tests cover both float fractions (e.g., 0.1) and Quantity percents (e.g., 10percent) yielding identical indices. If not, normalize percent Quantities to absolute via the same dur used for floats.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d75879e and 2581e7e.

📒 Files selected for processing (2)
  • dascore/proc/correlate.py (1 hunks)
  • dascore/proc/taper.py (6 hunks)
✅ Files skipped from review due to trivial changes (1)
  • dascore/proc/correlate.py
🧰 Additional context used
🧬 Code graph analysis (1)
dascore/proc/taper.py (2)
dascore/utils/signal.py (1)
  • _get_window_function (25-35)
dascore/core/coords.py (4)
  • coord_range (509-528)
  • sorted (483-485)
  • sorted (1239-1241)
  • sorted (1460-1462)
🪛 GitHub Actions: TestCode
dascore/proc/taper.py

[error] 58-58: ParameterError: Taper windows cannot overlap (in _validate_windows) during taper() call in tests/test_proc/test_correlate.py::TestCorrelateInternal::test_basic_correlation


[error] 58-58: ParameterError: Taper windows cannot overlap (in _validate_windows) during taper() call in tests/test_proc/test_correlate.py::TestCorrelateInternal::test_transpose_independent


[error] 58-58: ParameterError: Taper windows cannot overlap (in _validate_windows) during taper() call in tests/test_proc/test_correlate.py::TestCorrelateInternal::test_correlation_freq_domain_patch


[error] 58-58: ParameterError: Taper windows cannot overlap (in _validate_windows) during taper() call in tests/test_proc/test_correlate.py::TestCorrelateInternal::test_correlate_decimated_patch


[error] 58-58: ParameterError: Taper windows cannot overlap (in _validate_windows) during taper() call in tests/test_proc/test_correlate.py::TestCorrelateInternal::test_correlate_units_raises


[error] 58-58: ParameterError: Taper windows cannot overlap (in _validate_windows) during taper() call in tests/test_proc/test_correlate.py::TestCorrelateInternal::test_correlate_units


[error] 58-58: ParameterError: Taper windows cannot overlap (in _validate_windows) during taper() call in tests/test_proc/test_correlate.py::TestCorrelateInternal::test_lag_deprecated

🪛 GitHub Actions: TestCodeMinDeps
dascore/proc/taper.py

[error] 58-58: Taper windows cannot overlap.

🔇 Additional comments (3)
dascore/proc/taper.py (3)

17-17: Good call: centralize window lookup via utils.signal.

Importing WINDOW_FUNCTIONS and _get_window_function removes duplication and keeps the registry in one place.


62-62: Doc options now sourced from WINDOW_FUNCTIONS — LGTM.

Keeps docs in sync with the centralized registry.


199-200: Doc list source update mirrors taper() — LGTM.

Keeps both entry points consistent.

@codecov
Copy link

codecov bot commented Aug 30, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.87%. Comparing base (5edff1a) to head (ed20ba7).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #522   +/-   ##
=======================================
  Coverage   99.87%   99.87%           
=======================================
  Files         121      122    +1     
  Lines        9912     9926   +14     
=======================================
+ Hits         9900     9914   +14     
  Misses         12       12           
Flag Coverage Δ
unittests 99.87% <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: 1

♻️ Duplicate comments (1)
dascore/core/coords.py (1)

509-525: Fix inclusive range for reverse-sorted coords; tighten docs; improve error message.

  • Use the magnitude of the step when extending the range so reverse-sorted coords don’t shrink the range.
  • Correct “scaler” → “scalar”; “If true” → “If True”; grammar in the error message.

Apply this diff:

 def coord_range(self, extend: bool = True):
     """
-        Return a scaler value for the coordinate range (e.g., number of seconds).
+        Return a scalar value for the coordinate range (e.g., number of seconds).

         Parameters
         ----------
         extend
-            If true, count the end of the range as max() + sample step. This
-            can only work for evenly sampled coordinates.
+            If True, count the end of the range as max() + |step|.
+            This only works for evenly sampled coordinates.
     """
-    if not self.evenly_sampled and extend:
-        raise CoordError("coord_range has to be called on an evenly sampled data.")
+    if not self.evenly_sampled and extend:
+        raise CoordError("coord_range(extend=True) requires an evenly sampled coordinate.")
     coord_range = self.max() - self.min()
     if extend:
-        coord_range += self.step
+        coord_range += np.abs(self.step)
     return coord_range
🧹 Nitpick comments (1)
tests/test_proc/test_correlate.py (1)

73-77: Typo in comment.

“autocorrection” → “autocorrelation”.

-        # normalize energy so autocorrection is 1
+        # normalize energy so autocorrelation is 1
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2581e7e and 1479feb.

📒 Files selected for processing (5)
  • dascore/core/coords.py (3 hunks)
  • dascore/proc/taper.py (6 hunks)
  • tests/test_core/test_coords.py (3 hunks)
  • tests/test_proc/test_correlate.py (1 hunks)
  • tests/test_proc/test_taper.py (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/test_core/test_coords.py
  • dascore/proc/taper.py
  • tests/test_proc/test_taper.py
🧰 Additional context used
🧬 Code graph analysis (2)
tests/test_proc/test_correlate.py (1)
dascore/proc/taper.py (1)
  • taper (63-132)
dascore/core/coords.py (2)
dascore/core/coordmanager.py (1)
  • coord_range (1031-1033)
dascore/exceptions.py (1)
  • CoordError (38-39)
⏰ 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). (15)
  • GitHub Check: test_code (windows-latest, 3.12)
  • GitHub Check: test_code (macos-latest, 3.12)
  • GitHub Check: test_code (macos-latest, 3.10)
  • GitHub Check: test_code (ubuntu-latest, 3.12)
  • GitHub Check: test_code (macos-latest, 3.11)
  • GitHub Check: test_code (windows-latest, 3.10)
  • GitHub Check: test_code (windows-latest, 3.11)
  • GitHub Check: test_code (ubuntu-latest, 3.10)
  • GitHub Check: test_code (ubuntu-latest, 3.11)
  • GitHub Check: test_code_min_deps (windows-latest, 3.13)
  • GitHub Check: test_code_min_deps (macos-latest, 3.13)
  • GitHub Check: test_code_min_deps (macos-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 (windows-latest, 3.12)
🔇 Additional comments (3)
tests/test_proc/test_correlate.py (1)

66-73: Audit and standardize taper unit usage
Multiple .taper(time=<float>) calls with ambiguous units (e.g. time=0.05) exist in tests, docs, examples, and src. Replace intended percentage tapers with time=5 * percent (and explicit seconds with time=0.5 * s) and update imports (from dascore.units import percent, s). Run

rg -nP '\.taper\([^)]*time\s*=\s*\d+(\.\d+)?(?!\s*\*)'

to locate and update all occurrences.

dascore/core/coords.py (2)

33-34: LGTM: percent import is appropriate for new percent-handling logic.


509-525: No changes needed for coord_range call sites
Verified that the only callers using the default extend=True—the CoordManager.coord_range convenience method and its use in patch.py—operate on evenly sampled coordinates, and all other consumers explicitly pass extend=False where required.

Comment on lines 613 to 620
if hasattr(value, "units"):
value = convert_units(value.magnitude, self.units, value.units)
mag, unit = value.magnitude, value.units
if unit == percent:
value = (mag / 100.0) * self.coord_range(extend=False)
relative = True
else:
value = convert_units(value.magnitude, self.units, value.units)
# if null or ... just return None
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

Percent × timedelta64 can raise; convert range to float first.

Multiplying a float by numpy timedelta64 throws a TypeError. Convert the range to a numeric (via to_float) before scaling by percent, then let the existing relative/datetime path convert back as needed.

Apply this diff:

-        if hasattr(value, "units"):
-            mag, unit = value.magnitude, value.units
-            if unit == percent:
-                value = (mag / 100.0) * self.coord_range(extend=False)
-                relative = True
-            else:
-                value = convert_units(value.magnitude, self.units, value.units)
+        if hasattr(value, "units"):
+            mag, unit = value.magnitude, value.units
+            if unit == percent:
+                base = to_float(self.coord_range(extend=False))
+                value = (mag / 100.0) * base
+                relative = True
+            else:
+                value = convert_units(mag, self.units, unit)
📝 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 hasattr(value, "units"):
value = convert_units(value.magnitude, self.units, value.units)
mag, unit = value.magnitude, value.units
if unit == percent:
value = (mag / 100.0) * self.coord_range(extend=False)
relative = True
else:
value = convert_units(value.magnitude, self.units, value.units)
# if null or ... just return None
if hasattr(value, "units"):
mag, unit = value.magnitude, value.units
if unit == percent:
base = to_float(self.coord_range(extend=False))
value = (mag / 100.0) * base
relative = True
else:
value = convert_units(mag, self.units, unit)
# if null or ... just return None
🤖 Prompt for AI Agents
In dascore/core/coords.py around lines 613 to 620, multiplying a float percent
by a numpy timedelta64 can raise a TypeError; convert the coord range to a
numeric first before scaling. Replace the percent branch so it calls
to_float(self.coord_range(extend=False)) (or an equivalent numeric conversion)
and uses that numeric value for the (mag / 100.0) * range calculation, then set
relative = True so the existing relative/datetime conversion path handles
converting back as needed.

@d-chambers d-chambers merged commit 9232dd4 into master Aug 30, 2025
20 checks passed
@d-chambers d-chambers deleted the taper_percent branch August 30, 2025 21:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants