Context
An accuracy pass over xrspatial/geotiff/ turned up two correctness gaps, both in the read path. Filing together since the fix surface is small.
Partial-tile shape validation
_decode_strip_or_tile decompresses tile/strip bytes and reshapes straight to (height, width). If a TIFF is corrupt (truncated deflate stream) or a compressor misbehaves and produces too few or too many bytes, numpy.reshape raises ValueError: cannot reshape array of size N into shape (h, w). That message tells you nothing about which tile broke or what size was expected.
Note the legitimate case this should NOT break: a valid edge tile decompresses to the full tile_height x tile_width and we slice the top-left actual_h x actual_w. That has to keep working.
Fix: check chunk.size == expected before reshape and raise a ValueError naming the size mismatch.
ModelTransformationTag rotation/skew
_extract_transform() reads tag 34264 (4x4 affine matrix) and pulls M[0], M[5] for pixel sizes and M[3], M[7] for origin. Fine for axis-aligned transforms, but rotation (M[1], M[4]) and 3D coupling (M[2], M[6]) get dropped silently. A rotated GeoTIFF round-trips with corrupted coordinates and no warning to the caller.
Fix: detect non-zero rotation/skew and raise NotImplementedError pointing at ModelTransformationTag. Axis-aligned case keeps working.
Out of scope
The audit also flagged a possible MinIsWhite + windowed-read interaction (inversion seeing padded pixels). On close reading, the windowing path clamps r0/c0/r1/c1 to image bounds before allocating the output, so the result shape always matches the in-bounds region. No padding reaches the inversion. Dropping it.
Tests
- Truncated deflate stream raises ValueError with expected and actual sizes in the message.
- ModelTransformationTag with rotation raises NotImplementedError.
- Axis-aligned ModelTransformationTag (no PixelScale present) still extracts origin and pixel sizes correctly.
Context
An accuracy pass over
xrspatial/geotiff/turned up two correctness gaps, both in the read path. Filing together since the fix surface is small.Partial-tile shape validation
_decode_strip_or_tiledecompresses tile/strip bytes and reshapes straight to(height, width). If a TIFF is corrupt (truncated deflate stream) or a compressor misbehaves and produces too few or too many bytes,numpy.reshaperaisesValueError: cannot reshape array of size N into shape (h, w). That message tells you nothing about which tile broke or what size was expected.Note the legitimate case this should NOT break: a valid edge tile decompresses to the full
tile_height x tile_widthand we slice the top-leftactual_h x actual_w. That has to keep working.Fix: check
chunk.size == expectedbefore reshape and raise a ValueError naming the size mismatch.ModelTransformationTag rotation/skew
_extract_transform()reads tag 34264 (4x4 affine matrix) and pullsM[0],M[5]for pixel sizes andM[3],M[7]for origin. Fine for axis-aligned transforms, but rotation (M[1],M[4]) and 3D coupling (M[2],M[6]) get dropped silently. A rotated GeoTIFF round-trips with corrupted coordinates and no warning to the caller.Fix: detect non-zero rotation/skew and raise NotImplementedError pointing at ModelTransformationTag. Axis-aligned case keeps working.
Out of scope
The audit also flagged a possible MinIsWhite + windowed-read interaction (inversion seeing padded pixels). On close reading, the windowing path clamps
r0/c0/r1/c1to image bounds before allocating the output, so the result shape always matches the in-bounds region. No padding reaches the inversion. Dropping it.Tests