Add Plot2D.set_display_window — re-window contrast without re-quantising - #61
Merged
Conversation
…tising
`set_clim` re-encodes the cached raw frame over the new range. That is the
right default: the codes then span exactly the visible band, so the contrast
on screen gets all 8 bits. But it means the pixels move, and the previous
band is gone — everything outside the new range is saturated to 0/255 in the
codes that get stored.
For a figure that has been SERIALISED that is the difference between having a
contrast control and not having one. `build_standalone_html` writes the codes
plus `raw_min`/`raw_max`, and the JS rebuilds its LUT from `display_min`/
`display_max` over that band (`_buildLut32`) — so a saved page can re-window
freely inside the band it was encoded with, and not at all outside it. Encode
with `set_clim` at the display window and the band IS the window: the identity
LUT, nothing to move.
The non-destructive path already existed, inlined in `set_clim`'s tile branch,
where re-quantising would re-encode a full-res frame on every drag tick. This
promotes it to a documented public method so a caller can choose the trade
deliberately:
p = ax.imshow(frame, vmin=lo, vmax=hi) # quantise over a WIDE band
p.set_display_window(black, white) # window inside it, no re-encode
The cost is precision — a window much narrower than the encoding band resolves
in coarser steps — so the docstring says to quantise over the range you want to
be able to reach.
Tests pin the distinction from both sides, including that `set_clim` in tile
mode and `set_display_window` produce identical state, so the two cannot
quietly diverge.
Assisted-by: Claude Opus 5 (1M context)
The orphan '+' form is for a change with no PR number; this one has one now, so towncrier can render the link. Assisted-by: Claude Opus 5 (1M context)
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #61 +/- ##
==========================================
+ Coverage 90.74% 90.88% +0.14%
==========================================
Files 40 40
Lines 4526 4532 +6
==========================================
+ Hits 4107 4119 +12
+ Misses 419 413 -6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
set_climre-encodes the cached raw frame over the new range. That is the rightdefault — the codes then span exactly the visible band, so the contrast on screen
gets all 8 bits — but it means the pixels move, and the previous band is gone:
everything outside the new range is saturated to 0/255 in the codes that get
stored.
For a serialised figure that is the difference between having a contrast
control and not having one.
build_standalone_htmlwrites the codes plusraw_min/raw_max, and the JS rebuilds its LUT fromdisplay_min/display_maxover that band (
_buildLut32). So a saved page can re-window freely inside theband it was encoded with, and not at all outside it. Encode with
set_climat thedisplay window and the band is the window: an identity LUT, nothing left to
move.
What
The non-destructive path already existed — inlined in
set_clim's tile branch,where re-quantising would re-encode a full-res frame on every drag tick. This
promotes it to a documented public method so a caller can choose the trade
deliberately:
Nothing is re-encoded and nothing travels but two floats. The cost is precision —
a window much narrower than the encoding band resolves in coarser steps — so the
docstring says to quantise over the range you want to be able to reach, and
set_climgains a cross-reference so the pair is discoverable from either side.No behaviour changes for existing callers.
Tests
anyplotlib/tests/test_plot2d/test_display_window.py, 10 cases pinning thedistinction from both sides:
set_display_windowleavesimage_b64and theraw_*band untouched;set_climre-encodes and collapsesraw_*onto the window;set_climfirstwould have thrown that away;
set_climin tile mode andset_display_windowproduce identical state, sothe two cannot quietly diverge.
Full suite: 2122 passed, 58 skipped.
Context
Written for SpyDE's report export, where a reader opening a saved HTML report
could not adjust the contrast of a figure at all — the pixels arrive hard-clipped
to whatever window was chosen when the report was written.
Assisted-by: Claude Opus 5 (1M context)