Skip to content

fix: Write the predictor into COGs from write_cog - #74

Merged
kylebarron merged 1 commit into
developmentseed:mainfrom
james-willis:jw/write-cog-predictor
Oct 1, 2026
Merged

kylebarron merged 1 commit into
developmentseed:mainfrom
james-willis:jw/write-cog-predictor

Conversation

@james-willis

Copy link
Copy Markdown
Contributor

write_cog set predictor on the in-memory GTiff dataset but not on the COG copy, so every fixture generated with a predictor actually used none: tifffile reports PREDICTOR.NONE on every IFD of them. Found while making the big endian fixtures in #73.

This passes the predictor to the COG copy instead (on the in-memory dataset it had no effect on the output) and regenerates the four fixtures that ask for predictor 2:

  • uint16_1band_lzw_block128_predictor2
  • uint16_1band_scale_offset
  • uint8_1band_deflate_block128_unaligned_predictor2
  • uint8_1band_lzw_block64_predictor2

Their pixels are unchanged: every level of each regenerated file reads back identical to the committed version with rasterio. The only change in the _info.md files is a new PREDICTOR: 2 line.

Tests

tests/test_predictor.py checks that those four fixtures carry predictor 2 on every IFD. It fails on main (4 failed) and passes here. pixi run check and pixi run test pass.

Downstream

deck.gl-raster's integration-rasterio.test.ts passes (17/17) against the regenerated fixtures, both on deck.gl-raster main and with developmentseed/deck.gl-raster#687, so its predictor 2 decoding now gets real coverage and holds up.

#73's rewrite_big_endian passes the predictor explicitly because of this bug. It's still correct after this merges; whichever lands second, I can simplify it to take the predictor from the source.

write_cog set the predictor on the in-memory dataset only, not on the COG
copy, so every fixture generated with a predictor actually used none.
Pass it to the COG copy.

This regenerates the four fixtures that ask for predictor 2. Their pixels
are unchanged at every level; their info files now report PREDICTOR: 2.
A test checks that they carry the predictor on every IFD.
@james-willis
james-willis marked this pull request as ready for review October 1, 2026 21:08

@kylebarron kylebarron left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@kylebarron
kylebarron merged commit b7158c4 into developmentseed:main Oct 1, 2026
1 check passed
kylebarron added a commit that referenced this pull request Oct 1, 2026
GDAL only logs a warning when it ignores a creation option, and the
generators never show rasterio's log, so write_cog could silently write a
fixture without an option it was asked for, as happened with the predictor
in #74.

- Raise a ValueError when a predictor is combined with a codec other than
  DEFLATE, LZW or ZSTD. GDAL drops it for the others; for LZMA it doesn't
  even warn, although its own check says LZMA takes a predictor.
- Keep a `nodata` value passed with the default nodata_type instead of
  overwriting it with 0.
- Apply `scale` and `offset` independently instead of only when both are
  given.
- Drop `tiled` and `copy_src_overviews`, which the COG driver doesn't
  support, so they only produced warnings on every write.

No fixture changes: every existing call either passes a supported codec,
nodata=0, or both scale and offset.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
kylebarron added a commit that referenced this pull request Oct 1, 2026
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 added a commit that referenced this pull request Oct 1, 2026
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>
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.

2 participants