Skip to content
Open
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
1 change: 0 additions & 1 deletion docs/source/reference/geotiff.rst
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,6 @@ this section is the brief.
sentinel passes through and downstream callers can branch on the exact
value; pass ``masked=True`` to get NaN-masked output.
``xrspatial/geotiff/tests/write/test_nodata.py`` pins this split.
(``mask_nodata`` is a deprecated alias of ``masked``.)
* NaN nodata. A file that declares ``nodata=NaN`` is read with NaN in
both ``attrs['nodata']`` and pixel data (NaN propagates either way).
* ``attrs['masked_nodata']``. Every read sets a boolean lifecycle
Expand Down
103 changes: 7 additions & 96 deletions xrspatial/geotiff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,8 @@
from ._reader import (_MAX_CLOUD_BYTES_SENTINEL, CloudSizeLimitError, PixelSafetyLimitError,
UnsafeURLError)
from ._reader import read_to_array as _read_to_array
from ._runtime import (_CRS_WKT_DEPRECATED_SENTINEL, _GPU_DEPRECATED_SENTINEL, # noqa: F401
_MASK_AND_SCALE_DEPRECATED_SENTINEL, _MASK_NODATA_DEPRECATED_SENTINEL,
_MISSING_SOURCES_SENTINEL, _NAME_DEPRECATED_SENTINEL,
_ON_GPU_FAILURE_SENTINEL, GeoTIFFFallbackWarning, _geotiff_strict_mode,
from ._runtime import (_MISSING_SOURCES_SENTINEL, _ON_GPU_FAILURE_SENTINEL, # noqa: F401
GeoTIFFFallbackWarning, _geotiff_strict_mode,
_gpu_fallback_warning_message)
from ._validation import (_validate_3d_writer_dims, _validate_chunks_arg, # noqa: F401
_validate_tile_size_arg)
Expand All @@ -103,9 +101,7 @@
# callers can import ``xrspatial.geotiff._build_vrt``. Not public API.
from ._writers.vrt import _build_vrt # noqa: F401

# All names below are part of the supported public API. ``plot_geotiff``
# is intentionally omitted: it is deprecated in favour of ``da.xrs.plot()``
# and emits a ``DeprecationWarning`` when called.
# All names below are part of the supported public API.
__all__ = [
'CloudSizeLimitError',
'ConflictingCRSError',
Expand Down Expand Up @@ -499,7 +495,6 @@ def open_geotiff(source: str | BinaryIO, *,
overview_level: int | None = None,
band: int | None = None,
default_name: str | None = None,
name: str | None = _NAME_DEPRECATED_SENTINEL, # type: ignore[assignment]
chunks: int | tuple | None = None,
gpu: bool = False,
max_pixels: int | None = None,
Expand All @@ -515,10 +510,7 @@ def open_geotiff(source: str | BinaryIO, *,
allow_internal_only_jpeg: bool = False,
band_nodata: str | None = None,
masked: bool = False,
mask_nodata: bool = _MASK_NODATA_DEPRECATED_SENTINEL, # type: ignore[assignment]
unpack: bool = False,
mask_and_scale: bool = (
_MASK_AND_SCALE_DEPRECATED_SENTINEL), # type: ignore[assignment]
parse_coordinates: bool = True,
lock: object | None = None,
cache: bool = True,
Expand Down Expand Up @@ -560,7 +552,7 @@ def open_geotiff(source: str | BinaryIO, *,
``lock``, ``cache``) so callers can move between the two with minimal
edits. ``masked`` defaults to ``False`` (no sentinel-to-NaN promotion),
matching ``open_rasterio``. The scale/offset option is named ``unpack``
here; rioxarray's ``mask_and_scale`` is kept as a deprecated alias.
here (rioxarray calls it ``mask_and_scale``).

Automatically dispatches to the best backend:
- ``gpu=True``: GPU-accelerated read via nvCOMP (returns CuPy)
Expand Down Expand Up @@ -621,10 +613,6 @@ def open_geotiff(source: str | BinaryIO, *,
default_name : str or None
[stable] Name for the DataArray. None derives it from the source
file name. Matches rioxarray's ``open_rasterio`` parameter.
name : str or None
[deprecated] Deprecated alias of ``default_name``; emits a
``DeprecationWarning``. Passing both ``default_name`` and ``name``
raises ``TypeError``.
chunks : int, tuple, or None
[stable] Chunk size for Dask lazy reading. Dask reads are
gated against the eager reader by the cross-backend parity
Expand Down Expand Up @@ -702,18 +690,13 @@ def open_geotiff(source: str | BinaryIO, *,
step and keep the source dtype. ``attrs['nodata']`` still
carries the raw sentinel either way, so downstream code can mask
explicitly. The default matches rioxarray's ``open_rasterio``
(``masked=False``); note that earlier xrspatial releases masked
by default (``mask_nodata=True``), so a bare ``open_geotiff(path)``
no longer promotes the sentinel to NaN. Pass ``masked=True`` and
(``masked=False``); note that 0.x releases masked by default, so
a bare ``open_geotiff(path)`` no longer promotes the sentinel to
NaN. Pass ``masked=True`` and
``dtype=<integer>`` together on a source with a maskable sentinel
and the read raises ``ValueError``, because the unconditional
float64 promotion (issue #2990) makes the integer cast lossy
whether or not a sentinel pixel is present.
mask_nodata : bool
[deprecated] Deprecated alias of ``masked``; emits a
``DeprecationWarning``. Passing both ``masked`` and ``mask_nodata``
raises ``TypeError``. Note the default also changed from
``mask_nodata=True`` to ``masked=False``.
unpack : bool, default False
[experimental] If True, read the source's GDAL ``SCALE`` / ``OFFSET``
metadata and return ``data * scale + offset``, masking the nodata
Expand Down Expand Up @@ -746,10 +729,6 @@ def open_geotiff(source: str | BinaryIO, *,
``unpack=True`` applies the scale a second time. Drop those
tags (and ``attrs['scale_factor']`` / ``attrs['add_offset']``) before
writing if you need a clean round-trip.
mask_and_scale : bool
[deprecated] Deprecated alias of ``unpack``; emits a
``DeprecationWarning``. Named after rioxarray's ``open_rasterio``.
Passing both ``unpack`` and ``mask_and_scale`` raises ``TypeError``.
parse_coordinates : bool, default True
[stable] If True (the default), build ``x`` / ``y`` coordinate
arrays from the transform. If False, skip them and return a
Expand Down Expand Up @@ -934,57 +913,6 @@ def _attach_category_attrs(da):
da.attrs.update(read_pam_sidecar(source))
return da

# Resolve the rioxarray-compatible renames. ``masked`` / ``default_name``
# are the canonical names; ``mask_nodata`` / ``name`` are deprecated
# aliases kept for back-compat. Mirrors the sentinel-based deprecation in
# ``read_geotiff_gpu`` (gpu -> on_gpu_failure): passing both the old and
# new name is ambiguous and raises, passing the old name alone warns.
if mask_nodata is not _MASK_NODATA_DEPRECATED_SENTINEL:
# ``masked`` carries a real default of False, so an explicit
# ``masked=False`` cannot be told apart from the default here; that
# one combination (``masked=False`` + ``mask_nodata=True``) does not
# raise and resolves to the ``mask_nodata`` value. This matches the
# documented stance on ``read_geotiff_gpu``'s gpu/on_gpu_failure pair.
if masked is not False:
raise TypeError(
"open_geotiff: pass either 'masked' or the deprecated "
"'mask_nodata' alias, not both.")
warnings.warn(
"open_geotiff(..., mask_nodata=...) is deprecated; use "
"masked=... instead. Note the default also changed from "
"mask_nodata=True to masked=False to match rioxarray's "
"open_rasterio: a bare open_geotiff(path) no longer promotes "
"the nodata sentinel to NaN.",
DeprecationWarning, stacklevel=2)
masked = mask_nodata
if name is not _NAME_DEPRECATED_SENTINEL:
if default_name is not None:
raise TypeError(
"open_geotiff: pass either 'default_name' or the deprecated "
"'name' alias, not both.")
warnings.warn(
"open_geotiff(..., name=...) is deprecated; use default_name=... "
"instead to match rioxarray's open_rasterio.",
DeprecationWarning, stacklevel=2)
default_name = name
if mask_and_scale is not _MASK_AND_SCALE_DEPRECATED_SENTINEL:
# ``unpack`` is the canonical name; ``mask_and_scale`` is the
# deprecated rioxarray-compatible alias. ``unpack`` carries a real
# default of False, so an explicit ``unpack=False`` cannot be told
# apart from the default; that one combination resolves to the
# ``mask_and_scale`` value, matching the ``masked`` / ``mask_nodata``
# stance above.
if unpack is not False:
raise TypeError(
"open_geotiff: pass either 'unpack' or the deprecated "
"'mask_and_scale' alias, not both.")
warnings.warn(
"open_geotiff(..., mask_and_scale=...) is deprecated; use "
"unpack=... instead. The behaviour is unchanged: it applies the "
"source's GDAL SCALE / OFFSET and masks the nodata sentinel.",
DeprecationWarning, stacklevel=2)
unpack = mask_and_scale

# ``lock`` / ``cache`` are accepted for open_rasterio signature
# compatibility. xrspatial's dask reader re-opens the source per window,
# so there is no shared GDAL handle to lock and no caching backend to
Expand Down Expand Up @@ -1254,20 +1182,3 @@ def _attach_category_attrs(da):
parse_coordinates=parse_coordinates,
band=band,
))


def plot_geotiff(da: xr.DataArray, **kwargs):
"""Plot a DataArray using its embedded colormap if present.

.. deprecated:: 0.10.0
Use ``da.xrs.plot()`` instead. ``plot_geotiff`` is a thin wrapper
kept for backward compatibility and will be removed in a future
release.
"""
warnings.warn(
"plot_geotiff is deprecated and will be removed in a future "
"release. Use ``da.xrs.plot()`` instead.",
DeprecationWarning,
stacklevel=2,
)
return da.xrs.plot(**kwargs)
48 changes: 10 additions & 38 deletions xrspatial/geotiff/_backends/gpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
from .._nodata import NodataLifecycle as _NL
from .._reader import _MAX_CLOUD_BYTES_SENTINEL, CloudSizeLimitError, _coerce_path
from .._reader import read_to_array as _read_to_array
from .._runtime import (_GPU_DEPRECATED_SENTINEL, _MISSING_SOURCES_SENTINEL,
_ON_GPU_FAILURE_SENTINEL, _geotiff_strict_mode)
from .._runtime import (_MISSING_SOURCES_SENTINEL, _ON_GPU_FAILURE_SENTINEL,
_geotiff_strict_mode)
from .._validation import (_validate_chunks_arg, _validate_dispatch_kwargs, _validate_dtype_cast,
_validate_predictor_sample_format)
from ._gpu_helpers import (_apply_nodata_mask_gpu, _apply_orientation_geo_info,
Expand Down Expand Up @@ -74,7 +74,6 @@ def _read_geotiff_gpu(source: str, *,
band_nodata: str | None = None,
mask_nodata: bool = False,
mask_and_scale: bool = False,
gpu: str = _GPU_DEPRECATED_SENTINEL,
) -> xr.DataArray:
"""Read a GeoTIFF with GPU-accelerated decompression via Numba CUDA.

Expand Down Expand Up @@ -174,13 +173,6 @@ def _read_geotiff_gpu(source: str, *,
is unusable; transient errors inside a later
``cupy.asarray(...)`` upload (e.g. device OOM) still propagate
unchanged in both modes.
gpu : str, optional
[experimental] Deprecated alias for ``on_gpu_failure``. Emits
``DeprecationWarning`` when used. Passing both ``gpu`` and
``on_gpu_failure`` raises ``TypeError``. The old name shipped
with values ``'auto'`` / ``'strict'`` and was easy to confuse
with the boolean ``gpu=`` kwarg on ``open_geotiff`` /
``to_geotiff`` / ``_read_vrt``.
mask_nodata : bool, default False
[experimental] If True, replace the nodata sentinel with NaN
(integer rasters get promoted to ``float64`` first). If False,
Expand Down Expand Up @@ -303,32 +295,12 @@ def _read_geotiff_gpu(source: str, *,
allow_experimental_codecs=allow_experimental_codecs,
)

new_passed = on_gpu_failure is not _ON_GPU_FAILURE_SENTINEL
old_passed = gpu is not _GPU_DEPRECATED_SENTINEL
if new_passed and old_passed:
# Both supplied is ambiguous regardless of which values were
# chosen (including the matching ``on_gpu_failure='auto',
# gpu='auto'`` pair). Refuse rather than silently picking one.
raise TypeError(
"_read_geotiff_gpu: pass either 'on_gpu_failure' or the "
"deprecated 'gpu' alias, not both.")
if old_passed:
warnings.warn(
"_read_geotiff_gpu(..., gpu=...) is deprecated; use "
"on_gpu_failure=... instead. The kwarg was renamed because "
"'gpu' on open_geotiff/to_geotiff/_read_vrt is a bool that "
"selects the GPU backend, while here it selects the failure "
"policy when the GPU path raises.",
DeprecationWarning,
stacklevel=2,
)
on_gpu_failure = gpu
elif not new_passed:
if on_gpu_failure is _ON_GPU_FAILURE_SENTINEL:
on_gpu_failure = 'auto'
gpu = on_gpu_failure
if gpu not in ('auto', 'strict'):
if on_gpu_failure not in ('auto', 'strict'):
raise ValueError(
f"on_gpu_failure must be 'auto' or 'strict', got {gpu!r}")
"on_gpu_failure must be 'auto' or 'strict', "
f"got {on_gpu_failure!r}")
# Reject non-positive chunk sizes up front so the GPU dask+cupy path
# surfaces the same error as ``_read_geotiff_dask``. Previously
# ``chunks=0`` raised ``ZeroDivisionError`` deep in cupy/dask, and
Expand Down Expand Up @@ -797,7 +769,7 @@ def _read_once():
tw, th, width, height,
compression, predictor, file_dtype,
byte_order=ifd_header.byte_order,
gpu=gpu,
gpu=on_gpu_failure,
)
if band_arr is None:
# Auto-mode signal: stage-2 GPU decode failed for this
Expand Down Expand Up @@ -870,7 +842,7 @@ def _read_once():
masked_fill=masked_fill,
)
except Exception as e:
if gpu == 'strict' or _geotiff_strict_mode():
if on_gpu_failure == 'strict' or _geotiff_strict_mode():
raise
warnings.warn(
f"_read_geotiff_gpu: GPU decode failed "
Expand Down Expand Up @@ -909,7 +881,7 @@ def _read_once():
masked_fill=masked_fill,
)
except Exception as e:
if gpu == 'strict' or _geotiff_strict_mode():
if on_gpu_failure == 'strict' or _geotiff_strict_mode():
raise
warnings.warn(
f"_read_geotiff_gpu: GPU decode failed "
Expand Down Expand Up @@ -1111,7 +1083,7 @@ def _read_geotiff_gpu_eager_via_cpu(source, *, dtype, window, overview_level,
return. This helper is the eager analogue: read the
whole array on CPU, push it to the device in one shot.

The user-facing ``on_gpu_failure`` / ``gpu='strict'`` kwarg is a
The user-facing ``on_gpu_failure='strict'`` kwarg is a
no-op on this path. There is no GPU decode stage to be strict
about: the only GPU operation is the ``cupy.asarray`` upload, and
upload failures (e.g. device OOM) already propagate unconditionally.
Expand Down
2 changes: 1 addition & 1 deletion xrspatial/geotiff/_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ class InvalidIntegerNodataError(GeoTIFFAmbiguousMetadataError):
class MalformedScaleOffsetError(GeoTIFFAmbiguousMetadataError):
"""A present SCALE / OFFSET item cannot be parsed as a float.

Raised under ``mask_and_scale=True`` when the source's GDAL_METADATA
Raised under ``unpack=True`` when the source's GDAL_METADATA
carries a ``SCALE`` or ``OFFSET`` item whose value does not parse as
a float (for example ``SCALE="abc"``). ``mask_and_scale`` asks the
reader to honour the scale / offset metadata, so a malformed value
Expand Down
44 changes: 4 additions & 40 deletions xrspatial/geotiff/_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
a single canonical binding for each sentinel and helper without
threading them through ``__init__.py``. Sentinel object identity is
preserved by Python's module cache: every import of this module
returns the same module instance, so ``_GPU_DEPRECATED_SENTINEL is
other._GPU_DEPRECATED_SENTINEL`` resolves correctly regardless of
returns the same module instance, so ``_ON_GPU_FAILURE_SENTINEL is
other._ON_GPU_FAILURE_SENTINEL`` resolves correctly regardless of
which caller imported it.
"""
from __future__ import annotations
Expand All @@ -14,53 +14,17 @@

# Sentinels distinguishing "user passed this kwarg explicitly" from "user
# passed nothing". A plain default of None does not work because None is
# itself a value a caller could supply. ``_read_geotiff_gpu`` needs both
# sentinels so it can tell whether the deprecated ``gpu=`` and the new
# ``on_gpu_failure=`` were *each* supplied, and refuse the ambiguous
# both-supplied case regardless of which values were chosen.
# ``open_geotiff`` also uses ``_ON_GPU_FAILURE_SENTINEL`` to distinguish
# itself a value a caller could supply.
# ``open_geotiff`` uses ``_ON_GPU_FAILURE_SENTINEL`` to distinguish
# "caller never set on_gpu_failure" (default sentinel: skip forwarding so
# the _read_geotiff_gpu signature default applies) from "caller set
# on_gpu_failure=<value>" (forward verbatim).
_GPU_DEPRECATED_SENTINEL = object()
_ON_GPU_FAILURE_SENTINEL = object()
# ``_build_vrt`` needs to distinguish "user passed crs_wkt= explicitly"
# (deprecation path) from "user passed nothing" (no warning, pick CRS
# from the first source). A plain default of None does not work because
# None is itself a value a caller could supply alongside crs=.
_CRS_WKT_DEPRECATED_SENTINEL = object()
# ``open_geotiff`` needs to tell "caller never set missing_sources" (default
# sentinel: skip forwarding so the _read_vrt default applies, and reject the
# kwarg up front for non-VRT sources) from "caller set missing_sources=<value>"
# (forward verbatim to _read_vrt). Mirrors the on_gpu_failure pattern.
_MISSING_SOURCES_SENTINEL = object()
# ``_build_vrt`` historically named its first positional kwarg ``vrt_path``
# while ``to_geotiff`` / ``_write_geotiff_gpu`` use ``path``. The deprecation
# shim adds ``path`` as the new name and accepts ``vrt_path`` with a
# DeprecationWarning. The sentinel pattern distinguishes "user passed
# vrt_path= explicitly" from "user passed nothing", which is the same
# rationale ``_CRS_WKT_DEPRECATED_SENTINEL`` documents above.
_VRT_PATH_DEPRECATED_SENTINEL = object()
# ``_build_vrt`` also needs to distinguish "user passed path= explicitly"
# (including an explicit ``path=None``, which is an error) from "user
# passed nothing" (fall through to the ``vrt_path`` shim). Without this
# sentinel, ``_build_vrt(None, sources)`` silently fell through to the
# ``path is None`` branch and raised a "missing required argument"
# TypeError for the wrong reason.
_VRT_PATH_MISSING_SENTINEL = object()
# ``open_geotiff`` renamed ``mask_nodata`` -> ``masked`` (and flipped the
# default from True to False) and ``name`` -> ``default_name`` to match
# rioxarray's ``open_rasterio``. Each sentinel distinguishes "caller passed
# the deprecated alias" from "caller passed nothing", so passing both the old
# and new name raises TypeError and the old name alone warns. Same rationale
# as ``_GPU_DEPRECATED_SENTINEL`` above.
_MASK_NODATA_DEPRECATED_SENTINEL = object()
_NAME_DEPRECATED_SENTINEL = object()
# ``mask_and_scale`` was renamed to ``unpack`` (the operation unpacks
# CF-packed integers via SCALE/OFFSET). Same sentinel deprecation as the
# pair above: the old name still works but warns, and passing both raises.
_MASK_AND_SCALE_DEPRECATED_SENTINEL = object()


# Spatial dim names recognised on 3D writer inputs. ``y``/``x`` are the
# canonical TIFF axes; aliases are accepted so a user who happens to use
Expand Down
Loading
Loading