Skip to content

Fix get_j() feeding uninitialized memory into the Junge slope fit - #406

Open
animmosmith wants to merge 3 commits into
403-streamline-test-suite-with-shared-fixturesfrom
405-fix-junge-slope-uninitialized-memory
Open

Fix get_j() feeding uninitialized memory into the Junge slope fit#406
animmosmith wants to merge 3 commits into
403-streamline-test-suite-with-shared-fixturesfrom
405-fix-junge-slope-uninitialized-memory

Conversation

@animmosmith

Copy link
Copy Markdown
Collaborator

Summary

Fixes #405. Found via a NumPy UserWarning surfaced in #404's CI (an unrelated PR - just where it was noticed).

get_j() used np.log's where= to skip non-positive number_distribution bins (to avoid log(0)/log(negative)), added in #314 specifically to "handle zero values in number_distribution[ind]". But where= without a matching out= leaves the skipped positions as uninitialized memory rather than excluding them, and that array was fed straight into np.polyfit for the Junge slope fit — a real, user-facing value returned by nc_vc_from_stats() and stored in image_stats['junge'].

Fix: excludes non-positive bins from the fit outright (properly completing the intent of #314's original fix, rather than changing it), and returns NaN when fewer than two bins remain in the 150-300um fitting range (e.g. an image with no particles in that range at all), rather than crashing or fitting garbage — matching how other undefined/no-data cases are already handled elsewhere in the codebase (e.g. PerClassConcentration's empty-image handling).

The 150-300um range itself is untouched — per the existing code comment, it's a deliberate choice to fit the slope only where LISST-100 and SilCam data are considered mutually valid/comparable, not an arbitrary restriction, so I didn't touch that part.

Test plan

  • Added test_get_j_excludes_non_positive_bins_from_fit — confirmed it fails against the old code (uninitialized memory produces a different, wrong slope) and passes with the fix
  • Added test_get_j_returns_nan_when_no_particles_in_fitting_range — covers the "zero valid bins" edge case that the naive exclude-fix alone would've crashed on (this exact case is exercised by the existing test_calculate_image_stats_uses_configured_path_length, which has only one synthetic particle far outside the 150-300um range)
  • Full pyopia/tests/test_pipeline.py (7 tests, including the real holo/silcam pipeline tests) passes

🤖 Generated with Claude Code

@animmosmith animmosmith self-assigned this Jul 30, 2026
@animmosmith animmosmith added bug Something isn't working priority-high labels Jul 30, 2026
@animmosmith
animmosmith requested a review from emlynjdavies July 30, 2026 17:02
animmosmith added a commit that referenced this pull request Jul 31, 2026
README's "Build docs locally" section now syncs the docs dependency group
via uv before building, matching the RTD config. Bumped version to 2.16.20
so this PR is ready to merge right after #394, #404, #406, #408, #410.
@animmosmith
animmosmith changed the base branch from main to 403-streamline-test-suite-with-shared-fixtures July 31, 2026 17:00
emlynjdavies
emlynjdavies previously approved these changes Aug 5, 2026
Fixes #405.

get_j() used np.log's `where=` to skip non-positive number_distribution
bins (to avoid log(0)/log(negative)), added in #314 specifically to "handle
zero values in number_distribution[ind]". But `where=` without a matching
`out=` leaves the skipped positions as uninitialized memory rather than
excluding them, and that array was fed straight into np.polyfit for the
Junge slope fit - a real, user-facing value returned by nc_vc_from_stats()
and stored in image_stats['junge'].

Excludes non-positive bins from the fit outright (properly completing the
intent of #314's fix), and returns NaN when fewer than two bins remain in
the 150-300um fitting range (e.g. an image with no particles in that
range), rather than crashing or fitting garbage - matching how other
undefined/no-data cases are already handled elsewhere in the codebase.

The 150-300um range itself is untouched: it's a deliberate choice (per the
existing comment) to fit the Junge slope only where LISST-100 and SilCam
data are considered mutually valid, not an arbitrary restriction.
@animmosmith
animmosmith force-pushed the 405-fix-junge-slope-uninitialized-memory branch from 6682b57 to 607665d Compare August 5, 2026 10:07
…hared-fixtures' into 405-fix-junge-slope-uninitialized-memory
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working priority-high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

get_j() feeds uninitialized memory into the Junge slope fit for zero-count bins

2 participants