Skip to content

Add per-channel norm support for render_images - #572

Merged
timtreis merged 9 commits into
mainfrom
feature/issue-460-per-channel-norm
Apr 1, 2026
Merged

Add per-channel norm support for render_images#572
timtreis merged 9 commits into
mainfrom
feature/issue-460-per-channel-norm

Conversation

@timtreis

@timtreistimtreis commented Mar 31, 2026

Copy link
Copy Markdown
Member

Summary

  • ClosesDocument how to perform on-the-fly normalization for channels. #460render_images now accepts a list of Normalize objects for per-channel normalization
  • Essential for multi-channel protein/fluorescence data with vastly different intensity ranges per channel
  • The rendering pipeline already read per-channel norms from CmapParams — this change only widens validation and routes per-channel norms into the existing CmapParams creation loop

Changes

  • basic.py: Widen norm type to list[Normalize] | Normalize | None, update docstring, zip norm list with cmap list
  • utils.py: Accept list[Normalize] in validation, reject empty lists and non-Normalize elements
  • test_render_images.py: 5 new tests (per-channel norm, backward compat, length mismatch, empty list, invalid element)

Usage

frommatplotlib.colorsimportNormalizenorms= [
Normalize(vmin=0, vmax=0.05, clip=True), # dim channelNormalize(vmin=0, vmax=1.0, clip=True), # full rangeNormalize(vmin=0, vmax=0.5, clip=True), # medium
]
sdata.pl.render_images(
channel=[0, 1, 2],
norm=norms,
# cmaps are optiopnalcmap=[plt.cm.gray] *3,
).pl.show()

timtreisand others added 3 commits March 31, 2026 13:53
Accept a list of Normalize objects in render_images so each channel
can be normalized independently — essential for multi-channel protein
data with vastly different intensity ranges.
The rendering pipeline already reads per-channel norms from CmapParams,
so this change only widens the input validation and routes per-channel
norms into the existing CmapParams creation loop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…s only
- Use resolved per-element cmap for list branching so norm list works
correctly when cmap is auto-replicated
- Raise ValueError when norm is a list but cmap is not (no silent
pass-through to _prepare_cmap_norm)
- Restrict list norm validation to element_type="images" only — labels
has no list-norm support
- Add test for norm list without explicit cmap list
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Using the resolved (replicated) cmap for branching broke the scalar
cmap path — e.g. grayscale=True with cmap="viridis" entered the list
branch because the cmap was internally replicated to match channels.
Now only use the resolved cmap when norm is a list (the new feature
path); otherwise preserve the original user-supplied cmap for branching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented Apr 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.77778% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.17%. Comparing base (367204d) to head (f12292b).
⚠️ Report is 1 commits behind head on main.

Files with missing linesPatch %Lines
src/spatialdata_plot/pl/utils.py50.00%1 Missing and 2 partials ⚠️
src/spatialdata_plot/pl/render.py0.00%0 Missing and 1 partial ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #572 +/- ##
==========================================
- Coverage 75.61% 75.17% -0.44% 
==========================================
Files 10 10 Lines 2908 2929 +21 Branches 672 681 +9 ==========================================
+ Hits 2199 2202 +3 - Misses 428 445 +17 - Partials 281 282 +1 
Files with missing linesCoverage Δ
src/spatialdata_plot/pl/basic.py83.72% <100.00%> (+0.55%)⬆️
src/spatialdata_plot/pl/render.py86.69% <0.00%> (-0.17%)⬇️
src/spatialdata_plot/pl/utils.py65.32% <50.00%> (-0.85%)⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

timtreisand others added 6 commits April 1, 2026 12:12
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Improve error message when norm list + cmap resolution fails —
now mentions cmap/channel mismatch as possible cause instead of
the misleading "multiple colormaps are used"
2. Unwrap length-1 cmap_params lists to scalar so single-channel
images still take the fast path (proper norm in imshow + colorbar)
3. Make Normalize copy unconditional in the multi-channel compositing
loop — eliminates fragile conditional that only copied auto-ranging
norms, preventing any future cross-channel mutation bugs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…selected
When user specifies `channel=[0]` on a 3-channel image with
`cmap=['gray']`, the cmap matched the selected channel count but was
nullified because it didn't match the full image channel count. Now
the comparison uses the selected channel count (or full count when no
channel is specified).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Simplify error: "When 'norm' is a list, you must also pass a list
of colormaps via 'cmap' with matching length"
- Add test locking the cmap validation fix: cmap=['gray'] with
channel=[0] on a 3-channel image should succeed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restructure branching so scalar cmap_params is built directly when
there's only one cmap (len <= 1), instead of building a list and
unwrapping after. Also handles norm=[single_Normalize] gracefully
by extracting the scalar. Shortens the effective_cmap comment to
one line.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When norm is a list but no cmap is provided, auto-generate a default
cmap list (one per norm) so the per-channel rendering path works.
Users shouldn't need to write cmap=[plt.cm.gray]*3 just to use
per-channel normalization.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@timtreis
timtreis merged commit e54c74f into mainApr 1, 2026
8 checks passed
@timtreis
timtreis deleted the feature/issue-460-per-channel-norm branch April 1, 2026 11:15
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Document how to perform on-the-fly normalization for channels.

2 participants

@timtreis@codecov-commenter