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
2 changes: 2 additions & 0 deletions .claude/sweep-style-state.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module,last_inspected,issue,severity_max,categories_found,notes
geotiff,2026-05-21,2282,HIGH,1;2;3;4,Bundled 309 flake8 + 22 isort fixes; __init__.py F401 re-exports use # noqa pattern; Cat 5 grep clean.
107 changes: 37 additions & 70 deletions xrspatial/geotiff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
"""
from __future__ import annotations

import math
import os
import warnings
from typing import TYPE_CHECKING
Expand All @@ -44,81 +43,53 @@
if TYPE_CHECKING:
from typing import BinaryIO

from ._coords import (
_BAND_DIM_NAMES,
coords_from_pixel_geometry as _coords_from_pixel_geometry,
transform_tuple_from_pixel_geometry as _transform_tuple_from_pixel_geometry,
transform_tuple as _transform_tuple,
transform_from_attr as _transform_from_attr,
coords_to_transform as _coords_to_transform,
require_transform_for_georeferenced as _require_transform_for_georeferenced,
)
from ._errors import (
ConflictingCRSError,
ConflictingNodataError,
GeoTIFFAmbiguousMetadataError,
InvalidCRSCodeError,
MixedBandMetadataError,
NonUniformCoordsError,
RotatedTransformError,
UnknownCRSModelTypeError,
UnparseableCRSError,
)
from ._geotags import GeoTransform, RASTER_PIXEL_IS_AREA, RASTER_PIXEL_IS_POINT
from ._reader import UnsafeURLError
# Re-exports only; consumers import these as ``xrspatial.geotiff._coords_from_pixel_geometry``
# ``read_to_array`` is internal: it is used by ``open_geotiff`` and the
# GPU fallback below but is not in ``__all__`` or the module-level
# Public API docstring. Bind it under a leading-underscore name so it
# does not leak into ``xrspatial.geotiff``'s public namespace. Tests
# and internal callers that genuinely need it can import directly from
# ``xrspatial.geotiff._reader``. See issue #1708.
from ._attrs import (
GEOREF_STATUS_CRS_ONLY,
GEOREF_STATUS_FULL,
GEOREF_STATUS_NONE,
GEOREF_STATUS_ROTATED_DROPPED,
GEOREF_STATUS_TRANSFORM_ONLY,
GEOREF_STATUS_VALUES,
_LEVEL_RANGES,
_VALID_COMPRESSIONS,
_extent_to_window,
_extract_rich_tags,
_finalize_eager_read,
_resolve_nodata_attr,
)
from ._backends._gpu_helpers import _is_gpu_data
from ._attrs import (_LEVEL_RANGES, _VALID_COMPRESSIONS, GEOREF_STATUS_CRS_ONLY, # noqa: F401
GEOREF_STATUS_FULL, GEOREF_STATUS_NONE, GEOREF_STATUS_ROTATED_DROPPED,
GEOREF_STATUS_TRANSFORM_ONLY, GEOREF_STATUS_VALUES, _extent_to_window,
_extract_rich_tags, _finalize_eager_read, _resolve_nodata_attr)
# Re-export only; called by xrspatial/geotiff/tests/test_nodata_*.py.
from ._backends._gpu_helpers import _apply_nodata_mask_gpu # noqa: F401
from ._backends._gpu_helpers import _is_gpu_data # noqa: F401
from ._backends.dask import read_geotiff_dask
from ._backends.gpu import read_geotiff_gpu
from ._backends.vrt import read_vrt
from ._crs import _resolve_crs_to_wkt, _wkt_to_epsg
from ._reader import (
_MAX_CLOUD_BYTES_SENTINEL,
read_to_array as _read_to_array,
)
from ._runtime import (
GeoTIFFFallbackWarning,
_CRS_WKT_DEPRECATED_SENTINEL,
_GPU_DEPRECATED_SENTINEL,
_MISSING_SOURCES_SENTINEL,
_ON_GPU_FAILURE_SENTINEL,
_geotiff_strict_mode,
_gpu_fallback_warning_message,
)
from ._validation import (
_validate_3d_writer_dims,
_validate_chunks_arg,
_validate_tile_size_arg,
)
# etc. See the ``# noqa: F401`` pattern at lines 89 and 119 for the same convention.
from ._coords import _BAND_DIM_NAMES # noqa: F401
from ._coords import coords_from_pixel_geometry as _coords_from_pixel_geometry # noqa: F401
from ._coords import coords_to_transform as _coords_to_transform # noqa: F401
from ._coords import \
require_transform_for_georeferenced as _require_transform_for_georeferenced # noqa: F401
from ._coords import transform_from_attr as _transform_from_attr # noqa: F401
from ._coords import transform_tuple as _transform_tuple # noqa: F401
from ._coords import \
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,
InvalidCRSCodeError, MixedBandMetadataError, NonUniformCoordsError,
RotatedTransformError, UnknownCRSModelTypeError, UnparseableCRSError)
from ._geotags import RASTER_PIXEL_IS_AREA, RASTER_PIXEL_IS_POINT, GeoTransform # noqa: F401
from ._reader import _MAX_CLOUD_BYTES_SENTINEL, UnsafeURLError
from ._reader import read_to_array as _read_to_array
from ._runtime import (_CRS_WKT_DEPRECATED_SENTINEL, _GPU_DEPRECATED_SENTINEL, # noqa: F401
_MISSING_SOURCES_SENTINEL, _ON_GPU_FAILURE_SENTINEL, GeoTIFFFallbackWarning,
_geotiff_strict_mode, _gpu_fallback_warning_message)
from ._validation import (_validate_3d_writer_dims, _validate_chunks_arg, # noqa: F401
_validate_tile_size_arg)
# Re-export only; called by xrspatial/geotiff/tests/test_nodata_no_extra_copy_1553.py.
# ``_writer.write`` (alias for ``_writer._write``) is module-private;
# see ``_writer.py`` docstring and issue #2138. The public eager write
# surface is :func:`to_geotiff`; do not re-export the array-level
# entry point here. The dotted path ``xrspatial.geotiff._writer._write``
# still works for the handful of internal call sites that need it.
from ._writers.eager import to_geotiff
# Re-export only; called by xrspatial/geotiff/tests/test_nodata_no_extra_copy_1553.py.
from ._writers.eager import _write_single_tile # noqa: F401
from ._writers.eager import to_geotiff
from ._writers.gpu import write_geotiff_gpu
from ._writers.vrt import write_vrt

Expand Down Expand Up @@ -213,9 +184,6 @@ def _read_geo_info(source, *, overview_level: int | None = None,
grid instead of raising ``RotatedTransformError`` (issues #2115,
#2267).
"""
from ._dtypes import resolve_bits_per_sample, tiff_dtype_to_numpy
from ._geotags import extract_geo_info_with_overview_inheritance
from ._header import parse_all_ifds, parse_header, select_overview_ifd
# ``_parse_cog_http_meta`` is imported from ``_cog_http`` directly
# rather than re-routed through ``_reader`` because the
# ``open_geotiff(..., chunks=...)`` fsspec metadata path is not part
Expand All @@ -226,9 +194,10 @@ def _read_geo_info(source, *, overview_level: int | None = None,
# ``_HTTPSource`` construction, both of which still go through
# ``_reader`` for the patchable names. See PR-J / #2258.
from ._cog_http import _parse_cog_http_meta
from ._sources import (
_CloudSource, _coerce_path, _is_file_like, _is_fsspec_uri,
)
from ._dtypes import resolve_bits_per_sample, tiff_dtype_to_numpy
from ._geotags import extract_geo_info_with_overview_inheritance
from ._header import parse_all_ifds, parse_header, select_overview_ifd
from ._sources import _CloudSource, _coerce_path, _is_file_like, _is_fsspec_uri
from ._validation import _validate_predictor_sample_format

source = _coerce_path(source)
Expand Down Expand Up @@ -303,9 +272,7 @@ def _read_geo_info(source, *, overview_level: int | None = None,
# Append sibling `.tif.ovr` sidecar IFDs onto the pyramid list
# so ``overview_level`` indexes both internal and external
# overviews (issue #2112). Local file paths only.
from ._sidecar import (
attach_sidecar_origin, find_sidecar, load_sidecar,
)
from ._sidecar import attach_sidecar_origin, find_sidecar, load_sidecar
sidecar_path = find_sidecar(source)
if sidecar_path is not None:
sidecar = load_sidecar(sidecar_path)
Expand Down Expand Up @@ -350,7 +317,8 @@ def open_geotiff(source: str | BinaryIO, *,
chunks: int | tuple | None = None,
gpu: bool = False,
max_pixels: int | None = None,
max_cloud_bytes: int | None = _MAX_CLOUD_BYTES_SENTINEL, # type: ignore[assignment]
max_cloud_bytes: int | None = (
_MAX_CLOUD_BYTES_SENTINEL), # type: ignore[assignment]
on_gpu_failure: str = _ON_GPU_FAILURE_SENTINEL,
missing_sources: str = _MISSING_SOURCES_SENTINEL,
allow_rotated: bool = False,
Expand Down Expand Up @@ -665,7 +633,6 @@ def open_geotiff(source: str | BinaryIO, *,
# Derive from source path. File-like buffers don't have a path,
# so leave name unset rather than fabricating one.
if isinstance(source, str):
import os
name = os.path.splitext(os.path.basename(source))[0]

# Hand the post-decode buffer to the shared eager finalizer
Expand Down
16 changes: 4 additions & 12 deletions xrspatial/geotiff/_attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,20 +159,12 @@
from typing import Any

import numpy as np

import xarray as xr

from ._coords import (
coords_from_geo_info as _coords_from_geo_info,
resolve_georef as _resolve_georef,
transform_tuple_from_pixel_geometry as _transform_tuple_from_pixel_geometry,
)
from ._geotags import (
RASTER_PIXEL_IS_AREA,
RASTER_PIXEL_IS_POINT,
_NO_GEOREF_KEY,
)

from ._coords import coords_from_geo_info as _coords_from_geo_info
from ._coords import resolve_georef as _resolve_georef
from ._coords import transform_tuple_from_pixel_geometry as _transform_tuple_from_pixel_geometry
from ._geotags import _NO_GEOREF_KEY, RASTER_PIXEL_IS_AREA, RASTER_PIXEL_IS_POINT

# Per-codec valid compression-level ranges, used by ``to_geotiff`` for
# friendly up-front validation. Codecs not listed here either reject any
Expand Down
8 changes: 3 additions & 5 deletions xrspatial/geotiff/_backends/_gpu_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@
import numpy as np
import xarray as xr

from .._coords import (
coords_from_geo_info as _coords_from_geo_info,
geo_to_coords as _geo_to_coords,
)
from .._geotags import GeoTransform, RASTER_PIXEL_IS_POINT
from .._coords import coords_from_geo_info as _coords_from_geo_info
from .._coords import geo_to_coords as _geo_to_coords
from .._geotags import RASTER_PIXEL_IS_POINT, GeoTransform
from .._nodata import _sentinel_fits_dtype
from .._runtime import _geotiff_strict_mode

Expand Down
19 changes: 8 additions & 11 deletions xrspatial/geotiff/_backends/dask.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,13 @@
import xarray as xr

from .._attrs import _finalize_lazy_read_attrs
from .._coords import (
coords_from_geo_info as _coords_from_geo_info,
geo_to_coords as _geo_to_coords,
)
from .._coords import coords_from_geo_info as _coords_from_geo_info
from .._coords import geo_to_coords as _geo_to_coords
from .._nodata import NodataLifecycle
from .._reader import _MAX_CLOUD_BYTES_SENTINEL, read_to_array as _read_to_array
from .._reader import _MAX_CLOUD_BYTES_SENTINEL
from .._reader import read_to_array as _read_to_array
from .._runtime import _MISSING_SOURCES_SENTINEL, _ON_GPU_FAILURE_SENTINEL
from .._validation import (
_validate_chunks_arg,
_validate_dispatch_kwargs,
_validate_dtype_cast,
)
from .._validation import _validate_chunks_arg, _validate_dispatch_kwargs, _validate_dtype_cast
from .vrt import read_vrt


Expand All @@ -41,7 +36,8 @@ def read_geotiff_dask(source: str, *,
name: str | None = None,
chunks: int | tuple = 512,
max_pixels: int | None = None,
max_cloud_bytes: int | None = _MAX_CLOUD_BYTES_SENTINEL, # type: ignore[assignment]
max_cloud_bytes: int | None = (
_MAX_CLOUD_BYTES_SENTINEL), # type: ignore[assignment]
on_gpu_failure: str = _ON_GPU_FAILURE_SENTINEL,
missing_sources: str = _MISSING_SOURCES_SENTINEL,
allow_rotated: bool = False,
Expand Down Expand Up @@ -215,6 +211,7 @@ def read_geotiff_dask(source: str, *,
effective_source = source
if is_http or is_fsspec:
import dask

from .._cog_http import _parse_cog_http_meta
if is_http:
# ``_HTTPSource`` is resolved through ``_reader`` so existing
Expand Down
75 changes: 23 additions & 52 deletions xrspatial/geotiff/_backends/gpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,18 @@
import numpy as np
import xarray as xr

from .._attrs import (
_finalize_eager_read,
_finalize_lazy_read_attrs,
)
from .._coords import (
coords_from_geo_info as _coords_from_geo_info,
)
from .._attrs import _finalize_eager_read, _finalize_lazy_read_attrs
from .._coords import coords_from_geo_info as _coords_from_geo_info
from .._nodata import NodataLifecycle as _NL
from .._reader import (
_MAX_CLOUD_BYTES_SENTINEL,
_coerce_path,
read_to_array as _read_to_array,
)
from .._runtime import (
_GPU_DEPRECATED_SENTINEL,
_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,
_apply_orientation_gpu,
_gpu_apply_window_band,
_gpu_decode_single_band_tiles,
)
from .._reader import _MAX_CLOUD_BYTES_SENTINEL, _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 .._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,
_apply_orientation_gpu, _gpu_apply_window_band,
_gpu_decode_single_band_tiles)
from .dask import read_geotiff_dask


Expand Down Expand Up @@ -89,7 +69,8 @@ def read_geotiff_gpu(source: str, *,
name: str | None = None,
chunks: int | tuple | None = None,
max_pixels: int | None = None,
max_cloud_bytes: int | None = _MAX_CLOUD_BYTES_SENTINEL, # type: ignore[assignment]
max_cloud_bytes: int | None = (
_MAX_CLOUD_BYTES_SENTINEL), # type: ignore[assignment]
on_gpu_failure: str = _ON_GPU_FAILURE_SENTINEL,
missing_sources: str = _MISSING_SOURCES_SENTINEL,
allow_rotated: bool = False,
Expand Down Expand Up @@ -328,17 +309,13 @@ def read_geotiff_gpu(source: str, *,
mask_nodata=mask_nodata,
)

from .._reader import (
_FileSource, _check_dimensions, MAX_PIXELS_DEFAULT,
_is_fsspec_uri, _max_tile_bytes_from_env, _resolve_masked_fill,
)
from .._compression import COMPRESSION_LERC
from .._header import (
parse_header, parse_all_ifds, select_overview_ifd, validate_tile_layout,
)
from .._dtypes import resolve_bits_per_sample, tiff_dtype_to_numpy
from .._geotags import extract_geo_info_with_overview_inheritance
from .._gpu_decode import gpu_decode_tiles
from .._header import parse_all_ifds, parse_header, select_overview_ifd, validate_tile_layout
from .._reader import (MAX_PIXELS_DEFAULT, _check_dimensions, _FileSource, _is_fsspec_uri,
_max_tile_bytes_from_env, _resolve_masked_fill)

# ``source`` is already coerced above (before the dispatch
# validator); no need to re-coerce here.
Expand Down Expand Up @@ -399,10 +376,7 @@ def read_geotiff_gpu(source: str, *,
# we swap ``data`` / ``header`` to the sidecar's buffers below
# and skip the GDS fast path -- GDS reads the source file path,
# which would point at the base file rather than the sidecar.
from .._sidecar import (
attach_sidecar_origin, close_sidecar, find_sidecar,
load_sidecar,
)
from .._sidecar import attach_sidecar_origin, close_sidecar, find_sidecar, load_sidecar
sidecar_origin: dict[int, tuple] = {}
sidecar_path = find_sidecar(source)
if sidecar_path is not None:
Expand Down Expand Up @@ -1023,6 +997,7 @@ def _read_geotiff_gpu_eager_via_cpu(source, *, dtype, window, overview_level,

if name is None:
import os

# ``os.path.basename`` strips the scheme and host from a URL
# (``basename('https://host/path/foo.tif') == 'foo.tif'``); for
# ``s3://bucket/foo.tif`` it returns ``'foo.tif'``. Match the
Expand Down Expand Up @@ -1222,11 +1197,9 @@ def _read_geotiff_gpu_chunked(source, *, dtype, chunks, overview_level,
"""
import cupy

from .._reader import (
_FileSource, _coerce_path, _max_tile_bytes_from_env,
)
from .._header import parse_header, parse_all_ifds, select_overview_ifd
from .._geotags import extract_geo_info_with_overview_inheritance
from .._header import parse_all_ifds, parse_header, select_overview_ifd
from .._reader import _coerce_path, _FileSource, _max_tile_bytes_from_env

src_path = _coerce_path(source)

Expand Down Expand Up @@ -1357,13 +1330,11 @@ def _read_geotiff_gpu_chunked_gds(source, ifd, geo_info, header, *,
import dask
import dask.array as da_mod

from .._reader import (
_check_dimensions, MAX_PIXELS_DEFAULT,
_max_tile_bytes_from_env, _resolve_masked_fill,
)
from .._compression import COMPRESSION_LERC
from .._header import validate_tile_layout
from .._dtypes import resolve_bits_per_sample, tiff_dtype_to_numpy
from .._header import validate_tile_layout
from .._reader import (MAX_PIXELS_DEFAULT, _check_dimensions, _max_tile_bytes_from_env,
_resolve_masked_fill)

if max_pixels is None:
max_pixels = MAX_PIXELS_DEFAULT
Expand Down
Loading
Loading