Skip to content

Fix shaper.vals/calibration_data.freqs length mismatch on some Kalico builds - #282

Open
Hannott wants to merge 2 commits into
Frix-x:mainfrom
Hannott:fix/shaper-vals-length-mismatch
Open

Fix shaper.vals/calibration_data.freqs length mismatch on some Kalico builds#282
Hannott wants to merge 2 commits into
Frix-x:mainfrom
Hannott:fix/shaper-vals-length-mismatch

Conversation

@Hannott

@Hannott Hannott commented Jul 16, 2026

Copy link
Copy Markdown

Summary

SHAPER_CALIBRATE graph generation can crash with a ValueError: x and y must have same first dimension, but have shapes (N,) and (M,) matplotlib error, reported on KalicoCrew/kalico:main when a lower max_freq is configured (reproduced here with max_freq < 150).

Root cause: ShaperComputation.compute() assumes that on Klipper/Kalico builds where CalibrationResult has no freq_bins field, shaper.vals is already truncated to exactly match calibration_data.freqs. That assumption only holds when the requested max_freq is >= the shaper search's own internal frequency ceiling (MAX_SHAPER_FREQ, 150Hz on KalicoCrew/kalico). Internally, fit_shaper computes max_freq = max(max_freq, test_freqs.max()) -- so a lower requested max_freq gets silently overridden, and shaper.vals ends up longer than calibration_data.freqs, which was truncated with the original (lower) value. That length mismatch reaches matplotlib as an opaque crash during graph generation.

Fix

Since freq_bins is sorted ascending, both truncations are prefixes of the same underlying array -- slicing shaper.vals down to len(calibration_data.freqs) recovers the correct values without needing to know the installed Kalico/Klipper version's internal behavior. Falls back to edge-padding with a console warning in the (currently unobserved) case where vals is shorter than expected, rather than crashing.

Also fixes traceback.print_exc() being used inside an f-string in the graph-generation error handler (shaketune_process.py) -- it prints to stderr and returns None, which is why that error message always had a trailing literal None instead of the actual traceback.

Testing

Added test_shaper_length_mismatch.py, which drives the real ShaperComputation against a real Kalico checkout (KalicoCrew/kalico:main) with a synthetic capture, across max_freq values from 80-300:

  • Before this fix: fails for max_freq 80 and 100 (below MAX_SHAPER_FREQ) with the exact length-mismatch pattern; also reproduced the literal matplotlib crash end-to-end via shaketune.cli.
  • After this fix: passes for all 8 tested max_freq values, no regressions at the values that already worked.

Also verified with ruff check / ruff format --check (both files clean).

Summary by Sourcery

Handle length mismatches between shaper.vals and calibration_data.freqs on older Klipper/Kalico builds to prevent graph-generation crashes.

Bug Fixes:

  • Prevent matplotlib graph generation from crashing when shaper.vals has a different length than calibration_data.freqs by truncating or padding shaper values as needed.
  • Fix graph-generation error reporting so the full traceback is printed instead of a trailing literal 'None'.

Tests:

  • Add a regression test script that runs ShaperComputation against a real Kalico/Klipper checkout across multiple max_freq values to verify shaper.vals aligns with calibration_data.freqs.

… builds

ShaperComputation assumed that on Klipper/Kalico versions without a
freq_bins field on CalibrationResult, shaper.vals is already truncated
to exactly match calibration_data.freqs. That only holds when the
requested max_freq is >= the shaper search's own frequency ceiling
(MAX_SHAPER_FREQ, 150Hz on KalicoCrew/kalico) -- fit_shaper inflates
its internal max_freq to max(max_freq, test_freqs.max()), so a lower
requested max_freq gets silently overridden and shaper.vals ends up
longer than calibration_data.freqs. This reached matplotlib as an
opaque "x and y must have same first dimension" crash during graph
generation.

