Repository navigation
Keep Deep Zoom levels registered (fix sub-pixel shift between zoom levels) - #164
Merged
Merged
Conversation
…ce box zDeepZoomGenerator.get_tile read an integer region and shrank it with Image.thumbnail, which keeps the region's aspect ratio. The last tile row and column of a level could come out one pixel smaller than the Deep Zoom grid expects, and their scale differed slightly from the other tiles, so neighbouring levels were displaced by up to ~0.9 px (visible as wobbling while the viewer blends levels). Tiles are now sampled from a fractional source box with the true scale between the level and the slide level (Image.resize(..., box=...)) and always have exactly the grid size. Adds a test that builds synthetic slides and checks tile sizes and the registration of each level against a reference reduction. Fixes #163 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Levels of slideio images were displaced against each other by up to ~0.65 px, which shows as wobbling while the viewer blends zoom levels: - slideio reports 1 / scale (16.0007 for a 16:1 level) but the levels are exact decimation grids; reads at positions that are not multiples of the downsample are displaced by up to a level pixel. SlideIOSlide now reports the exact integer downsample, snaps positions to it and requests size * downsample. - the scale from a slide level to a Deep Zoom level is derived from the downsample (level 0 size / downsample) instead of the stored level size. - CZI pyramids store levels that are displaced against each other (0.2-0.8 px). util.registration measures the displacement once per image (each level against the level below, three textured spots, parabolic refinement, chained to level 0, a level is corrected only if two spots agree) and get_tile applies it as an offset of the fractional sampling box. Level 0 is never shifted. The offsets are stored in the new table images_pyramidregistration (with the file size/mtime and the algorithm version they were measured on). The table is shared with the Rust port of the server, which creates it with the same DDL; the migration uses CREATE TABLE IF NOT EXISTS so either side can create it first. Fixes #165 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ation Register the pyramid levels of slideio formats (CZI, VSI)
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.
Fixes #163.
What
zDeepZoomGenerator.get_tilenow samples every tile from a fractional source box instead of shrinking an integer region withImage.thumbnail:level_size / slide_level_size;frame/planearguments untouched, ICC profile kept, background compositing kept);tile.resize(z_size, LANCZOS, box=...)resamples the fractional box to exactly the tile size (no extra pass when the region already fits).Tiles therefore always have the size the Deep Zoom grid expects and all tiles of a level share the same scale, so neighbouring levels stay registered. See #163 for the cause, measurements and alternatives.
Measured
Real server, PNG 1920×1371, level 8: shift against a reference reduction −0.85 px → −0.01 px; PNG 1969×4030, levels 8–10: up to −0.39 px → ≤ 0.01 px, mean absolute difference 10–12 → 4 grey levels. NDPI, MRXS, TIFF, MP4 and NIfTI images still serve tiles.
Test
exact/util/test_deepzoom_registration.py(plainunittest, no database): synthetic slides of 1920×1371 and 1969×1301 px; for five levels each it asserts the exact size of every tile and the registration of the level mosaic against a reference reduction (< 0.15 px, sub-pixel cross-correlation). Fails onmaster((240, 171) != (240, 172), …), passes with this change.Notes
resize(box=...); the requirements pin ≥ 10.3.DeepZoomGeneratorattributes the class already depends on (_get_tile_info,_z_dimensions,_l_dimensions,_slide_from_dz_level,_l0_l_downsamples,_l0_offset,_z_t_downsample,_z_overlap).🤖 Generated with Claude Code