Skip to content

to_geotiff defaults 4-band rasters to RGB+alpha, tagging NIR/SWIR data as transparency #1769

Closed

Description

@brendancol

The CPU writer in xrspatial/geotiff/_writer.py assumes any array with 3 or more samples per pixel is RGB, and that a 4th band is an alpha channel:

photometric = 2 if samples_per_pixel >= 3 else 1
...
if photometric == 2 and samples_per_pixel > 3:
    n_extra = samples_per_pixel - 3
    extra_vals = [2] + [0] * (n_extra - 1)  # 2 = unassociated alpha
    tags.append((TAG_EXTRA_SAMPLES, SHORT, n_extra, extra_vals))

That is wrong for scientific multispectral data. A 4-band Landsat or Sentinel raster (R, G, B, NIR) is written with Photometric=RGB and ExtraSamples=[unassociated alpha]. Downstream tools then composite the NIR band as transparency.

The same pattern lives in write_streaming (lines ~1463-1479), so the dask path has the same bug.

A second problem makes it worse: a user who tries to override via extra_tags=[(TAG_EXTRA_SAMPLES, ...)] cannot. The writer adds its auto tag first, and the existing dedup loop drops the user tag because the tag id is already present.

Repro:

import numpy as np
import xarray as xr
from xrspatial.geotiff import to_geotiff
from xrspatial.geotiff._header import (
    TAG_PHOTOMETRIC, TAG_EXTRA_SAMPLES, parse_header, parse_ifd,
)

# 4-band NIR raster: red, green, blue, near-infrared
data = np.zeros((64, 64, 4), dtype=np.uint16)
da = xr.DataArray(data, dims=("y", "x", "band"))
to_geotiff(da, "nir.tif")

with open("nir.tif", "rb") as f:
    raw = f.read()
hdr = parse_header(raw[:8])
ifd, _ = parse_ifd(raw, hdr.ifd_offset, hdr.bigtiff)
print("Photometric:", ifd.get_value(TAG_PHOTOMETRIC, 1))   # 2 (RGB) -- wrong
print("ExtraSamples:", ifd.get_value(TAG_EXTRA_SAMPLES, 1)) # 2 (alpha) -- wrong

Proposed fix:

  1. Add a photometric kwarg to to_geotiff accepting 'auto' (default), 'rgb', 'rgba', 'minisblack', or an explicit integer. In 'auto' mode default to MinIsBlack for any band count, with ExtraSamples=[0]*N_extra (unspecified). RGB becomes opt-in via photometric='rgb'.
  2. Let user-supplied extra_tags override the auto-generated Photometric and ExtraSamples tags. Build the auto tags, drop any whose tag id appears in user extra_tags, then append the user tags. Restrict this to PHOTOMETRIC and EXTRA_SAMPLES so users cannot clobber IMAGE_WIDTH or strip offsets by accident.
  3. Document the kwarg and the override behavior in to_geotiff's docstring.

This is a behavior change. 4-band data that used to come out as RGB+alpha will come out as MinIsBlack + ExtraSamples=unspecified. That is the point: the old default was producing wrong files. Users with actual RGBA data can pass photometric='rgba' to opt back in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    apiAPI design and consistencybugSomething isn't workinghigh-priority

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions