Skip to content

fix: Fourier-ratio leaves a pixel with a non-finite power-law continuation unanswered - #80

Merged
Paullo9 merged 1 commit into
mainfrom
issue-68-fourier-ratio-bad-pixel
Nov 1, 2026
Merged

Paullo9 merged 1 commit into
mainfrom
issue-68-fourier-ratio-bad-pixel

Conversation

@Paullo9

@Paullo9 Paullo9 commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Requested by Paul · project thread

Found in an app EELS room on a real STEM spectrum image: Fourier-ratio failed the whole map when one pixel had a bad low-loss tail.

Cause

fourierRatio extends both spectra with the two-area power law before the FFT. On a pixel whose last 100 low-loss channels are near zero then large, r comes out large and negative (here -143), A stays finite (8e-296), and (scale * A) * pow(x, -r) overflows to inf on the continuation. The extended LowLossSpectrumImage then refuses its counts, so the call threw nonFiniteCounts(pixel: 5) and no pixel was deconvolved.

Refuted if: a pixel with a finite continuation fails the same way. Checked: the public powerLawExtrapolation of the bad image throws, the clean image does not.

What exSpy 0.3.2 does (References venv, python -I)

power_law_extrapolation warns overflow encountered in power and leaves inf at that pixel; fourier_ratio_deconvolution returns NaN at that pixel only and finite, unchanged values everywhere else. When the bad pixel is pixel 0 and the FWHM is estimated (exSpy measures the first pixel), the FWHM is NaN and every pixel is NaN.

Fix

  • A package helper extrapolates with a non-finite continuation zeroed and flagged per pixel (low loss and core loss). fourierRatio gives that pixel zeros and unavailable = .nonFiniteResult, the same convention as its other unanswered pixels. The other pixels are bit-identical.
  • Pixel 0 bad with an estimated FWHM: throws noPeakWidth (exSpy: NaN everywhere). With fwhmEV given, only pixel 0 is unanswered.
  • The public powerLawExtrapolation is unchanged (it still throws on an overflowing pixel). No public API change.
  • Whole-input validation kept: non-finite counts in the input images are still refused by their initialisers.

Numbers move

The failed pixel goes from a whole-map error to an unanswered pixel. Synthetic 4 x 3 image, 256 channels, threshold 2.5 eV:

case before after exSpy 0.3.2
low-loss continuation overflows at pixel 5, FWHM estimated throws nonFiniteCounts(pixel: 5), 0 of 12 pixels pixel 5: zeros + nonFiniteResult; 11 of 12 answered pixel 5 NaN; 11 of 12 finite
same, pixel 1, channels 60 to 63 no result -0.0171631, -0.0023426, -0.0368282, 0.0380497 -0.0171631, -0.0023426, -0.0368282, 0.0380497 (within rtol 1e-7 + atol 1e-7 x max)
same, the 11 good pixels against the image without the bad pixel no result bit-identical identical
core-loss continuation overflows at pixel 0 throws nonFiniteCounts(pixel: 0) pixel 0 unanswered, 11 bit-identical pixel 0 NaN
low-loss overflow at pixel 0, FWHM estimated throws nonFiniteCounts(pixel: 0) throws noPeakWidth (pass a FWHM) all 12 pixels NaN
low-loss overflow at pixel 0, FWHM 1.2 eV given throws nonFiniteCounts(pixel: 0) pixel 0 unanswered, 11 bit-identical pixel 0 NaN
any image without an overflowing pixel unchanged unchanged (parity pins green)

Tests

  • Red first: testFourierRatioPixelWithNonFiniteContinuationIsUnavailable and testFourierRatioFirstPixelNonFiniteContinuation failed with nonFiniteCounts before the fix; testFourierRatioCoreLossNonFiniteContinuation added after review.
  • Fast gate: swift test -c release --parallel --skip SpectroKitParityTests green (133 + 322 + 66 + 1 tests, the known issues unchanged).
  • Parity target once: 83 tests green, DeconvolutionPinsTests included.
  • SwiftLint strict and SwiftFormat lint clean on the changed files.
  • Numerics review done: no defects; it asked for a core-loss test and stronger pixel-0 checks, both added.

This PR changes numbers a user sees: it waits for Paul.

🤖 Generated with Claude Code

…ation unanswered instead of failing the whole image

Found in an app EELS room on a real spectrum image: one pixel whose low-loss window gives a large negative r (A finite, A x^-r overflows on the continuation) made the extrapolated image unformable, so fourierRatio threw nonFiniteCounts(pixel:) for every pixel. exSpy 0.3.2 gives NaN at that pixel only (RuntimeWarning) and deconvolves the rest.

fourierRatio now extrapolates through a package helper that zeroes and flags such a pixel (low loss or core loss); the pixel gets zeros and nonFiniteResult, the others are bit-identical to before. When the flagged pixel is the first low-loss pixel and the FWHM is estimated from it, exSpy is NaN everywhere and the kit throws noPeakWidth. The public powerLawExtrapolation is unchanged.

Done: per-pixel unavailability for a non-finite continuation, three red-first tests. Next: the app shows unanswered pixels and turns raw errors into sentences.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Paullo9 Paullo9 self-assigned this Nov 1, 2026
@Paullo9

Paullo9 commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

Checked on 3 real EELS spectrum images through the macTEM EELS room, with this change on top of 0.12.0: the one image that failed before (the whole Fourier-ratio map, nonFiniteCounts) now completes with 1 pixel unanswered (nonFiniteResult), and the fit after it runs on the rest. The other two images give a bit-identical Fourier-ratio result, and Align, thickness, edges, NNLS, Fourier-log and Richardson-Lucy are bit-identical on all three.

@Paullo9
Paullo9 merged commit 19ff45a into main Nov 1, 2026
7 checks passed
@Paullo9
Paullo9 deleted the issue-68-fourier-ratio-bad-pixel branch November 1, 2026 17:56
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