fix: align rasterized artists with get_extent #742
Merged
Conversation
1bdf93c to
bf0d385
Compare
…ent) Images, labels, and datashader output were drawn with matplotlib's default pixel-center imshow extent (-0.5, W-0.5, ...), placing them half a pixel off the world coordinates used for the axis limits (get_extent), the affine box, and matplotlib point/shape overlays. The affine's linear part amplifies the constant 0.5 offset, so a Scale(1000) image shifted by 500 world units (#216). Switch to the pixel-edge convention (0, W, H, 0) so each artist's data box is the same [0, shape] box get_extent transforms — they now coincide under any affine. Labels are drawn outside the shared _ax_show_and_transform helper via a direct imshow(origin="lower"), so they get an explicit extent=(0, W, 0, H) (origin-lower order) to match. This also removes the residual half-canvas-pixel offset of datashader points relative to the matplotlib backend. The pixel-edge convention is spatialdata's own: get_extent reports an image as occupying [0, shape], and get_centroids places a single pixel's centroid at its half-integer center — so a get_centroids overlay now lands dead-center on its label pixel instead of on the corner. Tests: - Non-visual regressions (test_utils.py) asserting the rendered world box equals get_extent for images and labels (Identity + Scale), and that the datashader points image occupies the points' extent. - Visual regression (test_render_labels.py) overlaying get_centroids on a small label grid, where the half-pixel shift is large in display pixels: dots sit at pixel centers after the fix, at pixel corners before.
bf0d385 to
bfe97ea
Compare
Datashader points/shapes, image-extent, and a few image baselines shift by the half-pixel correction (sub-tolerance for large images, hence only 32 of them). Adds the baseline for the new centroid-alignment visual test. Regenerated from CI artifacts (py3.11-stable; 31/32 byte-identical to py3.14-stable).
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #742 +/- ##
=======================================
Coverage 79.59% 79.59%
=======================================
Files 17 17
Lines 4641 4641
Branches 1029 1029
=======================================
Hits 3694 3694
Misses 598 598
Partials 349 349
🚀 New features to boost your workflow:
|
Member
Author
|
FYI @LucaMarconato ignored until now because I didn't think it'd matter much on real data, but I guess good that it's eventually fixed |
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.
Closes #216.
Problem
Rasterized artists (images, labels, datashader output) used matplotlib's default pixel-center imshow extent
(-0.5, W-0.5, ...), while axis limits (get_extent), the element affine, and point/shape overlays use the pixel-edge convention. So every image sat half a pixel off — and the affine amplifies it (aScale(1000)image shifted by 500 world units).Fix
Use pixel-edge extent
(0, W, H, 0)so each artist's data box is the[0, shape]boxget_extenttransforms — they coincide under any affine._datashader.py_ax_show_and_transform— shared by images + datashader.render.py_render_labels— labels bypass that helper via a directimshow(origin="lower"), so they get an explicitextent=(0, W, 0, H).This also removes the residual datashader-points offset relative to the matplotlib backend.
Why pixel-edge
It's spatialdata's own convention:
get_extentreports an image as occupying[0, shape], andget_centroidsputs pixel[0,0]'s centroid at(0.5, 0.5). The fix matches the data model — aget_centroidsoverlay now lands dead-center on its label pixel instead of on the corner.