Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions xrspatial/geotiff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@
transform_tuple_from_pixel_geometry as _transform_tuple_from_pixel_geometry # noqa: F401
from ._crs import _resolve_crs_to_wkt, _wkt_to_epsg # noqa: F401
from ._errors import (ConflictingCRSError, ConflictingNodataError, GeoTIFFAmbiguousMetadataError,
InconsistentGeoKeysError, InvalidCRSCodeError, MixedBandMetadataError,
InconsistentGeoKeysError, InvalidCRSCodeError, InvalidIntegerNodataError,
MixedBandMetadataError,
NonRepresentableEPSGCRSError, NonUniformCoordsError, RotatedTransformError,
UnknownCRSModelTypeError,
UnparseableCRSError, UnsupportedGeoTIFFFeatureError)
Expand Down Expand Up @@ -111,6 +112,7 @@
'GEOREF_STATUS_VALUES',
'InconsistentGeoKeysError',
'InvalidCRSCodeError',
'InvalidIntegerNodataError',
'MixedBandMetadataError',
'NonRepresentableEPSGCRSError',
'NonUniformCoordsError',
Expand Down Expand Up @@ -169,7 +171,8 @@


def _read_geo_info(source, *, overview_level: int | None = None,
allow_rotated: bool = False):
allow_rotated: bool = False,
allow_invalid_nodata: bool = False):
"""Read only the geographic metadata and image dimensions from a GeoTIFF.

Returns (geo_info, height, width, dtype, n_bands) without reading pixel
Expand All @@ -188,6 +191,10 @@ def _read_geo_info(source, *, overview_level: int | None = None,
``ModelTransformationTag`` reads as an ungeoreferenced pixel
grid instead of raising ``RotatedTransformError`` (issues #2115,
#2267).
allow_invalid_nodata : bool, optional
Forwarded to the geotag parser. When True, restores the legacy
no-op handling of non-finite / fractional ``GDAL_NODATA`` on
integer sources (#1774 follow-up, #2441).
"""
# ``_parse_cog_http_meta`` is imported from ``_cog_http`` directly
# rather than re-routed through ``_reader`` because the
Expand Down Expand Up @@ -230,6 +237,7 @@ def _read_geo_info(source, *, overview_level: int | None = None,
_header, _ifd, geo_info, _ = _parse_cog_http_meta(
_src, overview_level=overview_level,
allow_rotated=allow_rotated,
allow_invalid_nodata=allow_invalid_nodata,
source_path=source)
finally:
_src.close()
Expand Down Expand Up @@ -340,6 +348,7 @@ def _read_geo_info(source, *, overview_level: int | None = None,
geo_info = extract_geo_info_with_overview_inheritance(
ifd, ifds, data, header.byte_order,
allow_rotated=allow_rotated,
allow_invalid_nodata=allow_invalid_nodata,
sidecar_origin=georef_origin)
bps = resolve_bits_per_sample(ifd.bits_per_sample)
file_dtype = tiff_dtype_to_numpy(bps, ifd.sample_format)
Expand Down Expand Up @@ -379,6 +388,7 @@ def open_geotiff(source: str | BinaryIO, *,
allow_rotated: bool = False,
allow_unparseable_crs: bool = False,
allow_inconsistent_geokeys: bool = False,
allow_invalid_nodata: bool = False,
allow_experimental_codecs: bool = False,
allow_internal_only_jpeg: bool = False,
band_nodata: str | None = None,
Expand Down Expand Up @@ -589,6 +599,18 @@ def open_geotiff(source: str | BinaryIO, *,
raises ``InconsistentGeoKeysError``. Set to ``True`` to keep
the legacy permissive behaviour for files known to carry
quirky-but-trusted GeoKey layouts.
allow_invalid_nodata : bool, default False
[advanced] Read-side opt-in for integer-dtype sources whose
``GDAL_NODATA`` tag is non-finite (``"NaN"``, ``"Inf"``,
``"-Inf"``) or fractional (e.g. ``"3.5"`` on a ``uint16``
file). The legacy reader (#1774) parsed the value into
``attrs['nodata']`` and silently skipped the masking step, so
callers had no way to tell a silently-ignored sentinel from a
missing one. When ``False`` (the default), the read raises
``InvalidIntegerNodataError``. Set to ``True`` to keep the
pre-rejection no-op behaviour for files known to carry such
sentinels (e.g. external tooling that writes ``"nan"`` on
integer outputs). See issue #2441 (#1774 follow-up).
allow_experimental_codecs : bool, default False
Read-side opt-in for sources compressed with the Tier 3
experimental codecs (``lerc``, ``jpeg2000`` / ``j2k``, ``lz4``).
Expand Down Expand Up @@ -738,6 +760,7 @@ def open_geotiff(source: str | BinaryIO, *,
allow_unparseable_crs=allow_unparseable_crs,
allow_inconsistent_geokeys=(
allow_inconsistent_geokeys),
allow_invalid_nodata=allow_invalid_nodata,
allow_experimental_codecs=allow_experimental_codecs,
allow_internal_only_jpeg=allow_internal_only_jpeg,
band_nodata=band_nodata,
Expand All @@ -762,6 +785,7 @@ def open_geotiff(source: str | BinaryIO, *,
allow_unparseable_crs=allow_unparseable_crs,
allow_inconsistent_geokeys=(
allow_inconsistent_geokeys),
allow_invalid_nodata=allow_invalid_nodata,
allow_experimental_codecs=(
allow_experimental_codecs),
allow_internal_only_jpeg=(
Expand All @@ -779,6 +803,7 @@ def open_geotiff(source: str | BinaryIO, *,
allow_unparseable_crs=allow_unparseable_crs,
allow_inconsistent_geokeys=(
allow_inconsistent_geokeys),
allow_invalid_nodata=allow_invalid_nodata,
allow_experimental_codecs=(
allow_experimental_codecs),
allow_internal_only_jpeg=(
Expand All @@ -801,6 +826,7 @@ def open_geotiff(source: str | BinaryIO, *,
source, window=window,
overview_level=overview_level, band=band,
allow_rotated=allow_rotated,
allow_invalid_nodata=allow_invalid_nodata,
allow_experimental_codecs=allow_experimental_codecs,
allow_internal_only_jpeg=allow_internal_only_jpeg,
**kwargs,
Expand Down
16 changes: 15 additions & 1 deletion xrspatial/geotiff/_backends/dask.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def read_geotiff_dask(source: str, *,
allow_rotated: bool = False,
allow_unparseable_crs: bool = False,
allow_inconsistent_geokeys: bool = False,
allow_invalid_nodata: bool = False,
allow_experimental_codecs: bool = False,
allow_internal_only_jpeg: bool = False,
band_nodata: str | None = None,
Expand Down Expand Up @@ -135,6 +136,12 @@ def read_geotiff_dask(source: str, *,
to different EPSG codes). The default raises
``InconsistentGeoKeysError``. See ``open_geotiff`` for the
full description (issue #2417).
allow_invalid_nodata : bool, default False
[advanced] Read-side opt-in for integer-dtype sources whose
``GDAL_NODATA`` tag is non-finite or fractional. Default raises
``InvalidIntegerNodataError`` at graph-build time. See
``open_geotiff`` for the full description (#1774 follow-up,
#2441).
allow_experimental_codecs : bool, default False
[advanced] Read-side opt-in for Tier 3 experimental codecs
(``lerc``, ``jpeg2000`` / ``j2k``, ``lz4``). Fires at graph
Expand Down Expand Up @@ -219,6 +226,7 @@ def read_geotiff_dask(source: str, *,
allow_rotated=allow_rotated,
allow_unparseable_crs=allow_unparseable_crs,
allow_inconsistent_geokeys=allow_inconsistent_geokeys,
allow_invalid_nodata=allow_invalid_nodata,
band_nodata=band_nodata,
mask_nodata=mask_nodata,
**vrt_kwargs,
Expand Down Expand Up @@ -284,6 +292,7 @@ def read_geotiff_dask(source: str, *,
) = _parse_cog_http_meta(
_src, overview_level=overview_level,
allow_rotated=allow_rotated,
allow_invalid_nodata=allow_invalid_nodata,
source_path=source,
return_sidecar=True,
)
Expand Down Expand Up @@ -345,7 +354,8 @@ def read_geotiff_dask(source: str, *,
from .. import _read_geo_info
geo_info, full_h, full_w, file_dtype, n_bands = _read_geo_info(
source, overview_level=overview_level,
allow_rotated=allow_rotated)
allow_rotated=allow_rotated,
allow_invalid_nodata=allow_invalid_nodata)

# Reject experimental / internal-only codecs at graph build, before
# any chunk task is scheduled. The compression tag is stashed on
Expand Down Expand Up @@ -600,6 +610,8 @@ def read_geotiff_dask(source: str, *,
http_meta_key=http_meta_key,
max_pixels=max_pixels,
allow_rotated=allow_rotated,
allow_invalid_nodata=(
allow_invalid_nodata),
allow_experimental_codecs=(
allow_experimental_codecs),
allow_internal_only_jpeg=(
Expand All @@ -626,6 +638,7 @@ def read_geotiff_dask(source: str, *,
def _delayed_read_window(source, r0, c0, r1, c1, overview_level, nodata,
band, *, target_dtype=None, http_meta_key=None,
max_pixels=None, allow_rotated=False,
allow_invalid_nodata=False,
allow_experimental_codecs=False,
allow_internal_only_jpeg=False):
"""Dask-delayed function to read a single window.
Expand Down Expand Up @@ -688,6 +701,7 @@ def _read(http_meta):
overview_level=overview_level,
band=band,
allow_rotated=allow_rotated,
allow_invalid_nodata=allow_invalid_nodata,
allow_experimental_codecs=allow_experimental_codecs,
allow_internal_only_jpeg=allow_internal_only_jpeg,
**_r2a_kwargs)
Expand Down
21 changes: 21 additions & 0 deletions xrspatial/geotiff/_backends/gpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def read_geotiff_gpu(source: str, *,
allow_rotated: bool = False,
allow_unparseable_crs: bool = False,
allow_inconsistent_geokeys: bool = False,
allow_invalid_nodata: bool = False,
allow_experimental_codecs: bool = False,
allow_internal_only_jpeg: bool = False,
band_nodata: str | None = None,
Expand Down Expand Up @@ -210,6 +211,12 @@ def read_geotiff_gpu(source: str, *,
raises ``InconsistentGeoKeysError``; ``True`` restores the
legacy silent acceptance. See ``open_geotiff`` for the full
description (issue #2417).
allow_invalid_nodata : bool, default False
[experimental] Read-side opt-in for integer-dtype sources whose
``GDAL_NODATA`` tag is non-finite or fractional. Mirrors the CPU
eager and dask paths; default raises
``InvalidIntegerNodataError``. See ``open_geotiff`` for the full
description (#1774 follow-up, #2441).
allow_experimental_codecs : bool, default False
[experimental] Read-side opt-in for Tier 3 experimental codecs
(``lerc``, ``jpeg2000`` / ``j2k``, ``lz4``). The GPU read path
Expand Down Expand Up @@ -338,6 +345,7 @@ def read_geotiff_gpu(source: str, *,
allow_rotated=allow_rotated,
allow_unparseable_crs=allow_unparseable_crs,
allow_inconsistent_geokeys=allow_inconsistent_geokeys,
allow_invalid_nodata=allow_invalid_nodata,
allow_experimental_codecs=allow_experimental_codecs,
allow_internal_only_jpeg=allow_internal_only_jpeg,
mask_nodata=mask_nodata,
Expand Down Expand Up @@ -390,6 +398,7 @@ def read_geotiff_gpu(source: str, *,
max_pixels=max_pixels, allow_rotated=allow_rotated,
allow_unparseable_crs=allow_unparseable_crs,
allow_inconsistent_geokeys=allow_inconsistent_geokeys,
allow_invalid_nodata=allow_invalid_nodata,
allow_experimental_codecs=allow_experimental_codecs,
allow_internal_only_jpeg=allow_internal_only_jpeg,
mask_nodata=mask_nodata,
Expand Down Expand Up @@ -494,6 +503,7 @@ def read_geotiff_gpu(source: str, *,
geo_info = extract_geo_info_with_overview_inheritance(
ifd, ifds, data, header.byte_order,
allow_rotated=allow_rotated,
allow_invalid_nodata=allow_invalid_nodata,
sidecar_origin=georef_origin)
# Capture the Orientation tag (274) once so the post-decode flip
# below picks it up for both the stripped fallback and the tiled
Expand Down Expand Up @@ -589,6 +599,7 @@ def read_geotiff_gpu(source: str, *,
source, overview_level=overview_level,
window=window, band=band, max_pixels=max_pixels,
allow_rotated=allow_rotated,
allow_invalid_nodata=allow_invalid_nodata,
allow_experimental_codecs=allow_experimental_codecs,
allow_internal_only_jpeg=allow_internal_only_jpeg)
arr_gpu = cupy.asarray(arr_cpu)
Expand Down Expand Up @@ -794,6 +805,7 @@ def _read_once():
source, overview_level=overview_level,
window=window, band=band, max_pixels=max_pixels,
allow_rotated=allow_rotated,
allow_invalid_nodata=allow_invalid_nodata,
allow_experimental_codecs=allow_experimental_codecs,
allow_internal_only_jpeg=allow_internal_only_jpeg)
arr_gpu = cupy.asarray(arr_cpu)
Expand All @@ -812,6 +824,7 @@ def _read_once():
source, overview_level=overview_level,
window=window, band=band, max_pixels=max_pixels,
allow_rotated=allow_rotated,
allow_invalid_nodata=allow_invalid_nodata,
allow_experimental_codecs=allow_experimental_codecs,
allow_internal_only_jpeg=allow_internal_only_jpeg)
arr_gpu = cupy.asarray(arr_cpu)
Expand Down Expand Up @@ -892,6 +905,7 @@ def _read_once():
source, overview_level=overview_level,
window=window, band=band, max_pixels=max_pixels,
allow_rotated=allow_rotated,
allow_invalid_nodata=allow_invalid_nodata,
allow_experimental_codecs=allow_experimental_codecs,
allow_internal_only_jpeg=allow_internal_only_jpeg)
arr_gpu = cupy.asarray(arr_cpu)
Expand Down Expand Up @@ -1056,6 +1070,7 @@ def _read_geotiff_gpu_eager_via_cpu(source, *, dtype, window, overview_level,
allow_rotated: bool = False,
allow_unparseable_crs: bool = False,
allow_inconsistent_geokeys: bool = False,
allow_invalid_nodata: bool = False,
allow_experimental_codecs: bool = False,
allow_internal_only_jpeg: bool = False,
mask_nodata: bool = True):
Expand Down Expand Up @@ -1098,6 +1113,7 @@ def _read_geotiff_gpu_eager_via_cpu(source, *, dtype, window, overview_level,
arr_cpu, geo_info = _read_to_array(
source, window=window, overview_level=overview_level,
band=band, max_pixels=max_pixels, allow_rotated=allow_rotated,
allow_invalid_nodata=allow_invalid_nodata,
allow_experimental_codecs=allow_experimental_codecs,
allow_internal_only_jpeg=allow_internal_only_jpeg,
)
Expand Down Expand Up @@ -1290,6 +1306,7 @@ def _read_geotiff_gpu_chunked(source, *, dtype, chunks, overview_level,
allow_rotated: bool = False,
allow_unparseable_crs: bool = False,
allow_inconsistent_geokeys: bool = False,
allow_invalid_nodata: bool = False,
allow_experimental_codecs: bool = False,
allow_internal_only_jpeg: bool = False,
mask_nodata: bool = True):
Expand Down Expand Up @@ -1390,6 +1407,7 @@ def _read_geotiff_gpu_chunked(source, *, dtype, chunks, overview_level,
geo_info = extract_geo_info_with_overview_inheritance(
ifd, ifds, raw, header.byte_order,
allow_rotated=allow_rotated,
allow_invalid_nodata=allow_invalid_nodata,
sidecar_origin=None,
)
orientation = ifd.orientation
Expand All @@ -1407,6 +1425,7 @@ def _read_geotiff_gpu_chunked(source, *, dtype, chunks, overview_level,
allow_unparseable_crs=allow_unparseable_crs,
allow_inconsistent_geokeys=(
allow_inconsistent_geokeys),
allow_invalid_nodata=allow_invalid_nodata,
mask_nodata=mask_nodata,
)
except Exception:
Expand All @@ -1422,6 +1441,7 @@ def _read_geotiff_gpu_chunked(source, *, dtype, chunks, overview_level,
allow_rotated=allow_rotated,
allow_unparseable_crs=allow_unparseable_crs,
allow_inconsistent_geokeys=allow_inconsistent_geokeys,
allow_invalid_nodata=allow_invalid_nodata,
allow_experimental_codecs=allow_experimental_codecs,
allow_internal_only_jpeg=allow_internal_only_jpeg,
mask_nodata=mask_nodata,
Expand Down Expand Up @@ -1450,6 +1470,7 @@ def _read_geotiff_gpu_chunked_gds(source, ifd, geo_info, header, *,
allow_rotated: bool = False,
allow_unparseable_crs: bool = False,
allow_inconsistent_geokeys: bool = False,
allow_invalid_nodata: bool = False,
mask_nodata: bool = True):
"""Build a Dask+CuPy graph that decodes each chunk disk->GPU.

Expand Down
Loading
Loading