Since freq_bins is sorted ascending, both truncations are prefixes of
the same array, so slicing shaper.vals down to the length we need
recovers the correct values without needing to know the installed
Kalico/Klipper version's internal behavior. Falls back to edge-padding
with a warning in case vals is ever shorter than expected instead of
crashing.

Also fixes traceback.print_exc() being used inside an f-string in the
graph generation error handler -- it prints to stderr and returns
None, which is why that error message always had a trailing "None"
line instead of the actual traceback.

Adds test_shaper_length_mismatch.py, which reproduces the crash
against a real Kalico checkout with a synthetic capture (fails on
max_freq below 150 on the old code, passes across max_freq 80-300 with
this fix).
@sourcery-ai

sourcery-ai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Fixes a crash in SHAPER_CALIBRATE graph generation caused by a length mismatch between shaper.vals and calibration_data.freqs on older Klipper/Kalico builds with low max_freq, and improves error reporting plus adds a regression test that drives real Kalico firmware across multiple max_freq values.

File-Level Changes

Change Details Files
Guard ShaperComputation against shaper.vals/calibration_data.freqs length mismatches on older Klipper/Kalico builds.
  • Replace assumption that shaper.vals exactly matches calibration_data.freqs with logic that truncates shaper.vals to the expected length when longer.
  • Add a defensive path that pads shaper.vals via np.pad with edge mode when it is shorter than calibration_data.freqs, emitting a ConsoleOutput warning instead of crashing.
  • Document the internal fit_shaper/max_freq behavior and the previous matplotlib crash scenario in comments.
shaketune/graph_creators/computations/shaper_computation.py
Improve exception reporting when graph generation fails.
  • Replace traceback.print_exc() usage inside an f-string with traceback.format_exc() so the full traceback string is captured and printed without an extra literal 'None'.
shaketune/shaketune_process.py
Add a regression test that exercises ShaperComputation against a real Kalico/Klipper checkout across a range of max_freq values.
  • Introduce a synthetic accelerometer capture generator with resonance-like data extending beyond 300Hz.
  • Load actual shaper_calibrate/shaper_defs modules from a provided Kalico/Klipper checkout to use real firmware code in the test.
  • Drive ShaperComputation over a configurable list of max_freq values spanning below, at, and above MAX_SHAPER_FREQ, and assert that all shaper vals arrays match calibration_data.freqs length, failing with a clear summary if any mismatch is found.
test_shaper_length_mismatch.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="shaketune/graph_creators/computations/shaper_computation.py" line_range="120-127" />
<code_context>
+                # (a mismatch here used to reach matplotlib as an opaque "x and y must have the same
+                # first dimension" crash -- see issue with max_freq=300 on KalicoCrew/kalico).
+                n = len(calibration_data.freqs)
+                if len(shaper.vals) < n:
+                    ConsoleOutput.print(
+                        f'Warning: {shaper.name} returned fewer frequency bins than expected '
+                        f'({len(shaper.vals)} < {n}); padding with its last value. This may indicate '
+                        'an unsupported Klipper/Kalico version -- results near the high end of the '
+                        'graph may be inaccurate.'
+                    )
+                    vals_resampled = np.pad(shaper.vals, (0, n - len(shaper.vals)), mode='edge')
+                else:
+                    vals_resampled = shaper.vals[:n]
</code_context>
<issue_to_address>
**issue:** Guard against the case where `shaper.vals` is empty before using `np.pad(..., mode='edge')`.

If `shaper.vals` is ever empty, `np.pad(..., mode='edge')` will raise a `ValueError` because there is no edge element to replicate. Please add an explicit check for the empty case (e.g. `if not shaper.vals: ...`) or use `mode='constant'` for this branch so a Klipper/Kalico mismatch doesn’t become a hard crash.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread shaketune/graph_creators/computations/shaper_computation.py Outdated
np.pad with mode='edge' has no element to replicate when the input
array is empty, so it raises ValueError instead of the graceful
fallback this was meant to be. Handle len(shaper.vals) == 0 explicitly
with a zero-filled array instead.

Addresses Sourcery review comment on Frix-x#282.
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.

1 participant