Skip to content

feat: Handle GeoTIFF transparency masks#309

Merged
kylebarron merged 19 commits intomainfrom
kyle/mask
Mar 5, 2026
Merged

feat: Handle GeoTIFF transparency masks#309
kylebarron merged 19 commits intomainfrom
kyle/mask

Conversation

@kylebarron
Copy link
Copy Markdown
Member

@kylebarron kylebarron commented Mar 4, 2026

Change list

  • Update tile fetching to fetch mask if one exists.
  • Ensure we clip mask together with tile when it's at the edge of an image.
  • Create new MaskTexture module for masking out invalid pixels on the GPU.
  • Update render-pipeline to automatically apply masking if it exists
  • Update integration tests, update render pipeline tests

Notes

Closes #196 Closes #168

Testing with https://maxar-opendata.s3.us-west-2.amazonaws.com/events/kentucky-flooding-7-29-2022/ard/17/031133010311/2021-07-03/10300100C12A9500-visual.tif

Before:

Screenshot 2026-03-05 at 2 40 35 PM

After:
Screenshot 2026-03-05 at 2 40 43 PM

Screen.Recording.2026-03-05.at.2.43.33.PM.mov

@github-actions github-actions bot added the feat label Mar 4, 2026
@kylebarron kylebarron changed the title feat: Fetch geotiff mask if it exists feat: Handle GeoTIFF transparency masks Mar 4, 2026
@kylebarron kylebarron requested a review from Copilot March 4, 2026 23:40
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds end-to-end support for GeoTIFF per-dataset nodata masks by fetching/decoding the mask alongside tile data and applying it in the Deck.gl raster shader pipeline to discard invalid pixels.

Changes:

  • Fetch mask tiles (when present) in parallel with data tiles and attach the decoded mask to RasterArray.
  • Add 1-bit (bit-packed) decoding support to the GeoTIFF decoder path to handle common mask encodings.
  • Introduce a new MaskTexture GPU shader module and update the render pipeline to upload/apply the mask texture automatically.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
packages/geotiff/src/fetch.ts Fetches and decodes mask tiles; attaches mask to returned raster arrays.
packages/geotiff/src/decode.ts Adds bit-packed (1-bit) unpacking to support mask decoding.
packages/geotiff/src/array.ts Updates mask semantics documentation (non-zero valid, 0 invalid).
packages/deck.gl-raster/src/gpu-modules/mask-texture.ts New shader module that discards fragments when mask indicates invalid pixels.
packages/deck.gl-raster/src/gpu-modules/index.ts Exports the new MaskTexture module.
packages/deck.gl-geotiff/src/geotiff/render-pipeline.ts Uploads mask textures and injects masking into the render pipeline when present.
Comments suppressed due to low confidence (1)

packages/geotiff/src/fetch.ts:110

  • When boundless: false is used, clipToImageBounds trims data/bands to match the clipped width/height, but it does not clip mask. With masks now being populated, edge tiles will return array.width/height smaller than tileWidth/tileHeight while array.mask remains full-tile length, breaking the stated contract (mask.length === width*height) and causing mask upload/rendering issues. clipToImageBounds should clip mask in the same way it clips pixel data.
  const array: RasterArray = {
    ...decodedPixels,
    count: samplesPerPixel,
    height: self.tileHeight,
    width: self.tileWidth,
    mask,
    transform: tileTransform,
    crs: self.crs,
    nodata: self.nodata,
  };

  return {
    x,
    y,
    array: boundless === false ? clipToImageBounds(self, x, y, array) : array,
  };

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/deck.gl-geotiff/src/geotiff/render-pipeline.ts Outdated
Comment thread packages/deck.gl-geotiff/src/geotiff/render-pipeline.ts
Comment on lines +111 to +118
if (geotiff.maskImage !== null) {
renderPipeline.push({
module: MaskTexture,
props: {
maskTexture: (data: TextureDataT) => data.mask as Texture,
},
});
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

MaskTexture is added to the pipeline when geotiff.maskImage !== null, but TextureDataT.mask is optional and the module props use a forced cast (data.mask as Texture). If a mask tile can't be fetched/decoded (e.g. zero-byte tile, missing tile, decode failure), this will attempt to bind undefined to a sampler2D and can break rendering. Consider making mask non-optional when a mask IFD exists, or conditionally injecting MaskTexture per-tile only when a mask texture is actually present.

Copilot uses AI. Check for mistakes.
Comment thread packages/geotiff/src/fetch.ts Outdated
Comment thread packages/geotiff/src/decode.ts
Comment thread packages/geotiff/src/decode.ts
Comment thread packages/deck.gl-raster/src/gpu-modules/mask-texture.ts
Comment thread packages/deck.gl-geotiff/src/geotiff/render-pipeline.ts
@kylebarron kylebarron enabled auto-merge (squash) March 5, 2026 20:43
@kylebarron kylebarron merged commit f35df81 into main Mar 5, 2026
3 checks passed
@kylebarron kylebarron deleted the kyle/mask branch March 5, 2026 20:45
@ds-release-bot ds-release-bot bot mentioned this pull request Mar 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for COGs with nodata masks Handle GeoTIFF nodata masks

2 participants