Skip to content

Comments

feat(imaging): SpatialBinner + bin_size in analyze_imaging (#186)#242

Merged
KedoKudo merged 10 commits intofeature/172-2d-imagingfrom
feature/186-spatial-binner
Feb 21, 2026
Merged

feat(imaging): SpatialBinner + bin_size in analyze_imaging (#186)#242
KedoKudo merged 10 commits intofeature/172-2d-imagingfrom
feature/186-spatial-binner

Conversation

@KedoKudo
Copy link
Collaborator

@KedoKudo KedoKudo commented Feb 20, 2026

Summary

  • Adds SpatialBinner in src/pleiades/imaging/binner.py:
    • bin_hyperspectral: reduces (n_e, H, W)(n_e, H//N, W//N) via skimage.measure.block_reduce; uncertainty propagation σ_bin = σ_pixel / N
    • unbin_map: nearest-neighbour upscale of 2D maps with NaN preservation and shape cropping
    • unbin_results: upscales all maps in Imaging2DResults back to original resolution; restores source_hyperspectral
  • Adds bin_size: int = 1 parameter to analyze_imaging() (fully backward-compatible default)
  • Exports SpatialBinner from pleiades.imaging
  • 37 new unit tests in test_binner.py

Test plan

  • pixi run pytest tests/unit/pleiades/imaging/test_binner.py -v → 37 passed
  • pixi run pytest tests/unit/pleiades/imaging/ -v → 463 passed, 0 regressions
  • analyze_imaging(..., bin_size=1) behaves identically to before

🤖 Generated with Claude Code

…186)

Adds SpatialBinner (src/pleiades/imaging/binner.py) with:
- bin_hyperspectral: (n_e,H,W) -> (n_e,H//N,W//N) via skimage.measure.block_reduce
  with uncertainty propagation sigma_bin = sigma_pixel / bin_size
- unbin_map: nearest-neighbour upscale of 2D maps with NaN preservation
- unbin_results: full Imaging2DResults upscale to original resolution

Adds bin_size parameter to analyze_imaging() (default=1, fully
backward-compatible). When bin_size > 1, data is binned before fitting and
results are upscaled back to original shape with source_hyperspectral
restored and bin_size recorded in metadata.

Exports SpatialBinner from pleiades.imaging.__init__.
37 unit tests in test_binner.py; all 463 existing imaging tests still pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@KedoKudo KedoKudo force-pushed the feature/186-spatial-binner branch from 29baaa7 to a08ec8e Compare February 21, 2026 02:33
@KedoKudo KedoKudo self-assigned this Feb 21, 2026
KedoKudo and others added 8 commits February 20, 2026 21:41
…propagation

P1: When bin_size>1, pixel iteration now reads from the binned
HyperspectralData instead of the loader's original unbinned cube,
so pixel coordinates match the binned grid expected by the aggregator.

P2a: Crop spatial dimensions to exact multiples of bin_size before
block_reduce, avoiding zero-padded partial bins that bias edge values.

P2b: Propagate uncertainty as sqrt(sum(sigma_i^2))/N instead of
mean(sigma)/N, which is correct for heterogeneous per-pixel
uncertainties under independent Gaussian noise.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ROI was passed in original image coordinates to the binned pixel
iterator, causing IndexError on out-of-bounds access.  Now remap
start coords with floor division and end coords with ceiling
division (clamped to binned dimensions).  Also clamp ROI bounds
in _iter_hyperspectral_pixels as a safety net.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… clamping

Replace silent clamping with the same ValueError validation that
loader.iter_pixels uses, so invalid ROIs (reversed, out-of-range)
raise consistently regardless of bin_size.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move ROI bounds validation to right after image loading (before
binning), so invalid ROIs raise ValueError consistently in both
binned and non-binned paths.  The min() cap on remapped end coords
now only accounts for edge-block cropping, not missing validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…alidation

P1: NaN-padded edge pixels in success_mask are now set to False
(bool(np.nan) is truthy) via nan_to_num before bool cast, preventing
unfitted edges from being reported as successful.

P2a: Empty remapped ROI (x1==x2 when ROI falls in cropped region)
is now allowed and yields zero pixels instead of raising ValueError.

P2b: bin_size larger than image dimensions now raises ValueError
at bin time with a clear message instead of producing a zero-sized
binned image that fails unpredictably downstream.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
P1: Move bin_size to keyword-only (after *) in analyze_imaging to
preserve positional-argument compatibility with existing callers.

P3: The bin_size==1 branch in unbin_map now uses the same crop/pad
logic as the main path, so it returns exactly original_shape even
when the input map is smaller.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When ROI edges are not aligned to bin_size, the remapped binned ROI
expands to neighboring bins.  After unbinning, those extra pixels
are now masked to NaN (float maps) and False (success_mask) so
fitted values don't leak beyond the user-requested region.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…_map

For non-floating dtypes (e.g. integer count maps), np.nan cannot be
represented and silently becomes 0, making padding look like valid
data.  Now promote to float64 when the input dtype is not floating.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds spatial binning functionality to the imaging pipeline to improve signal-to-noise ratio by averaging N×N pixel blocks before fitting. The implementation includes a new SpatialBinner class with binning/unbinning methods, integration into the analyze_imaging() API via a backward-compatible bin_size parameter (default=1), and comprehensive test coverage (37 new unit tests).

Changes:

  • Adds SpatialBinner class in src/pleiades/imaging/binner.py with bin_hyperspectral(), unbin_map(), and unbin_results() methods for spatial resolution reduction and restoration
  • Integrates bin_size parameter into analyze_imaging() with ROI coordinate remapping and result unbinning logic
  • Adds 37 unit tests covering binning operations, uncertainty propagation, edge cases, and integration with analyze_imaging()

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
src/pleiades/imaging/binner.py New module implementing SpatialBinner class with spatial binning/unbinning and uncertainty propagation
src/pleiades/imaging/api.py Adds bin_size parameter, _iter_hyperspectral_pixels() helper, ROI remapping logic, and result unbinning integration
src/pleiades/imaging/__init__.py Exports SpatialBinner class
tests/unit/pleiades/imaging/test_binner.py Comprehensive test suite with 37 tests covering binning operations and integration scenarios

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Add metadata (source, binned flag) to PixelSpectrum in binned path
  for traceability, matching loader.iter_pixels behavior.
- Fix misleading uncertainty propagation docstring: now documents
  sqrt(sum(sigma_i^2))/N formula matching the implementation.
- Simplify redundant noise description in class docstring.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@KedoKudo KedoKudo merged commit 63d7633 into feature/172-2d-imaging Feb 21, 2026
3 checks passed
@KedoKudo KedoKudo deleted the feature/186-spatial-binner branch February 21, 2026 23:42
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