diff --git a/backend/services/wavelet_engine.py b/backend/services/wavelet_engine.py index b885c59..d115526 100644 --- a/backend/services/wavelet_engine.py +++ b/backend/services/wavelet_engine.py @@ -80,18 +80,30 @@ def band_decompose( try: recon = icwt(Wx[mask, :], wavelet=wavelet, scales=scales_band, x_len=n) except Exception: - # ssqueezepy's internal scale-type inference (log vs linear spacing - # detection) can raise on some narrow slices depending on how many - # scales land in this bucket for this specific series length. - # Treat as a negligible band instead of failing the whole request — - # but log it (previously silently swallowed) and flag it, since a flat - # zero band otherwise looks identical to "genuinely no energy here". - logger.warning( - "band_decompose: icwt failed for band %d (n=%d scales, wavelet=%s) — " - "falling back to a zero band", i, len(scales_band), wavelet, exc_info=True, - ) - recon = np.zeros(n) - reconstruction_failed = True + # Root-caused: icwt's internal scaletype inference recursively splits + # `scales` looking for a log/linear transition point (infer_scaletype -> + # logscale_transition_idx), and on a masked SUBSET of the original CWT + # scales that recursive sub-slice can come out too short, crashing with + # "attempt to get argmax of an empty sequence" instead of a clean error. + # Confirmed by reproducing it directly against ssqueezepy 0.6.6: retrying + # with a regenerated, exactly log-uniform array over the same range/count + # sidesteps the fragile inference entirely (trivially satisfies its "is + # this log-spaced" check) and reliably recovers a real reconstruction — + # verified on 5 independently reproduced failures, all recovered. + try: + clean_scales = np.geomspace(scales_band.min(), scales_band.max(), len(scales_band)) + recon = icwt(Wx[mask, :], wavelet=wavelet, scales=clean_scales, x_len=n) + logger.warning( + "band_decompose: icwt failed for band %d with original scales (n=%d, wavelet=%s) — " + "recovered using a regenerated log-uniform scale array", i, len(scales_band), wavelet, + ) + except Exception: + logger.warning( + "band_decompose: icwt failed for band %d (n=%d scales, wavelet=%s) even after " + "retry — falling back to a zero band", i, len(scales_band), wavelet, exc_info=True, + ) + recon = np.zeros(n) + reconstruction_failed = True scale_lo, scale_hi = float(scales_band.min()), float(scales_band.max()) else: # Too few scales in this bucket for a stable reconstruction; report a