Skip to content

fix: Make the predictor fixtures test what their names say - #76

Merged
kylebarron merged 1 commit into
mainfrom
fix/predictor-fixtures
Oct 1, 2026
Merged

kylebarron merged 1 commit into
mainfrom
fix/predictor-fixtures

Conversation

@kylebarron

@kylebarron kylebarron commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Note

This PR was written by Claude (Claude Code), not by @kylebarron.

Follow-up to #74. That PR made write_cog actually write the predictor, but reviewing it showed the predictor fixtures still miss common reader bugs. This PR doesn't touch write_cog. It's rebased onto #75, and its fixtures regenerate byte for byte with #75's write_cog.

Changes

  • uint16_1band_lzw_block128_predictor2 gets new data and tiles.
    • The old np.arange data steps by 1 per pixel, so within a tile row the low byte never carries into the high byte. A reader that undoes predictor 2 byte by byte still decodes it correctly, and so does one that adds without wrapping.
    • Each row is now a triangle wave that falls and rises by 1000 per pixel.
    • The file is now 256×256 with the 128×128 tiles its name says, instead of 128×128 with 64×64 tiles. It still has four tiles and one overview.
  • uint16_1band_scale_offset drops predictor=2. fix: Write the predictor into COGs from write_cog #74 turned that predictor on, so a reader without predictor support started failing what looks like a scale/offset test. The .tif and _info.md are back to their exact pre-fix: Write the predictor into COGs from write_cog #74 bytes. Its docstrings no longer say LZW and 512×512.
  • New uint8_rgb_deflate_block64_predictor2. Every predictor fixture was single band, so a reader that differences adjacent samples instead of adjacent pixels passed them all. Each band changes differently along the rows.
  • New float32_1band_deflate_block64_predictor3. write_cog can write predictor 3 since fix: Write the predictor into COGs from write_cog #74, but no fixture had it. It uses the same np.linspace data as float32_1band_lerc_block32.

Simulated buggy readers

I re-applied predictor 2 to every tile row as rasterio reads it, then decoded it with simulated buggy readers. Each cell counts the tiles, across both levels, that the reader decodes correctly. The buggy readers should get none.

Fixture Correct reader Adds bytes without carry Adds without wrapping Adds to adjacent sample
uint16_…_predictor2 on main 5/5 5/5 5/5 n/a
uint16_…_predictor2 here 5/5 0/5 0/5 n/a
uint8_rgb_…_predictor2 5/5 n/a 0/5 0/5

Tests

tests/test_predictor.py now checks every generated fixture instead of four named ones. A fixture must carry a predictor on every IFD exactly when its name says predictorN. GDAL's mask IFDs are skipped because GDAL never writes a predictor on them: a masked predictor 2 COG has predictors [2, 1, 2, 1]. On main the test fails for uint16_1band_scale_offset.

pixi run check and pixi run test pass. All four new or changed files pass rio cogeo validate --strict.

Downstream

  • uint16_1band_lzw_block128_predictor2 changes size and tiling. Tests that compare against regenerated .npy tiles pick that up automatically. Anything that hard-codes its size or tile count needs an update.
  • uint16_1band_scale_offset no longer needs predictor support.

Merging with #73

🤖 Written by Claude Code

Follow-up to #74, which made write_cog actually write the predictor:

- uint16_1band_lzw_block128_predictor2: its arange data never carried
  from the low byte into the high byte within a tile row, so a reader
  that undoes predictor 2 byte by byte, or without wrapping, still read
  it correctly. Each row is now a triangle wave stepping by 1000 per
  pixel. The fixture is also 256x256 with the 128x128 tiles its name
  says, instead of 128x128 with 64x64 tiles.
- uint16_1band_scale_offset: drop the predictor, which #74 turned on,
  so a reader without predictor support doesn't fail this fixture's
  scale/offset test. The file is back to its pre-#74 bytes. Also fix
  its docstrings, which said LZW and 512x512.
- Add uint8_rgb_deflate_block64_predictor2: every predictor fixture was
  single band, so a reader that differences adjacent samples instead of
  adjacent pixels passed them all.
- Add float32_1band_deflate_block64_predictor3: write_cog can write
  predictor 3 now, but no fixture had it.

tests/test_predictor.py now checks every generated fixture: it carries a
predictor exactly when its name says so, ignoring GDAL's mask IFDs,
which never have one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kylebarron
kylebarron force-pushed the fix/predictor-fixtures branch from a50ca68 to f37ed41 Compare October 1, 2026 22:11
@kylebarron
kylebarron merged commit e222752 into main Oct 1, 2026
1 check passed
@kylebarron
kylebarron deleted the fix/predictor-fixtures branch October 1, 2026 22:32
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