diff --git a/.claude/sweep-style-state.csv b/.claude/sweep-style-state.csv new file mode 100644 index 000000000..143bd53a2 --- /dev/null +++ b/.claude/sweep-style-state.csv @@ -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. diff --git a/xrspatial/geotiff/__init__.py b/xrspatial/geotiff/__init__.py index 8f045dcf8..2f692500d 100644 --- a/xrspatial/geotiff/__init__.py +++ b/xrspatial/geotiff/__init__.py @@ -33,7 +33,6 @@ """ from __future__ import annotations -import math import os import warnings from typing import TYPE_CHECKING @@ -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 @@ -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 @@ -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) @@ -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) @@ -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, @@ -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 diff --git a/xrspatial/geotiff/_attrs.py b/xrspatial/geotiff/_attrs.py index 1a2d4f4af..8c5866ae0 100644 --- a/xrspatial/geotiff/_attrs.py +++ b/xrspatial/geotiff/_attrs.py @@ -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 diff --git a/xrspatial/geotiff/_backends/_gpu_helpers.py b/xrspatial/geotiff/_backends/_gpu_helpers.py index 34602d11d..3d7861072 100644 --- a/xrspatial/geotiff/_backends/_gpu_helpers.py +++ b/xrspatial/geotiff/_backends/_gpu_helpers.py @@ -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 diff --git a/xrspatial/geotiff/_backends/dask.py b/xrspatial/geotiff/_backends/dask.py index 5d82d3cf6..528b3e3d3 100644 --- a/xrspatial/geotiff/_backends/dask.py +++ b/xrspatial/geotiff/_backends/dask.py @@ -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 @@ -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, @@ -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 diff --git a/xrspatial/geotiff/_backends/gpu.py b/xrspatial/geotiff/_backends/gpu.py index e9fc0b60b..92ba91f01 100644 --- a/xrspatial/geotiff/_backends/gpu.py +++ b/xrspatial/geotiff/_backends/gpu.py @@ -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 @@ -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, @@ -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. @@ -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: @@ -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 @@ -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) @@ -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 diff --git a/xrspatial/geotiff/_backends/vrt.py b/xrspatial/geotiff/_backends/vrt.py index d69f56949..087c59b92 100644 --- a/xrspatial/geotiff/_backends/vrt.py +++ b/xrspatial/geotiff/_backends/vrt.py @@ -13,28 +13,14 @@ import numpy as np import xarray as xr -from .._attrs import ( - _finalize_lazy_read_attrs, -) -from .._coords import ( - coords_from_pixel_geometry as _coords_from_pixel_geometry, -) +from .._attrs import _finalize_lazy_read_attrs +from .._coords import coords_from_pixel_geometry as _coords_from_pixel_geometry from .._crs import _wkt_to_epsg -from .._geotags import ( - RASTER_PIXEL_IS_AREA, - RASTER_PIXEL_IS_POINT, - GeoInfo, - GeoTransform, -) +from .._geotags import RASTER_PIXEL_IS_AREA, RASTER_PIXEL_IS_POINT, GeoInfo, GeoTransform from .._reader import _MAX_CLOUD_BYTES_SENTINEL from .._runtime import _ON_GPU_FAILURE_SENTINEL -from .._validation import ( - _gdal_geotransform_to_affine_tuple, - _validate_chunks_arg, - _validate_dispatch_kwargs, - _validate_dtype_cast, -) - +from .._validation import (_gdal_geotransform_to_affine_tuple, _validate_chunks_arg, + _validate_dispatch_kwargs, _validate_dtype_cast) # Hard cap on the per-VRT chunk task count. Matches the # ``_MAX_DASK_CHUNKS`` value used by ``read_geotiff_dask`` so the two @@ -286,11 +272,9 @@ def read_vrt(source: str, *, missing sources at execution time as well. """ from .._reader import _coerce_path - from .._vrt import ( - read_vrt as _read_vrt_internal, - _apply_integer_sentinel_mask_with_presence as _vrt_mask_with_presence, - _scan_for_sentinel as _vrt_scan_for_sentinel, - ) + from .._vrt import _apply_integer_sentinel_mask_with_presence as _vrt_mask_with_presence + from .._vrt import _scan_for_sentinel as _vrt_scan_for_sentinel + from .._vrt import read_vrt as _read_vrt_internal source = _coerce_path(source) @@ -396,8 +380,10 @@ def read_vrt(source: str, *, # helper-routed post-read call acts as a defensive consistency check # rather than the no-op it was before #2210. import os as _os + from .._validation import validate_read_metadata - from .._vrt import parse_vrt as _parse_vrt, _read_vrt_xml + from .._vrt import _read_vrt_xml + from .._vrt import parse_vrt as _parse_vrt _xml_str = _read_vrt_xml(source) _vrt_dir = _os.path.dirname(_os.path.abspath(source)) _parsed_vrt = _parse_vrt(_xml_str, _vrt_dir) @@ -659,10 +645,8 @@ def _vrt_chunk_read(source, r0, c0, r1, c1, *, of the surrounding ``dask.array.from_delayed`` is the contract; a mismatch would silently produce a wrong-shape dask array. """ - from .._vrt import ( - read_vrt as _read_vrt_internal, - _apply_integer_sentinel_mask, - ) + from .._vrt import _apply_integer_sentinel_mask + from .._vrt import read_vrt as _read_vrt_internal # Forward ``mask_nodata`` to the internal reader so the float # source NaN masking inside ``_read_data`` honors the opt-out too, @@ -723,17 +707,13 @@ def _read_vrt_chunked(source, *, window, band, name, chunks, gpu, dtype, ``GeoTIFFFallbackWarning`` from each worker. """ import os as _os + import dask import dask.array as da from .._reader import MAX_PIXELS_DEFAULT from .._runtime import _geotiff_strict_mode - from .._vrt import ( - parse_vrt, - _read_vrt_xml, - _effective_dtype_for_bands, - _sentinel_for_dtype, - ) + from .._vrt import _effective_dtype_for_bands, _read_vrt_xml, _sentinel_for_dtype, parse_vrt # Parse the VRT XML up-front (cheap; no pixel decode). Route through # ``_read_vrt_xml`` so the 64 MiB ``XRSPATIAL_VRT_MAX_XML_BYTES`` cap diff --git a/xrspatial/geotiff/_cog_http.py b/xrspatial/geotiff/_cog_http.py index 393b8f3b4..84e00818f 100644 --- a/xrspatial/geotiff/_cog_http.py +++ b/xrspatial/geotiff/_cog_http.py @@ -59,51 +59,27 @@ # pulled back through ``_reader`` so monkeypatches against # ``_reader._apply_photometric_miniswhite`` / ``_reader._decode_strip_or_tile`` # keep working after the helper move (PR-J / #2258). -from ._decode import ( - _PARALLEL_DECODE_PIXEL_THRESHOLD, - _apply_orientation_with_geo, - _miniswhite_inverted_nodata, - _read_strips, - _resolve_masked_fill, -) +from ._decode import (_PARALLEL_DECODE_PIXEL_THRESHOLD, _apply_orientation_with_geo, + _miniswhite_inverted_nodata, _read_strips, _resolve_masked_fill) from ._dtypes import SUB_BYTE_BPS, resolve_bits_per_sample, tiff_dtype_to_numpy -from ._geotags import ( - GeoInfo, - extract_geo_info_with_overview_inheritance, -) -from ._header import ( - IFD, - TIFFHeader, - parse_all_ifds, - parse_header, - select_overview_ifd, - validate_tile_layout, -) -from ._layout import ( - MAX_PIXELS_DEFAULT, - _check_dimensions, - _check_source_dimensions, - _compute_full_image_byte_budget, - _has_sparse, - _ifd_required_extent, - _sparse_fill_value, -) -from ._sources import ( - COALESCE_GAP_THRESHOLD_DEFAULT, - # ``_HTTPSource`` is imported as a real module-level binding (rather - # than guarded by TYPE_CHECKING) because it appears in the public - # signature of three helpers and ``typing.get_type_hints(...)`` - # resolves annotations against the function's ``__globals__``. - # Hiding the name behind TYPE_CHECKING would break runtime hint - # introspection on this module *and* on the re-exports in - # ``_reader`` (which share the same function objects). At runtime - # every construction call still goes through ``_reader._HTTPSource`` - # so monkeypatches against the ``_reader`` namespace continue to - # intercept the source. PR-J / #2258. - _HTTPSource, - _max_coalesced_range_bytes_from_env, - _max_tile_bytes_from_env, -) +from ._geotags import GeoInfo, extract_geo_info_with_overview_inheritance +from ._header import (IFD, TIFFHeader, parse_all_ifds, parse_header, select_overview_ifd, + validate_tile_layout) +from ._layout import (MAX_PIXELS_DEFAULT, _check_dimensions, _check_source_dimensions, + _compute_full_image_byte_budget, _has_sparse, _ifd_required_extent, + _sparse_fill_value) +# ``_HTTPSource`` is imported as a real module-level binding (rather +# than guarded by TYPE_CHECKING) because it appears in the public +# signature of three helpers and ``typing.get_type_hints(...)`` +# resolves annotations against the function's ``__globals__``. +# Hiding the name behind TYPE_CHECKING would break runtime hint +# introspection on this module *and* on the re-exports in +# ``_reader`` (which share the same function objects). At runtime +# every construction call still goes through ``_reader._HTTPSource`` +# so monkeypatches against the ``_reader`` namespace continue to +# intercept the source. PR-J / #2258. +from ._sources import (COALESCE_GAP_THRESHOLD_DEFAULT, _HTTPSource, + _max_coalesced_range_bytes_from_env, _max_tile_bytes_from_env) from ._validation import _validate_predictor_sample_format #: Initial prefetch size for ``_parse_cog_http_meta``. Sized for the common diff --git a/xrspatial/geotiff/_compression.py b/xrspatial/geotiff/_compression.py index 24f516a1b..62d46f039 100644 --- a/xrspatial/geotiff/_compression.py +++ b/xrspatial/geotiff/_compression.py @@ -585,7 +585,8 @@ def _predictor_encode_u64(view, width, height, samples_per_pixel): view[idx] = np.uint64(view[idx]) - np.uint64(view[idx - samples_per_pixel]) -import sys as _sys +import sys as _sys # noqa: E402 + _NATIVE_BO = '<' if _sys.byteorder == 'little' else '>' @@ -1482,8 +1483,9 @@ def jpeg2000_decompress(data: bytes, width: int = 0, height: int = 0, "glymur is required to read JPEG 2000-compressed TIFFs. " "Install it with: pip install glymur") import math - import tempfile import os + import tempfile + # glymur reads from files, so write the codestream to a temp file fd, tmp = tempfile.mkstemp(suffix='.j2k') try: @@ -1540,8 +1542,8 @@ def jpeg2000_compress(data: bytes, width: int, height: int, "glymur is required to write JPEG 2000-compressed TIFFs. " "Install it with: pip install glymur") import math - import tempfile import os + import tempfile if samples == 1: arr = np.frombuffer(data, dtype=dtype).reshape(height, width) else: diff --git a/xrspatial/geotiff/_coords.py b/xrspatial/geotiff/_coords.py index 902542ff3..059b1836b 100644 --- a/xrspatial/geotiff/_coords.py +++ b/xrspatial/geotiff/_coords.py @@ -31,8 +31,7 @@ import xarray as xr from ._errors import NonUniformCoordsError -from ._geotags import _NO_GEOREF_KEY, GeoTransform, RASTER_PIXEL_IS_POINT - +from ._geotags import _NO_GEOREF_KEY, RASTER_PIXEL_IS_POINT, GeoTransform # Canonical georef_status string values (mirrored in ``_attrs.py`` as # ``GEOREF_STATUS_*`` constants). Issue #2225 centralises the diff --git a/xrspatial/geotiff/_crs.py b/xrspatial/geotiff/_crs.py index 0b08e20ed..0df5fef59 100644 --- a/xrspatial/geotiff/_crs.py +++ b/xrspatial/geotiff/_crs.py @@ -18,7 +18,6 @@ from ._errors import UnparseableCRSError from ._runtime import GeoTIFFFallbackWarning, _geotiff_strict_mode - #: WKT root keywords. A string that starts with one of these (after #: stripping leading whitespace) is structurally a WKT and is allowed #: to land in ``GTCitationGeoKey`` even when pyproj is not available @@ -237,7 +236,7 @@ def _resolve_crs_to_wkt(crs) -> str | None: # keywords; anything else (EPSG:NNNN, +proj=...) gets normalised # through pyproj so the downstream XML sees a canonical WKT. if crs.lstrip().startswith(('PROJCS', 'GEOGCS', 'PROJCRS', 'GEOGCRS', - 'COMPD_CS', 'COMPOUNDCRS')): + 'COMPD_CS', 'COMPOUNDCRS')): return crs try: from pyproj import CRS diff --git a/xrspatial/geotiff/_decode.py b/xrspatial/geotiff/_decode.py index 7b577e7f3..f8ca9ff8b 100644 --- a/xrspatial/geotiff/_decode.py +++ b/xrspatial/geotiff/_decode.py @@ -33,25 +33,11 @@ import numpy as np -from ._compression import ( - COMPRESSION_LERC, - decompress, - fp_predictor_decode, - lerc_decompress_with_mask, - predictor_decode, - unpack_bits, -) +from ._compression import (COMPRESSION_LERC, decompress, fp_predictor_decode, + lerc_decompress_with_mask, predictor_decode, unpack_bits) from ._dtypes import SUB_BYTE_BPS, resolve_bits_per_sample -from ._geotags import ( - GeoInfo, - GeoTransform, - RASTER_PIXEL_IS_POINT, -) -from ._header import ( - IFD, - TIFFHeader, - validate_tile_layout, -) +from ._geotags import RASTER_PIXEL_IS_POINT, GeoInfo, GeoTransform +from ._header import IFD, TIFFHeader, validate_tile_layout from ._sources import _max_tile_bytes_from_env from ._validation import _validate_predictor_sample_format @@ -77,6 +63,7 @@ def _resolve_max_pixels(value): return MAX_PIXELS_DEFAULT return value + #: Per-tile pixel count at and above which the local and HTTP tile-read paths #: spread codec decode across a ``ThreadPoolExecutor``. Below this, pool #: startup costs outweigh the parallelism win (issue #1551). Bound is inclusive @@ -329,12 +316,8 @@ def _read_strips(data: bytes, ifd: IFD, header: TIFFHeader, # Layout / validation helpers live in ``_layout`` (issue #2247). # Imported lazily so the decode module stays cycle-free against the # layout module's lazy import of ``_int_nodata_in_range`` from here. - from ._layout import ( - _check_dimensions, - _check_source_dimensions, - _has_sparse, - _sparse_fill_value, - ) + from ._layout import (_check_dimensions, _check_source_dimensions, _has_sparse, + _sparse_fill_value) max_pixels = _resolve_max_pixels(max_pixels) width = ifd.width height = ifd.height @@ -559,12 +542,8 @@ def _read_tiles(data: bytes, ifd: IFD, header: TIFFHeader, # the layout module's binding) so tests that monkeypatch # ``_reader.MAX_PIXELS_DEFAULT`` keep taking effect on the per-tile # path. The function-level helpers come from ``_layout`` directly. + from ._layout import _check_dimensions, _has_sparse, _sparse_fill_value from ._reader import MAX_PIXELS_DEFAULT - from ._layout import ( - _check_dimensions, - _has_sparse, - _sparse_fill_value, - ) max_pixels = _resolve_max_pixels(max_pixels) width = ifd.width height = ifd.height diff --git a/xrspatial/geotiff/_encode.py b/xrspatial/geotiff/_encode.py index 9e264e5b8..467a316f6 100644 --- a/xrspatial/geotiff/_encode.py +++ b/xrspatial/geotiff/_encode.py @@ -37,21 +37,10 @@ import numpy as np -from ._compression import ( - COMPRESSION_DEFLATE, - COMPRESSION_JPEG, - COMPRESSION_JPEG2000, - COMPRESSION_LERC, - COMPRESSION_LZ4, - COMPRESSION_LZW, - COMPRESSION_NONE, - COMPRESSION_PACKBITS, - COMPRESSION_ZSTD, - compress, - fp_predictor_encode, - jpeg_compress, - predictor_encode, -) +from ._compression import (COMPRESSION_DEFLATE, COMPRESSION_JPEG, COMPRESSION_JPEG2000, + COMPRESSION_LERC, COMPRESSION_LZ4, COMPRESSION_LZW, COMPRESSION_NONE, + COMPRESSION_PACKBITS, COMPRESSION_ZSTD, compress, fp_predictor_encode, + jpeg_compress, predictor_encode) from ._header import TAG_PHOTOMETRIC from ._write_layout import BO @@ -435,8 +424,8 @@ def _write_stripped(data: np.ndarray, compression: int, predictor: int, # ``deflate`` (libdeflate) binding holds the GIL during compress, so # 8 threads run effectively serially through it. Sequential callers # still get libdeflate's per-call speedup (~3x). - from concurrent.futures import ThreadPoolExecutor import os + from concurrent.futures import ThreadPoolExecutor n_workers = min(num_strips, os.cpu_count() or 4) with ThreadPoolExecutor(max_workers=n_workers) as pool: @@ -622,8 +611,8 @@ def _write_tiled(data: np.ndarray, compression: int, predictor: int, return rel_offsets, byte_counts, tiles # Parallel tile compression -- zlib/zstd/LZW all release the GIL - from concurrent.futures import ThreadPoolExecutor import os + from concurrent.futures import ThreadPoolExecutor n_workers = min(n_tiles, os.cpu_count() or 4) tile_indices = [(tr, tc) for tr in range(tiles_down) diff --git a/xrspatial/geotiff/_geotags.py b/xrspatial/geotiff/_geotags.py index 2eb7f9c6e..be0e6956c 100644 --- a/xrspatial/geotiff/_geotags.py +++ b/xrspatial/geotiff/_geotags.py @@ -1,9 +1,20 @@ """GeoTIFF tag interpretation: CRS, affine transform, GeoKeys.""" from __future__ import annotations -import struct from dataclasses import dataclass, field +from ._dtypes import resolve_bits_per_sample +from ._errors import RotatedTransformError, UnknownCRSModelTypeError +from ._header import (IFD, TAG_BITS_PER_SAMPLE, TAG_COMPRESSION, TAG_EXTRA_SAMPLES, + TAG_GDAL_METADATA, TAG_GDAL_NODATA, TAG_GEO_ASCII_PARAMS, + TAG_GEO_DOUBLE_PARAMS, TAG_GEO_KEY_DIRECTORY, TAG_IMAGE_LENGTH, + TAG_IMAGE_WIDTH, TAG_MODEL_PIXEL_SCALE, TAG_MODEL_TIEPOINT, + TAG_MODEL_TRANSFORMATION, TAG_NEW_SUBFILE_TYPE, TAG_ORIENTATION, + TAG_PHOTOMETRIC, TAG_PLANAR_CONFIG, TAG_PREDICTOR, TAG_RESOLUTION_UNIT, + TAG_ROWS_PER_STRIP, TAG_SAMPLE_FORMAT, TAG_SAMPLES_PER_PIXEL, + TAG_STRIP_BYTE_COUNTS, TAG_STRIP_OFFSETS, TAG_SUB_IFDS, TAG_TILE_BYTE_COUNTS, + TAG_TILE_LENGTH, TAG_TILE_OFFSETS, TAG_TILE_WIDTH, TAG_X_RESOLUTION, + TAG_Y_RESOLUTION) # Stamped by the reader on arrays read from files that carry no # GeoTIFF transform tags (ModelTransformation, ModelPixelScale, or @@ -15,27 +26,6 @@ # without a cycle. See issue #2120. _NO_GEOREF_KEY = '_xrspatial_no_georef' -from ._header import ( - IFD, - TAG_NEW_SUBFILE_TYPE, - TAG_IMAGE_WIDTH, TAG_IMAGE_LENGTH, TAG_BITS_PER_SAMPLE, - TAG_COMPRESSION, TAG_PHOTOMETRIC, - TAG_STRIP_OFFSETS, TAG_ORIENTATION, TAG_SAMPLES_PER_PIXEL, - TAG_ROWS_PER_STRIP, TAG_STRIP_BYTE_COUNTS, - TAG_X_RESOLUTION, TAG_Y_RESOLUTION, - TAG_PLANAR_CONFIG, TAG_RESOLUTION_UNIT, - TAG_PREDICTOR, TAG_COLORMAP, TAG_SUB_IFDS, - TAG_TILE_WIDTH, TAG_TILE_LENGTH, - TAG_TILE_OFFSETS, TAG_TILE_BYTE_COUNTS, - TAG_EXTRA_SAMPLES, - TAG_SAMPLE_FORMAT, TAG_GDAL_METADATA, TAG_GDAL_NODATA, - TAG_MODEL_PIXEL_SCALE, TAG_MODEL_TIEPOINT, - TAG_MODEL_TRANSFORMATION, - TAG_GEO_KEY_DIRECTORY, TAG_GEO_DOUBLE_PARAMS, TAG_GEO_ASCII_PARAMS, -) -from ._dtypes import resolve_bits_per_sample -from ._errors import RotatedTransformError, UnknownCRSModelTypeError - # ImageDescription tag (270). Captured for round-trip but not managed # by the writer -- it flows through extra_tags pass-through. TAG_IMAGE_DESCRIPTION = 270 @@ -202,6 +192,7 @@ def _parse_gdal_metadata(xml_str: str) -> dict: Per-band items are stored as ``{(name, band_int): value}``. """ import xml.etree.ElementTree as ET + from ._safe_xml import safe_fromstring result = {} try: @@ -238,7 +229,8 @@ def _build_gdal_metadata_xml(meta: dict) -> str: the document or inject extra elements. Sample indices are emitted from an ``int(...)`` cast and need no escaping. See issue #1614. """ - from xml.sax.saxutils import escape as _xml_escape, quoteattr as _xml_quoteattr + from xml.sax.saxutils import escape as _xml_escape + from xml.sax.saxutils import quoteattr as _xml_quoteattr def _text(v) -> str: if v is None: @@ -276,7 +268,8 @@ def _epsg_to_wkt(epsg: int) -> str | None: return CRS.from_epsg(epsg).to_wkt() except Exception as e: import warnings - from . import _geotiff_strict_mode, GeoTIFFFallbackWarning + + from . import GeoTIFFFallbackWarning, _geotiff_strict_mode if _geotiff_strict_mode(): raise warnings.warn( @@ -403,7 +396,8 @@ def _synthesize_user_defined_wkt( return crs.to_wkt() except Exception as exc: import warnings - from . import _geotiff_strict_mode, GeoTIFFFallbackWarning + + from . import GeoTIFFFallbackWarning, _geotiff_strict_mode if _geotiff_strict_mode(): raise warnings.warn( @@ -1057,7 +1051,10 @@ def extract_geo_info(ifd: IFD, data: bytes | memoryview, has_georef=has_georef, crs_epsg=epsg, model_type=int(model_type) if isinstance(model_type, (int, float)) else 0, - raster_type=int(raster_type) if isinstance(raster_type, (int, float)) else RASTER_PIXEL_IS_AREA, + raster_type=( + int(raster_type) if isinstance(raster_type, (int, float)) + else RASTER_PIXEL_IS_AREA + ), nodata=nodata, colormap=colormap, x_resolution=ifd.x_resolution, @@ -1477,7 +1474,6 @@ def build_geo_tags(transform: GeoTransform, crs_epsg: int | None = None, ) # GeoKeyDirectoryTag (34735) - geokeys = [] # Header: version=1, revision=1, minor=0 num_keys = 1 # at least RasterType key_entries = [] diff --git a/xrspatial/geotiff/_gpu_decode.py b/xrspatial/geotiff/_gpu_decode.py index 3e9e8c66c..dae797e3f 100644 --- a/xrspatial/geotiff/_gpu_decode.py +++ b/xrspatial/geotiff/_gpu_decode.py @@ -25,7 +25,7 @@ def _warn_or_raise_gpu_fallback(stage: str, exc: BaseException) -> bool: chooses its own next step (typically another decoder or CPU fallback). """ - from . import _geotiff_strict_mode, GeoTIFFFallbackWarning + from . import GeoTIFFFallbackWarning, _geotiff_strict_mode if _geotiff_strict_mode(): return True warnings.warn( @@ -36,6 +36,7 @@ def _warn_or_raise_gpu_fallback(stage: str, exc: BaseException) -> bool: ) return False + #: Fraction of free GPU memory we're willing to allocate in a single call. #: Above this, raise MemoryError up-front so the caller gets an actionable #: error rather than a CUDA OOM deep inside the kernel launch. @@ -81,6 +82,7 @@ def _check_gpu_memory(required_bytes: int, what: str = "tile buffer") -> None: "with cupy.get_default_memory_pool().free_all_blocks()." ) + def _xp_byteswap(arr): """Return *arr* with each element's bytes physically reversed. @@ -313,15 +315,10 @@ def _lzw_decode_tiles_kernel( # Type aliases for Numba CUDA local arrays -from numba import ( - int32 as numba_int32, - uint8 as numba_uint8, - uint16 as numba_uint16, - uint32 as numba_uint32, - uint64 as numba_uint64, - int64 as numba_int64, -) - +from numba import int32 as numba_int32 # noqa: E402 +from numba import int64 as numba_int64 # noqa: E402 +from numba import uint8 as numba_uint8 # noqa: E402 +from numba import uint32 as numba_uint32 # noqa: E402 # --------------------------------------------------------------------------- # Deflate/inflate decode kernel -- one thread block per tile @@ -368,7 +365,7 @@ def _inflate_read_bits(src, src_start, src_len, bit_pos, n): @cuda.jit(device=True) def _inflate_build_table(lengths, n_codes, table, max_bits, - overflow_codes, overflow_lens, n_overflow): + overflow_codes, overflow_lens, n_overflow): """Build a Huffman decode table from code lengths. Codes <= max_bits go into the fast table: table[reversed_code] = (sym << 5) | length. @@ -425,7 +422,7 @@ def _inflate_build_table(lengths, n_codes, table, max_bits, @cuda.jit(device=True) def _inflate_decode_symbol(src, src_start, src_len, bit_pos, table, max_bits, - overflow_codes, overflow_lens, n_overflow): + overflow_codes, overflow_lens, n_overflow): """Decode one Huffman symbol. Fast table for short codes, overflow scan for long.""" # Peek 15 bits (max deflate code length) peek = numba_int64(0) @@ -968,8 +965,8 @@ def _try_kvikio_read_tiles(file_path, tile_offsets, tile_byte_counts, tile_bytes return [] try: - import kvikio import cupy + import kvikio except ImportError: return None @@ -1146,6 +1143,7 @@ def _try_nvcomp_batch_decompress(compressed_tiles, tile_bytes, compression): # Direct ctypes nvCOMP C API import ctypes + import cupy class _NvcompDecompOpts(ctypes.Structure): @@ -1350,7 +1348,7 @@ def _get_nvjpeg(): def _try_nvjpeg_batch_decode(compressed_tiles, tile_width, tile_height, - samples): + samples): """Try batch JPEG decode via nvJPEG. Returns CuPy buffer or None. Decodes all JPEG tiles on GPU in one batched call. Falls back to None @@ -1361,6 +1359,7 @@ def _try_nvjpeg_batch_decode(compressed_tiles, tile_width, tile_height, return None import ctypes + import cupy try: @@ -1459,7 +1458,7 @@ class _NvjpegImage(ctypes.Structure): def _nvjpeg_batch_encode(d_tile_bufs, tile_width, tile_height, samples, - quality=75): + quality=75): """Encode tiles as JPEG on GPU via nvJPEG. Returns list of bytes or None. Each tile must be a CuPy uint8 array of interleaved pixel data. @@ -1469,11 +1468,10 @@ def _nvjpeg_batch_encode(d_tile_bufs, tile_width, tile_height, samples, return None import ctypes + import cupy try: - n_tiles = len(d_tile_bufs) - nvjpeg_handle = ctypes.c_void_p() create_fn = getattr(lib, 'nvjpegCreateSimple', None) if create_fn is None: @@ -1694,6 +1692,7 @@ def _try_nvcomp_from_device_bufs(d_tiles, tile_bytes, compression): See issue #1659. """ import ctypes + import cupy lib = _get_nvcomp() @@ -1839,10 +1838,10 @@ def _gpu_predictor2_encode(d_decomp, tile_width, total_rows, dtype, samples): def _apply_predictor_and_assemble(d_decomp, d_decomp_offsets, n_tiles, - tile_width, tile_height, - image_width, image_height, - predictor, dtype, samples, tile_bytes, - byte_order: str = '<'): + tile_width, tile_height, + image_width, image_height, + predictor, dtype, samples, tile_bytes, + byte_order: str = '<'): """Apply predictor decode and tile assembly on GPU.""" import cupy @@ -2439,6 +2438,7 @@ def _nvcomp_batch_compress(d_tile_bufs, tile_byte_counts, tile_bytes, Compressed tile data on CPU, ready for file assembly. """ import ctypes + import cupy lib = _get_nvcomp() @@ -2546,8 +2546,8 @@ class _DeflateCompOpts(ctypes.Structure): # stream and is the dominant cost on the deflate path). adler_checksums = None if compression in (8, 32946): - import zlib import struct + import zlib adler_checksums = [None] * n_tiles if n_tiles > 0: d_contig = cupy.empty(n_tiles * tile_bytes, dtype=cupy.uint8) @@ -2656,7 +2656,7 @@ def _get_nvjpeg2k(): def _try_nvjpeg2k_batch_decode(compressed_tiles, tile_width, tile_height, - dtype, samples): + dtype, samples): """Try decoding JPEG 2000 tiles via nvJPEG2000. Returns list of CuPy arrays or None. Each tile is decoded independently. The decoded pixels are returned as a @@ -2844,13 +2844,14 @@ class _NvJpeg2kImage(ctypes.Structure): def _nvjpeg2k_batch_encode(d_tile_bufs, tile_width, tile_height, - dtype, samples, n_tiles, lossless=True): + dtype, samples, n_tiles, lossless=True): """Encode tiles as JPEG 2000 via nvJPEG2000. Returns list of bytes or None.""" lib = _get_nvjpeg2k() if lib is None: return None import ctypes + import cupy try: diff --git a/xrspatial/geotiff/_header.py b/xrspatial/geotiff/_header.py index ec31339da..8ef15a303 100644 --- a/xrspatial/geotiff/_header.py +++ b/xrspatial/geotiff/_header.py @@ -6,14 +6,7 @@ from dataclasses import dataclass, field from typing import Any -from ._dtypes import ( - TIFF_TYPE_SIZES, - TIFF_TYPE_STRUCT_CODES, - RATIONAL, - SRATIONAL, - ASCII, - UNDEFINED, -) +from ._dtypes import ASCII, RATIONAL, SRATIONAL, TIFF_TYPE_SIZES, TIFF_TYPE_STRUCT_CODES, UNDEFINED # Caps for IFD entries that aren't pixel-data offset or byte-count # arrays. Pixel-data arrays (TileOffsets, StripOffsets, etc.) scale with diff --git a/xrspatial/geotiff/_overview.py b/xrspatial/geotiff/_overview.py index 6360e5dee..c66e96295 100644 --- a/xrspatial/geotiff/_overview.py +++ b/xrspatial/geotiff/_overview.py @@ -18,7 +18,6 @@ import numpy as np - OVERVIEW_METHODS = ('mean', 'nearest', 'min', 'max', 'median', 'mode', 'cubic') #: Maximum number of overview levels generated by auto-overview mode in COG diff --git a/xrspatial/geotiff/_reader.py b/xrspatial/geotiff/_reader.py index 8f80162cd..80bd0e922 100644 --- a/xrspatial/geotiff/_reader.py +++ b/xrspatial/geotiff/_reader.py @@ -17,10 +17,6 @@ """ from __future__ import annotations -import math -import os as _os_module -from concurrent.futures import ThreadPoolExecutor - import numpy as np # ``urllib3`` is kept as a top-level import here even though the HTTP # source moved to ``_sources`` in #2228. ``test_http_no_stdlib_fallback_2050`` @@ -30,22 +26,41 @@ # "urllib3 is a hard install dep" guard. import urllib3 # noqa: F401 -from ._compression import COMPRESSION_LERC -from ._dtypes import SUB_BYTE_BPS, resolve_bits_per_sample, tiff_dtype_to_numpy -from ._geotags import ( - GeoInfo, - extract_geo_info_with_overview_inheritance, -) -from ._header import ( - IFD, - TIFFHeader, - parse_all_ifds, - parse_header, - select_overview_ifd, - validate_tile_layout, -) -from ._validation import _validate_predictor_sample_format - +# COG-over-HTTP transport (bounded header prefetch, range-based tile/strip +# fetch + decode) lives in ``_cog_http``. It is imported back here so that: +# * existing call sites inside this module (``_read_cog_http``, +# ``_parse_cog_http_meta``) keep their bare names, and +# * the historical public import surface +# (``from xrspatial.geotiff._reader import _read_cog_http`` and +# friends, used by the dask backend, the test suite, and external +# code that patches ``_reader._HTTPSource`` / ``_reader._parse_cog_http_meta``) +# stays intact without churn. +# ``_cog_http._read_cog_http`` resolves ``_HTTPSource`` and +# ``_parse_cog_http_meta`` through this module (via ``from . import _reader`` +# at call time) so monkeypatches against ``_reader._HTTPSource`` / +# ``_reader._parse_cog_http_meta`` continue to take effect after the move. +# Source: PR-J of the GeoTIFF refactor epic, issue #2258. +from ._cog_http import (INITIAL_HTTP_HEADER_BYTES, MAX_HTTP_HEADER_BYTES, # noqa: F401 + _fetch_decode_cog_http_strips, _fetch_decode_cog_http_tiles, + _parse_cog_http_meta, _read_cog_http) +# Strip/tile decode orchestration lives in ``_decode``. It is imported +# back here so that: +# * existing call sites inside this module (``_read_strips``, +# ``_read_tiles``, ``_decode_strip_or_tile``, the photometric and +# orientation helpers) keep their bare names, and +# * the historical public import surface +# (``from xrspatial.geotiff._reader import _read_strips`` and +# friends, used by VRT / GPU / dask backends, the writer, and the +# test suite) is preserved without churn. +# Source: PR-G of the GeoTIFF refactor epic, issue #2246. +from ._decode import (_NATIVE_ORDER, _PARALLEL_DECODE_PIXEL_THRESHOLD, # noqa: F401 + _apply_orientation, _apply_orientation_with_geo, + _apply_photometric_miniswhite, _apply_predictor, _decode_strip_or_tile, + _int_nodata_in_range, _miniswhite_inverted_nodata, _packed_byte_count, + _read_strips, _read_tiles, _resolve_masked_fill) +from ._dtypes import resolve_bits_per_sample, tiff_dtype_to_numpy +from ._geotags import GeoInfo, extract_geo_info_with_overview_inheritance +from ._header import parse_all_ifds, parse_header, select_overview_ifd # Layout / validation helpers live in ``_layout``. They are imported back # here so that: # * existing call sites inside this module keep their bare names, and @@ -55,18 +70,10 @@ # by sidecar / VRT / GPU / dask backends and by the test suite) is # preserved without churn. # Source: PR-H of the GeoTIFF refactor epic, issue #2247. -from ._layout import ( # noqa: F401 - MAX_PIXELS_DEFAULT, - PixelSafetyLimitError, - _FULL_IMAGE_BUDGET_HEADER_SLACK, - _check_dimensions, - _check_source_dimensions, - _compute_full_image_byte_budget, - _has_sparse, - _ifd_required_extent, - _sparse_fill_value, -) - +from ._layout import (_FULL_IMAGE_BUDGET_HEADER_SLACK, MAX_PIXELS_DEFAULT, # noqa: F401 + PixelSafetyLimitError, _check_dimensions, _check_source_dimensions, + _compute_full_image_byte_budget, _has_sparse, _ifd_required_extent, + _sparse_fill_value) # The data-source layer (local mmap, HTTP with SSRF defences and DNS-rebind # pinning, fsspec cloud, BytesIO) lives in ``_sources``. It is imported back # here so that: @@ -77,114 +84,32 @@ # used by sidecar / VRT / GPU / dask backends and by the test suite) is # preserved without churn. # Source: PR-E of the GeoTIFF refactor epic, issue #2228. -from ._sources import ( # noqa: F401 - # Public module-level constants. - COALESCE_GAP_THRESHOLD_DEFAULT, - MAX_CLOUD_BYTES_DEFAULT, - MAX_COALESCED_RANGE_BYTES_DEFAULT, - MAX_TILE_BYTES_DEFAULT, - # Private module-level constants and sentinels. - _CLOUD_SCHEMES, - _DEFAULT_MMAP_CACHE_SIZE, - _HTTP_ALLOWED_SCHEMES, - _HTTP_CONNECT_TIMEOUT_DEFAULT, - _HTTP_MAX_REDIRECTS, - _HTTP_READ_TIMEOUT_DEFAULT, - _MAX_CLOUD_BYTES_SENTINEL, - # Exceptions. - CloudSizeLimitError, - UnsafeURLError, - # Source classes and the shared mmap cache singleton. - _BytesIOSource, - _CloudSource, - _FileSource, - _HTTPSource, - _MmapCache, - _mmap_cache, - # Public byte-range helpers. - coalesce_ranges, - split_coalesced_bytes, - # Private helpers and dispatch. - _build_pinned_connection_classes, - _coerce_path, - _get_http_pool, - _get_pinned_conn_classes, - _http_allow_private_hosts, - _http_connect_timeout, - _http_read_timeout, - _http_timeout_from_env, - _ip_is_private, - _is_file_like, - _is_fsspec_uri, - _make_pinned_pool, - _max_coalesced_range_bytes_from_env, - _max_tile_bytes_from_env, - _mmap_cache_size_from_env, - _open_source, - _resolve_max_cloud_bytes, - _validate_http_url, -) - -# Strip/tile decode orchestration lives in ``_decode``. It is imported -# back here so that: -# * existing call sites inside this module (``_read_strips``, -# ``_read_tiles``, ``_decode_strip_or_tile``, the photometric and -# orientation helpers) keep their bare names, and -# * the historical public import surface -# (``from xrspatial.geotiff._reader import _read_strips`` and -# friends, used by VRT / GPU / dask backends, the writer, and the -# test suite) is preserved without churn. -# Source: PR-G of the GeoTIFF refactor epic, issue #2246. -from ._decode import ( # noqa: F401 - _NATIVE_ORDER, - _PARALLEL_DECODE_PIXEL_THRESHOLD, - _apply_orientation, - _apply_orientation_with_geo, - _apply_photometric_miniswhite, - _apply_predictor, - _decode_strip_or_tile, - _int_nodata_in_range, - _miniswhite_inverted_nodata, - _packed_byte_count, - _read_strips, - _read_tiles, - _resolve_masked_fill, -) - -# COG-over-HTTP transport (bounded header prefetch, range-based tile/strip -# fetch + decode) lives in ``_cog_http``. It is imported back here so that: -# * existing call sites inside this module (``_read_cog_http``, -# ``_parse_cog_http_meta``) keep their bare names, and -# * the historical public import surface -# (``from xrspatial.geotiff._reader import _read_cog_http`` and -# friends, used by the dask backend, the test suite, and external -# code that patches ``_reader._HTTPSource`` / ``_reader._parse_cog_http_meta``) -# stays intact without churn. -# ``_cog_http._read_cog_http`` resolves ``_HTTPSource`` and -# ``_parse_cog_http_meta`` through this module (via ``from . import _reader`` -# at call time) so monkeypatches against ``_reader._HTTPSource`` / -# ``_reader._parse_cog_http_meta`` continue to take effect after the move. -# Source: PR-J of the GeoTIFF refactor epic, issue #2258. -from ._cog_http import ( # noqa: F401 - INITIAL_HTTP_HEADER_BYTES, - MAX_HTTP_HEADER_BYTES, - _fetch_decode_cog_http_strips, - _fetch_decode_cog_http_tiles, - _parse_cog_http_meta, - _read_cog_http, -) - +from ._sources import (_CLOUD_SCHEMES, _DEFAULT_MMAP_CACHE_SIZE, # noqa: F401 + _HTTP_ALLOWED_SCHEMES, _HTTP_CONNECT_TIMEOUT_DEFAULT, _HTTP_MAX_REDIRECTS, + _HTTP_READ_TIMEOUT_DEFAULT, _MAX_CLOUD_BYTES_SENTINEL, + COALESCE_GAP_THRESHOLD_DEFAULT, MAX_CLOUD_BYTES_DEFAULT, + MAX_COALESCED_RANGE_BYTES_DEFAULT, MAX_TILE_BYTES_DEFAULT, + CloudSizeLimitError, UnsafeURLError, _build_pinned_connection_classes, + _BytesIOSource, _CloudSource, _coerce_path, _FileSource, _get_http_pool, + _get_pinned_conn_classes, _http_allow_private_hosts, _http_connect_timeout, + _http_read_timeout, _http_timeout_from_env, _HTTPSource, _ip_is_private, + _is_file_like, _is_fsspec_uri, _make_pinned_pool, + _max_coalesced_range_bytes_from_env, _max_tile_bytes_from_env, _mmap_cache, + _mmap_cache_size_from_env, _MmapCache, _open_source, + _resolve_max_cloud_bytes, _validate_http_url, coalesce_ranges, + split_coalesced_bytes) # --------------------------------------------------------------------------- # Main read function # --------------------------------------------------------------------------- + def _read_to_array(source, *, window=None, overview_level: int | None = None, - band: int | None = None, - max_pixels: int = MAX_PIXELS_DEFAULT, - max_cloud_bytes=_MAX_CLOUD_BYTES_SENTINEL, - allow_rotated: bool = False, - ) -> tuple[np.ndarray, GeoInfo]: + band: int | None = None, + max_pixels: int = MAX_PIXELS_DEFAULT, + max_cloud_bytes=_MAX_CLOUD_BYTES_SENTINEL, + allow_rotated: bool = False, + ) -> tuple[np.ndarray, GeoInfo]: """Read a GeoTIFF/COG to a numpy array (module-private). Parameters @@ -279,9 +204,7 @@ def _read_to_array(source, *, window=None, overview_level: int | None = None, # enforces (#2121). The sidecar must be loaded before IFD # selection so ``overview_level`` indexes into a unified # pyramid list. - from ._sidecar import ( - attach_sidecar_origin, find_sidecar, load_sidecar, - ) + from ._sidecar import attach_sidecar_origin, find_sidecar, load_sidecar sidecar_origin: dict[int, tuple] = {} sidecar_path = find_sidecar(source) if sidecar_path is not None: diff --git a/xrspatial/geotiff/_runtime.py b/xrspatial/geotiff/_runtime.py index 27306e5d9..2902f798c 100644 --- a/xrspatial/geotiff/_runtime.py +++ b/xrspatial/geotiff/_runtime.py @@ -14,7 +14,6 @@ import os - # 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 diff --git a/xrspatial/geotiff/_sidecar.py b/xrspatial/geotiff/_sidecar.py index 2ad63187f..050900f01 100644 --- a/xrspatial/geotiff/_sidecar.py +++ b/xrspatial/geotiff/_sidecar.py @@ -27,7 +27,6 @@ # top-level import does not form a cycle at module load time. from ._reader import _is_fsspec_uri - #: Type of the bytes-like buffer a sidecar carries: an mmap for local #: files, bytes for HTTP / fsspec downloads. Narrowed from ``object`` #: so the reader sees the actual variants when slicing IFD data. diff --git a/xrspatial/geotiff/_sources.py b/xrspatial/geotiff/_sources.py index 0e6e7d9fb..c774f465f 100644 --- a/xrspatial/geotiff/_sources.py +++ b/xrspatial/geotiff/_sources.py @@ -34,7 +34,6 @@ import urllib3 - # --------------------------------------------------------------------------- # Cloud byte budget (eager fsspec reads) # --------------------------------------------------------------------------- @@ -733,12 +732,9 @@ def _build_pinned_connection_classes(): subclasses override ``_new_conn`` to dial the validated IP directly. """ import socket as _socket + from urllib3.connection import HTTPConnection, HTTPSConnection - from urllib3.exceptions import ( - ConnectTimeoutError, - NameResolutionError, - NewConnectionError, - ) + from urllib3.exceptions import ConnectTimeoutError, NameResolutionError, NewConnectionError class _PinnedHTTPConnection(HTTPConnection): """``HTTPConnection`` that dials a fixed IP, ignoring DNS. @@ -834,7 +830,7 @@ def _get_pinned_conn_classes(): def _make_pinned_pool(scheme: str, host: str, port: int, pinned_ip: str, - connect_timeout: float, read_timeout: float): + connect_timeout: float, read_timeout: float): """Build a urllib3 ConnectionPool whose connections dial *pinned_ip*. The pool's ``host`` stays the original hostname so the HTTP ``Host`` diff --git a/xrspatial/geotiff/_validation.py b/xrspatial/geotiff/_validation.py index 92f11fa6e..86f6a16bb 100644 --- a/xrspatial/geotiff/_validation.py +++ b/xrspatial/geotiff/_validation.py @@ -26,21 +26,10 @@ import numpy as np from ._coords import _BAND_DIM_NAMES -from ._errors import ( - ConflictingCRSError, - ConflictingNodataError, - MixedBandMetadataError, - NonUniformCoordsError, - RotatedTransformError, - UnparseableCRSError, -) -from ._runtime import ( - _MISSING_SOURCES_SENTINEL, - _ON_GPU_FAILURE_SENTINEL, - _TIME_DIM_NAMES, - _X_DIM_NAMES, - _Y_DIM_NAMES, -) +from ._errors import (ConflictingCRSError, ConflictingNodataError, MixedBandMetadataError, + NonUniformCoordsError, RotatedTransformError, UnparseableCRSError) +from ._runtime import (_MISSING_SOURCES_SENTINEL, _ON_GPU_FAILURE_SENTINEL, _TIME_DIM_NAMES, + _X_DIM_NAMES, _Y_DIM_NAMES) def _is_temporal_dim_name(name) -> bool: @@ -309,8 +298,8 @@ def _validate_chunks_arg(chunks, *, allow_none=False): if allow_none: return chunks raise ValueError( - f"chunks must be a positive int or (row, col) tuple of " - f"positive ints, got chunks=None.") + "chunks must be a positive int or (row, col) tuple of " + "positive ints, got chunks=None.") if (isinstance(chunks, (int, np.integer)) and not isinstance(chunks, bool)): if chunks <= 0: diff --git a/xrspatial/geotiff/_vrt.py b/xrspatial/geotiff/_vrt.py index ff5f1e3d3..3bd370ce3 100644 --- a/xrspatial/geotiff/_vrt.py +++ b/xrspatial/geotiff/_vrt.py @@ -9,9 +9,11 @@ import os import struct import zlib -from dataclasses import dataclass, field, replace as _dc_replace +from dataclasses import dataclass, field +from dataclasses import replace as _dc_replace from typing import Union -from xml.sax.saxutils import escape as _xml_escape, quoteattr as _xml_quoteattr +from xml.sax.saxutils import escape as _xml_escape +from xml.sax.saxutils import quoteattr as _xml_quoteattr import numpy as np @@ -164,6 +166,7 @@ def _xml_attr(value) -> str: return '""' return _xml_quoteattr(str(value)) + # Mapping from GDAL VRT dataType names to NumPy dtypes. # # ``Int8`` is the GDAL 3.7+ signed-byte name. ``UInt64`` / ``Int64`` are @@ -1072,7 +1075,7 @@ def read_vrt(vrt_path: str, *, window=None, out_h = r1 - r0 out_w = c1 - c0 - from ._reader import _check_dimensions, MAX_PIXELS_DEFAULT + from ._reader import MAX_PIXELS_DEFAULT, _check_dimensions if max_pixels is None: max_pixels = MAX_PIXELS_DEFAULT n_bands = len([vrt.bands[band]] if band is not None else vrt.bands) @@ -1301,7 +1304,8 @@ def read_vrt(vrt_path: str, *, window=None, # sentinel, which is why the default was flipped to raise # in #1843. See also issues #1734 and #1843. import warnings - from . import _geotiff_strict_mode, GeoTIFFFallbackWarning + + from . import GeoTIFFFallbackWarning, _geotiff_strict_mode if missing_sources == 'raise' or _geotiff_strict_mode(): raise warnings.warn( @@ -1529,11 +1533,10 @@ def write_vrt(vrt_path: str, source_files: list[str], *, str Path to the written VRT file. """ - from ._reader import read_to_array - from ._header import parse_header, parse_all_ifds + from ._dtypes import resolve_bits_per_sample from ._geotags import extract_geo_info + from ._header import parse_all_ifds, parse_header from ._reader import _FileSource - from ._dtypes import resolve_bits_per_sample # Defense-in-depth: the public ``write_vrt`` wrapper in # ``_writers/vrt.py`` already rejects bool / non-numeric ``nodata`` via @@ -1569,7 +1572,10 @@ def write_vrt(vrt_path: str, source_files: list[str], *, 'bands': ifd.samples_per_pixel, 'dtype': np.dtype(_DTYPE_MAP.get( {v: k for k, v in _DTYPE_MAP.items()}.get( - np.dtype(f'{"f" if ifd.sample_format == 3 else ("i" if ifd.sample_format == 2 else "u")}{bps // 8}').type, + np.dtype( + f'{"f" if ifd.sample_format == 3 else ("i" if ifd.sample_format == 2 else "u")}' # noqa: E501 + f'{bps // 8}' + ).type, 'Float32'), np.float32)), 'bps': bps, diff --git a/xrspatial/geotiff/_write_layout.py b/xrspatial/geotiff/_write_layout.py index da2be39ca..872c1f687 100644 --- a/xrspatial/geotiff/_write_layout.py +++ b/xrspatial/geotiff/_write_layout.py @@ -34,48 +34,16 @@ import numpy as np from ._compression import COMPRESSION_NONE -from ._dtypes import ( - ASCII, - DOUBLE, - LONG, - LONG8, - RATIONAL, - SHORT, - numpy_to_tiff_dtype, -) -from ._geotags import ( - GeoTransform, - TAG_GDAL_NODATA, - TAG_GEO_ASCII_PARAMS, - TAG_GEO_KEY_DIRECTORY, - TAG_MODEL_PIXEL_SCALE, - TAG_MODEL_TIEPOINT, - TAG_MODEL_TRANSFORMATION, - build_geo_tags, -) -from ._header import ( - TAG_BITS_PER_SAMPLE, - TAG_COMPRESSION, - TAG_EXTRA_SAMPLES, - TAG_GDAL_METADATA, - TAG_IMAGE_LENGTH, - TAG_IMAGE_WIDTH, - TAG_NEW_SUBFILE_TYPE, - TAG_PHOTOMETRIC, - TAG_PREDICTOR, - TAG_RESOLUTION_UNIT, - TAG_ROWS_PER_STRIP, - TAG_SAMPLE_FORMAT, - TAG_SAMPLES_PER_PIXEL, - TAG_STRIP_BYTE_COUNTS, - TAG_STRIP_OFFSETS, - TAG_TILE_BYTE_COUNTS, - TAG_TILE_LENGTH, - TAG_TILE_OFFSETS, - TAG_TILE_WIDTH, - TAG_X_RESOLUTION, - TAG_Y_RESOLUTION, -) +from ._dtypes import ASCII, DOUBLE, LONG, LONG8, RATIONAL, SHORT, numpy_to_tiff_dtype +from ._geotags import (TAG_GDAL_NODATA, TAG_GEO_ASCII_PARAMS, TAG_GEO_KEY_DIRECTORY, + TAG_MODEL_PIXEL_SCALE, TAG_MODEL_TIEPOINT, TAG_MODEL_TRANSFORMATION, + GeoTransform, build_geo_tags) +from ._header import (TAG_BITS_PER_SAMPLE, TAG_COMPRESSION, TAG_EXTRA_SAMPLES, TAG_GDAL_METADATA, + TAG_IMAGE_LENGTH, TAG_IMAGE_WIDTH, TAG_NEW_SUBFILE_TYPE, TAG_PHOTOMETRIC, + TAG_PREDICTOR, TAG_RESOLUTION_UNIT, TAG_ROWS_PER_STRIP, TAG_SAMPLE_FORMAT, + TAG_SAMPLES_PER_PIXEL, TAG_STRIP_BYTE_COUNTS, TAG_STRIP_OFFSETS, + TAG_TILE_BYTE_COUNTS, TAG_TILE_LENGTH, TAG_TILE_OFFSETS, TAG_TILE_WIDTH, + TAG_X_RESOLUTION, TAG_Y_RESOLUTION) # Byte order: always write little-endian. BO = '<' @@ -288,7 +256,7 @@ def _assemble_standard_layout(header_size: int, patched_tags.append((tag_id, type_id, count, values)) ifd_bytes, overflow_bytes = _build_ifd(patched_tags, overflow_base, - bigtiff=bigtiff) + bigtiff=bigtiff) output.extend(ifd_bytes) output.extend(overflow_bytes) @@ -370,7 +338,7 @@ def _assemble_cog_layout(header_size: int, overflow_base = current_ifd_pos + ifd_block_size ifd_bytes, overflow_bytes = _build_ifd(patched_tags, overflow_base, - bigtiff=bigtiff) + bigtiff=bigtiff) # Patch next IFD offset if level_idx < len(ifd_specs) - 1: @@ -495,7 +463,7 @@ def _assemble_tiff(width: int, height: int, dtype: np.dtype, Parameters ---------- - pixel_data_parts : list of (array, width, height, relative_offsets, byte_counts, compressed_data) + pixel_data_parts : list of (array, width, height, rel_offsets, byte_counts, comp_data) One entry per resolution level (full res first, then overviews). is_cog : bool If True, layout IFDs contiguously at file start (COG layout). @@ -575,7 +543,8 @@ def _assemble_tiff(width: int, height: int, dtype: np.dtype, # Build IFDs for each resolution level ifd_specs = [] - for level_idx, (arr, lw, lh, rel_offsets, byte_counts, comp_data) in enumerate(pixel_data_parts): + for level_idx, (arr, lw, lh, rel_offsets, byte_counts, comp_data) in enumerate( + pixel_data_parts): tags = [] # Mark overview IFDs as reduced-resolution images (TIFF tag 254). diff --git a/xrspatial/geotiff/_writer.py b/xrspatial/geotiff/_writer.py index e0ca6d84d..ab0bd069d 100644 --- a/xrspatial/geotiff/_writer.py +++ b/xrspatial/geotiff/_writer.py @@ -20,98 +20,32 @@ import math import struct -import warnings import numpy as np -from ._compression import ( - COMPRESSION_JPEG, - COMPRESSION_NONE, -) -from ._dtypes import ( - DOUBLE, - RATIONAL, - SHORT, - LONG, - ASCII, - numpy_to_tiff_dtype, -) -from ._geotags import ( - GeoTransform, - build_geo_tags, - TAG_GEO_ASCII_PARAMS, - TAG_GEO_KEY_DIRECTORY, - TAG_GDAL_NODATA, - TAG_MODEL_PIXEL_SCALE, - TAG_MODEL_TIEPOINT, - TAG_MODEL_TRANSFORMATION, -) -from ._header import ( - TAG_NEW_SUBFILE_TYPE, - TAG_IMAGE_WIDTH, - TAG_IMAGE_LENGTH, - TAG_BITS_PER_SAMPLE, - TAG_COMPRESSION, - TAG_PHOTOMETRIC, - TAG_SAMPLES_PER_PIXEL, - TAG_SAMPLE_FORMAT, - TAG_STRIP_OFFSETS, - TAG_ROWS_PER_STRIP, - TAG_STRIP_BYTE_COUNTS, - TAG_SUB_IFDS, - TAG_X_RESOLUTION, - TAG_Y_RESOLUTION, - TAG_RESOLUTION_UNIT, - TAG_TILE_WIDTH, - TAG_TILE_LENGTH, - TAG_TILE_OFFSETS, - TAG_TILE_BYTE_COUNTS, - TAG_EXTRA_SAMPLES, - TAG_PREDICTOR, - TAG_GDAL_METADATA, -) -# IFD-assembly and BigTIFF / COG layout helpers live in -# ``_write_layout.py``. Re-export them here so internal call sites -# (``_write``, ``_write_streaming``) and external importers (the -# ``_writers`` subpackage, tests, ``_gpu_decode``) keep using the -# ``xrspatial.geotiff._writer`` import path. -from ._write_layout import ( # noqa: F401 - BO, - _assemble_cog_layout, - _assemble_standard_layout, - _assemble_tiff, - _build_ifd, - _compute_classic_ifd_overhead, - _float_to_rational, - _pack_tag_value, - _promote_offsets_to_long8, - _serialize_tag_value, - _should_use_bigtiff_streaming, -) +from ._compression import COMPRESSION_JPEG, COMPRESSION_NONE +from ._dtypes import ASCII, DOUBLE, LONG, RATIONAL, SHORT, numpy_to_tiff_dtype # Strip / tile encode helpers, photometric resolution, predictor # normalisation, and the compression-name tag mapping live in # ``_encode.py``. Re-export them here so internal call sites # (``_write``, ``_write_streaming``) and external importers (the # ``_writers`` subpackage, tests, ``_gpu_decode``) keep using the # ``xrspatial.geotiff._writer`` import path. See issue #2260. -from ._encode import ( # noqa: F401 - PHOTOMETRIC_MINISBLACK, - PHOTOMETRIC_RGB, - _PARALLEL_MIN_BYTES, - _PHOTOMETRIC_NAME_MAP, - _apply_photometric_miniswhite_invert, - _apply_predictor_encode, - _compress_block, - _compression_tag, - _invert_nodata_for_miniswhite, - _prepare_strip, - _prepare_tile, - _reject_disagreeing_photometric_override, - _resolve_photometric, - _write_stripped, - _write_tiled, - normalize_predictor, -) +from ._encode import (_PARALLEL_MIN_BYTES, _PHOTOMETRIC_NAME_MAP, # noqa: F401 + PHOTOMETRIC_MINISBLACK, PHOTOMETRIC_RGB, _apply_photometric_miniswhite_invert, + _apply_predictor_encode, _compress_block, _compression_tag, + _invert_nodata_for_miniswhite, _prepare_strip, _prepare_tile, + _reject_disagreeing_photometric_override, _resolve_photometric, + _write_stripped, _write_tiled, normalize_predictor) +from ._geotags import (TAG_GDAL_NODATA, TAG_GEO_ASCII_PARAMS, TAG_GEO_KEY_DIRECTORY, + TAG_MODEL_PIXEL_SCALE, TAG_MODEL_TIEPOINT, TAG_MODEL_TRANSFORMATION, + GeoTransform, build_geo_tags) +from ._header import (TAG_BITS_PER_SAMPLE, TAG_COMPRESSION, TAG_EXTRA_SAMPLES, TAG_GDAL_METADATA, + TAG_IMAGE_LENGTH, TAG_IMAGE_WIDTH, TAG_NEW_SUBFILE_TYPE, TAG_PHOTOMETRIC, + TAG_PREDICTOR, TAG_RESOLUTION_UNIT, TAG_ROWS_PER_STRIP, TAG_SAMPLE_FORMAT, + TAG_SAMPLES_PER_PIXEL, TAG_STRIP_BYTE_COUNTS, TAG_STRIP_OFFSETS, TAG_SUB_IFDS, + TAG_TILE_BYTE_COUNTS, TAG_TILE_LENGTH, TAG_TILE_OFFSETS, TAG_TILE_WIDTH, + TAG_X_RESOLUTION, TAG_Y_RESOLUTION) # Overview pyramid helpers (``_make_overview``, ``_block_reduce_2d``, # ``_replicate_pad_2d``, ``_resolve_int_nodata``, # ``_validate_overview_levels``) and the ``OVERVIEW_METHODS`` / @@ -119,15 +53,18 @@ # them here so internal call sites and external importers (the # ``_writers`` subpackage, ``_gpu_decode``, tests) keep using the # ``xrspatial.geotiff._writer`` import path. See issue #2259. -from ._overview import ( # noqa: F401 - OVERVIEW_METHODS, - _MAX_OVERVIEW_LEVELS, - _block_reduce_2d, - _make_overview, - _replicate_pad_2d, - _resolve_int_nodata, - _validate_overview_levels, -) +from ._overview import (_MAX_OVERVIEW_LEVELS, OVERVIEW_METHODS, _block_reduce_2d, # noqa: F401 + _make_overview, _replicate_pad_2d, _resolve_int_nodata, + _validate_overview_levels) +# IFD-assembly and BigTIFF / COG layout helpers live in +# ``_write_layout.py``. Re-export them here so internal call sites +# (``_write``, ``_write_streaming``) and external importers (the +# ``_writers`` subpackage, tests, ``_gpu_decode``) keep using the +# ``xrspatial.geotiff._writer`` import path. +from ._write_layout import (BO, _assemble_cog_layout, _assemble_standard_layout, # noqa: F401 + _assemble_tiff, _build_ifd, _compute_classic_ifd_overhead, + _float_to_rational, _pack_tag_value, _promote_offsets_to_long8, + _serialize_tag_value, _should_use_bigtiff_streaming) # Tag IDs the writer must never accept from ``extra_tags``. NewSubfileType # (254) is a per-IFD status flag the writer emits on its own for overview @@ -314,30 +251,30 @@ def _validate_lowlevel_write_kwargs(*, def _write(data: np.ndarray, path: str, *, - geo_transform: GeoTransform | None = None, - crs_epsg: int | None = None, - crs_wkt: str | None = None, - nodata=None, - compression: str = 'zstd', - compression_level: int | None = None, - tiled: bool = True, - tile_size: int = 256, - predictor: bool | int = False, - cog: bool = False, - overview_levels: list[int] | None = None, - overview_resampling: str = 'mean', - raster_type: int = 1, - x_resolution: float | None = None, - y_resolution: float | None = None, - resolution_unit: int | None = None, - gdal_metadata_xml: str | None = None, - extra_tags: list | None = None, - bigtiff: bool | None = None, - max_z_error: float = 0.0, - photometric='auto', - restore_sentinel: bool = True, - allow_internal_only_jpeg: bool = False, - allow_unparseable_crs: bool = False) -> None: + geo_transform: GeoTransform | None = None, + crs_epsg: int | None = None, + crs_wkt: str | None = None, + nodata=None, + compression: str = 'zstd', + compression_level: int | None = None, + tiled: bool = True, + tile_size: int = 256, + predictor: bool | int = False, + cog: bool = False, + overview_levels: list[int] | None = None, + overview_resampling: str = 'mean', + raster_type: int = 1, + x_resolution: float | None = None, + y_resolution: float | None = None, + resolution_unit: int | None = None, + gdal_metadata_xml: str | None = None, + extra_tags: list | None = None, + bigtiff: bool | None = None, + max_z_error: float = 0.0, + photometric='auto', + restore_sentinel: bool = True, + allow_internal_only_jpeg: bool = False, + allow_unparseable_crs: bool = False) -> None: """Write a numpy array as a GeoTIFF or COG. Parameters @@ -539,12 +476,12 @@ def _write(data: np.ndarray, path: str, *, # Full resolution if tiled: rel_off, bc, comp_data = _write_tiled(data, comp_tag, pred_int, tile_size, - compression_level=compression_level, - max_z_error=max_z_error) + compression_level=compression_level, + max_z_error=max_z_error) else: rel_off, bc, comp_data = _write_stripped(data, comp_tag, pred_int, - compression_level=compression_level, - max_z_error=max_z_error) + compression_level=compression_level, + max_z_error=max_z_error) h, w = data.shape[:2] parts.append((data, w, h, rel_off, bc, comp_data)) @@ -618,13 +555,13 @@ def _write(data: np.ndarray, path: str, *, oh, ow = current.shape[:2] if tiled: o_off, o_bc, o_data = _write_tiled(current, comp_tag, pred_int, - tile_size, - compression_level=compression_level, - max_z_error=max_z_error) + tile_size, + compression_level=compression_level, + max_z_error=max_z_error) else: o_off, o_bc, o_data = _write_stripped(current, comp_tag, pred_int, - compression_level=compression_level, - max_z_error=max_z_error) + compression_level=compression_level, + max_z_error=max_z_error) parts.append((current, ow, oh, o_off, o_bc, o_data)) file_bytes = _assemble_tiff( @@ -665,30 +602,29 @@ def _write(data: np.ndarray, path: str, *, # re-exported above for backwards compatibility. - def _write_streaming(dask_data, path: str, *, - geo_transform: 'GeoTransform | None' = None, - crs_epsg: int | None = None, - crs_wkt: str | None = None, - nodata=None, - compression: str = 'zstd', - compression_level: int | None = None, - tiled: bool = True, - tile_size: int = 256, - predictor: bool | int = False, - raster_type: int = 1, - x_resolution: float | None = None, - y_resolution: float | None = None, - resolution_unit: int | None = None, - gdal_metadata_xml: str | None = None, - extra_tags: list | None = None, - bigtiff: bool | None = None, - streaming_buffer_bytes: int = 256 * 1024 * 1024, - max_z_error: float = 0.0, - photometric='auto', - restore_sentinel: bool = True, - allow_internal_only_jpeg: bool = False, - allow_unparseable_crs: bool = False) -> None: + geo_transform: 'GeoTransform | None' = None, + crs_epsg: int | None = None, + crs_wkt: str | None = None, + nodata=None, + compression: str = 'zstd', + compression_level: int | None = None, + tiled: bool = True, + tile_size: int = 256, + predictor: bool | int = False, + raster_type: int = 1, + x_resolution: float | None = None, + y_resolution: float | None = None, + resolution_unit: int | None = None, + gdal_metadata_xml: str | None = None, + extra_tags: list | None = None, + bigtiff: bool | None = None, + streaming_buffer_bytes: int = 256 * 1024 * 1024, + max_z_error: float = 0.0, + photometric='auto', + restore_sentinel: bool = True, + allow_internal_only_jpeg: bool = False, + allow_unparseable_crs: bool = False) -> None: """Write a dask array as a GeoTIFF by streaming pixel data. For tiled output, each tile-row is computed in horizontal segments @@ -971,7 +907,7 @@ def _write_streaming(dask_data, path: str, *, ifd_block_size = count_size + entry_size * num_tags + next_size overflow_base = header_size + ifd_block_size _, placeholder_overflow = _build_ifd(sorted_tags, overflow_base, - bigtiff=use_bigtiff) + bigtiff=use_bigtiff) pixel_data_start = overflow_base + len(placeholder_overflow) dir_name = os.path.dirname(os.path.abspath(path)) @@ -1053,7 +989,7 @@ def _write_streaming(dask_data, path: str, *, for seg_start in range(0, tiles_across, tiles_per_segment): seg_end = min(seg_start + tiles_per_segment, - tiles_across) + tiles_across) seg_c0 = seg_start * tw seg_c1 = min(seg_end * tw, width) diff --git a/xrspatial/geotiff/_writers/eager.py b/xrspatial/geotiff/_writers/eager.py index 7aea77d67..68c9d1b15 100644 --- a/xrspatial/geotiff/_writers/eager.py +++ b/xrspatial/geotiff/_writers/eager.py @@ -21,38 +21,20 @@ if TYPE_CHECKING: from typing import BinaryIO -from .._attrs import ( - _EXPERIMENTAL_CODECS, - _LEVEL_RANGES, - _VALID_COMPRESSIONS, - _extract_rich_tags, - _resolve_nodata_attr, - _should_restore_nan_sentinel, -) +from .._attrs import (_EXPERIMENTAL_CODECS, _LEVEL_RANGES, _VALID_COMPRESSIONS, _extract_rich_tags, + _resolve_nodata_attr, _should_restore_nan_sentinel) from .._backends._gpu_helpers import _is_gpu_data -from .._coords import ( - _BAND_DIM_NAMES, - _has_no_georef_marker, - require_transform_for_georeferenced as _require_transform_for_georeferenced, - resolve_georef as _resolve_georef, -) +from .._coords import _BAND_DIM_NAMES, _has_no_georef_marker +from .._coords import require_transform_for_georeferenced as _require_transform_for_georeferenced +from .._coords import resolve_georef as _resolve_georef from .._crs import _validate_crs_arg, _validate_crs_fallback, _wkt_to_epsg -from .._geotags import GeoTransform, RASTER_PIXEL_IS_AREA +from .._geotags import RASTER_PIXEL_IS_AREA, GeoTransform from .._nodata import NodataLifecycle as _NL -from .._runtime import ( - GeoTIFFFallbackWarning, - _geotiff_strict_mode, - _gpu_fallback_warning_message, - _resolve_spatial_coords, -) -from .._validation import ( - _validate_3d_writer_dims, - _validate_no_rotated_affine, - _validate_nodata_arg, - _validate_tile_size_arg, - _validate_writer_spatial_shape, - validate_write_metadata, -) +from .._runtime import (GeoTIFFFallbackWarning, _geotiff_strict_mode, _gpu_fallback_warning_message, + _resolve_spatial_coords) +from .._validation import (_validate_3d_writer_dims, _validate_no_rotated_affine, + _validate_nodata_arg, _validate_tile_size_arg, + _validate_writer_spatial_shape, validate_write_metadata) from .._writer import write from .gpu import write_geotiff_gpu @@ -785,6 +767,7 @@ def to_geotiff(data: xr.DataArray | np.ndarray, f"compression_level={compression_level} out of " f"range for {compression} (valid: {lo}-{hi})") from .._writer import write_streaming + # Issue #1929: refuse to write an unvalidatable CRS string # into GTCitationGeoKey unless the caller opts in. ``epsg`` # is set when ``_wkt_to_epsg`` succeeded; only the fallback @@ -1285,4 +1268,3 @@ def _write_vrt_tiled(data, vrt_path, *, crs=None, nodata=None, # Write VRT index with relative paths from .._vrt import write_vrt as _write_vrt_fn _write_vrt_fn(vrt_path, tile_paths, relative=True, nodata=nodata) - diff --git a/xrspatial/geotiff/_writers/gpu.py b/xrspatial/geotiff/_writers/gpu.py index cb6cff32c..60a4ccc50 100644 --- a/xrspatial/geotiff/_writers/gpu.py +++ b/xrspatial/geotiff/_writers/gpu.py @@ -17,29 +17,19 @@ if TYPE_CHECKING: from typing import BinaryIO -from .._attrs import ( - _EXPERIMENTAL_CODECS, - _extract_rich_tags, - _resolve_nodata_attr, - _should_restore_nan_sentinel, -) -from .._coords import ( - _BAND_DIM_NAMES, - _has_no_georef_marker, - require_transform_for_georeferenced as _require_transform_for_georeferenced, - resolve_georef as _resolve_georef, -) + import cupy + +from .._attrs import (_EXPERIMENTAL_CODECS, _extract_rich_tags, _resolve_nodata_attr, + _should_restore_nan_sentinel) +from .._coords import _BAND_DIM_NAMES, _has_no_georef_marker +from .._coords import require_transform_for_georeferenced as _require_transform_for_georeferenced +from .._coords import resolve_georef as _resolve_georef from .._crs import _validate_crs_arg, _validate_crs_fallback, _wkt_to_epsg from .._nodata import NodataLifecycle as _NL from .._runtime import GeoTIFFFallbackWarning, _resolve_spatial_coords -from .._validation import ( - _validate_3d_writer_dims, - _validate_no_rotated_affine, - _validate_nodata_arg, - _validate_tile_size_arg, - _validate_writer_spatial_shape, - validate_write_metadata, -) +from .._validation import (_validate_3d_writer_dims, _validate_no_rotated_affine, + _validate_nodata_arg, _validate_tile_size_arg, + _validate_writer_spatial_shape, validate_write_metadata) def _compute_gpu_samples_hint(data) -> int: @@ -433,10 +423,7 @@ def write_geotiff_gpu(data: xr.DataArray | cupy.ndarray | np.ndarray, raise ImportError("cupy is required for GPU writes") from .._gpu_decode import gpu_compress_tiles, make_overview_gpu - from .._writer import ( - _compression_tag, _assemble_tiff, _write_bytes, - normalize_predictor, - ) + from .._writer import _assemble_tiff, _compression_tag, _write_bytes, normalize_predictor # Extract array and metadata geo_transform = None @@ -610,6 +597,7 @@ def _gpu_compress_to_part(gpu_arr, w, h, spp): if cog: if overview_levels is None: from .._overview import _MAX_OVERVIEW_LEVELS + # Auto-generated lists hold actual decimation factors (2, # 4, 8, ...) so the loop below treats auto-generated and # user-supplied lists identically (issue #1766). @@ -705,5 +693,3 @@ def _gpu_compress_to_part(gpu_arr, w, h, spp): _write_bytes(file_bytes, path) return path - - diff --git a/xrspatial/geotiff/_writers/vrt.py b/xrspatial/geotiff/_writers/vrt.py index 371bb3b48..0b0b81e6a 100644 --- a/xrspatial/geotiff/_writers/vrt.py +++ b/xrspatial/geotiff/_writers/vrt.py @@ -10,11 +10,8 @@ import warnings from .._crs import _resolve_crs_to_wkt -from .._runtime import ( - _CRS_WKT_DEPRECATED_SENTINEL, - _VRT_PATH_DEPRECATED_SENTINEL, - _VRT_PATH_MISSING_SENTINEL, -) +from .._runtime import (_CRS_WKT_DEPRECATED_SENTINEL, _VRT_PATH_DEPRECATED_SENTINEL, + _VRT_PATH_MISSING_SENTINEL) from .._validation import _validate_nodata_arg diff --git a/xrspatial/geotiff/tests/bench_vs_rioxarray.py b/xrspatial/geotiff/tests/bench_vs_rioxarray.py index 3ecd67349..4d01349e2 100644 --- a/xrspatial/geotiff/tests/bench_vs_rioxarray.py +++ b/xrspatial/geotiff/tests/bench_vs_rioxarray.py @@ -12,6 +12,7 @@ # Helpers # --------------------------------------------------------------------------- + def _timer(fn, warmup=1, runs=5): """Time a callable, returning (median_seconds, result_from_last_call).""" for _ in range(warmup): @@ -36,6 +37,7 @@ def _fmt_ms(seconds): def check_consistency(path): """Compare pixel values and geo metadata between the two readers.""" import rioxarray # noqa: F401 + from xrspatial.geotiff import open_geotiff rio_da = xr.open_dataarray(path, engine='rasterio') @@ -96,6 +98,7 @@ def check_consistency(path): def bench_read(path, runs=10): """Benchmark read performance.""" import rioxarray # noqa: F401 + from xrspatial.geotiff import open_geotiff def rio_read(): @@ -120,8 +123,8 @@ def our_read(): def bench_write(shape=(512, 512), compression='deflate', runs=5): """Benchmark write performance.""" import rioxarray # noqa: F401 + from xrspatial.geotiff import to_geotiff - from xrspatial.geotiff._geotags import GeoTransform rng = np.random.RandomState(42) arr = rng.rand(*shape).astype(np.float32) @@ -166,6 +169,7 @@ def our_write(): def bench_round_trip(shape=(256, 256), compression='deflate'): """Write with our module, read back with rioxarray, and vice versa.""" import rioxarray # noqa: F401 + from xrspatial.geotiff import open_geotiff, to_geotiff rng = np.random.RandomState(99) @@ -236,7 +240,7 @@ def main(): print(f" pixels: max_diff={c['max_pixel_diff']:.6f} " f"mean_diff={c['mean_pixel_diff']:.6f} exact={c['pct_exact_match']:.1f}%") print(f" EPSG: rio={c['epsg_rio']} ours={c['epsg_ours']} match={c['epsg_match']}") - print(f" coords: y_max_diff={c['y_max_diff']:.6f} x_max_diff={c['x_max_diff']:.6f}") + print(f" coords: y_max_diff={c['y_max_diff']:.6f} x_max_diff={c['x_max_diff']:.6f}") # noqa: E501 # --- Read performance --- print("\n--- Read Performance (median of 10 runs) ---\n") @@ -253,7 +257,7 @@ def main(): # --- Write performance --- print("\n--- Write Performance (512x512 float32, median of 5 runs) ---\n") - print(f" {'Compression':<12} {'rioxarray':>12} {'xrspatial':>12} {'ratio':>8} {'size rio':>10} {'size ours':>10}") + print(f" {'Compression':<12} {'rioxarray':>12} {'xrspatial':>12} {'ratio':>8} {'size rio':>10} {'size ours':>10}") # noqa: E501 print(f" {'-'*12} {'-'*12} {'-'*12} {'-'*8} {'-'*10} {'-'*10}") for comp in ['none', 'deflate', 'lzw']: rio_t, our_t, rio_sz, our_sz = bench_write((512, 512), comp, runs=5) @@ -263,7 +267,7 @@ def main(): # --- Write performance (larger) --- print("\n--- Write Performance (2048x2048 float32, median of 3 runs) ---\n") - print(f" {'Compression':<12} {'rioxarray':>12} {'xrspatial':>12} {'ratio':>8} {'size rio':>10} {'size ours':>10}") + print(f" {'Compression':<12} {'rioxarray':>12} {'xrspatial':>12} {'ratio':>8} {'size rio':>10} {'size ours':>10}") # noqa: E501 print(f" {'-'*12} {'-'*12} {'-'*12} {'-'*8} {'-'*10} {'-'*10}") for comp in ['none', 'deflate']: rio_t, our_t, rio_sz, our_sz = bench_write((2048, 2048), comp, runs=3) @@ -289,7 +293,7 @@ def main(): ] print("\n--- Real-World Files: Consistency & Read Performance ---\n") - print(f" {'File':<52} {'Format':<20} {'Shape':>12} {'Exact%':>7} {'rio':>9} {'ours':>9} {'ratio':>7}") + print(f" {'File':<52} {'Format':<20} {'Shape':>12} {'Exact%':>7} {'rio':>9} {'ours':>9} {'ratio':>7}") # noqa: E501 print(f" {'-'*52} {'-'*20} {'-'*12} {'-'*7} {'-'*9} {'-'*9} {'-'*7}") for fname, desc in rtxpy_files: diff --git a/xrspatial/geotiff/tests/golden_corpus/_marks.py b/xrspatial/geotiff/tests/golden_corpus/_marks.py index 7ec3cc64f..ce1396e69 100644 --- a/xrspatial/geotiff/tests/golden_corpus/_marks.py +++ b/xrspatial/geotiff/tests/golden_corpus/_marks.py @@ -36,7 +36,6 @@ def _build_param(entry): import pytest - _FAST_TAG = "fast" diff --git a/xrspatial/geotiff/tests/golden_corpus/_oracle.py b/xrspatial/geotiff/tests/golden_corpus/_oracle.py index 1fd1faac6..2603ee6c3 100644 --- a/xrspatial/geotiff/tests/golden_corpus/_oracle.py +++ b/xrspatial/geotiff/tests/golden_corpus/_oracle.py @@ -29,11 +29,11 @@ import numpy as np import xarray as xr - # --------------------------------------------------------------------------- # Rasterio lazy import # --------------------------------------------------------------------------- + def _require_rasterio(): """Import rasterio lazily so the module is importable in environments without it; tests using the oracle call ``pytest.importorskip('rasterio')`` diff --git a/xrspatial/geotiff/tests/golden_corpus/generate.py b/xrspatial/geotiff/tests/golden_corpus/generate.py index d95b722d9..9263f153b 100644 --- a/xrspatial/geotiff/tests/golden_corpus/generate.py +++ b/xrspatial/geotiff/tests/golden_corpus/generate.py @@ -48,7 +48,6 @@ import numpy as np - HERE = pathlib.Path(__file__).resolve().parent MANIFEST_PATH = HERE / "manifest.yaml" DEFAULT_OUTPUT_DIR = HERE / "fixtures" diff --git a/xrspatial/geotiff/tests/golden_corpus/test_corpus_determinism.py b/xrspatial/geotiff/tests/golden_corpus/test_corpus_determinism.py index a29f7c216..abb15a102 100644 --- a/xrspatial/geotiff/tests/golden_corpus/test_corpus_determinism.py +++ b/xrspatial/geotiff/tests/golden_corpus/test_corpus_determinism.py @@ -42,7 +42,6 @@ from xrspatial.geotiff.tests.golden_corpus import generate # noqa: E402 - FIXTURES_DIR = ( pathlib.Path(__file__).resolve().parent / "fixtures" ) diff --git a/xrspatial/geotiff/tests/golden_corpus/test_nodata_sentinels.py b/xrspatial/geotiff/tests/golden_corpus/test_nodata_sentinels.py index 585d37766..0c35f0810 100644 --- a/xrspatial/geotiff/tests/golden_corpus/test_nodata_sentinels.py +++ b/xrspatial/geotiff/tests/golden_corpus/test_nodata_sentinels.py @@ -38,10 +38,7 @@ rasterio = pytest.importorskip('rasterio') -from xrspatial.geotiff.tests.golden_corpus._oracle import ( # noqa: E402 - compare_to_oracle, -) - +from xrspatial.geotiff.tests.golden_corpus._oracle import compare_to_oracle # noqa: E402 FIXTURE_DIR = Path(__file__).resolve().parent / 'fixtures' diff --git a/xrspatial/geotiff/tests/golden_corpus/test_oracle.py b/xrspatial/geotiff/tests/golden_corpus/test_oracle.py index 9336d6b4b..13d6ce5c2 100644 --- a/xrspatial/geotiff/tests/golden_corpus/test_oracle.py +++ b/xrspatial/geotiff/tests/golden_corpus/test_oracle.py @@ -21,15 +21,13 @@ from rasterio.transform import Affine, from_origin # noqa: E402 -from xrspatial.geotiff.tests.golden_corpus._oracle import ( # noqa: E402 - compare_to_oracle, -) - +from xrspatial.geotiff.tests.golden_corpus._oracle import compare_to_oracle # noqa: E402 # --------------------------------------------------------------------------- # Fixture builders # --------------------------------------------------------------------------- + def _write_tiff( path: Path, data: np.ndarray, @@ -806,9 +804,7 @@ def test_masked_nodata_out_of_range_sentinel_does_not_mask() -> None: The test calls the helper directly with synthesised inputs to confirm the guard fires. """ - from xrspatial.geotiff.tests.golden_corpus._oracle import ( - _normalise_for_masked_nodata, - ) + from xrspatial.geotiff.tests.golden_corpus._oracle import _normalise_for_masked_nodata ref_pixels = np.array( [[1, 2, 65535], [4, 5, 6]], dtype=np.uint16, @@ -986,9 +982,7 @@ def test_normalise_axis_order_helper_directly() -> None: A genuine 3-D mismatch (e.g. different band counts on either side) falls through unchanged. """ - from xrspatial.geotiff.tests.golden_corpus._oracle import ( - _normalise_axis_order, - ) + from xrspatial.geotiff.tests.golden_corpus._oracle import _normalise_axis_order a2 = np.arange(12).reshape(3, 4) a3_lead_single = a2[np.newaxis] # (1, 3, 4) diff --git a/xrspatial/geotiff/tests/test_accessor_io.py b/xrspatial/geotiff/tests/test_accessor_io.py index 5166da18b..23ef66d79 100644 --- a/xrspatial/geotiff/tests/test_accessor_io.py +++ b/xrspatial/geotiff/tests/test_accessor_io.py @@ -8,11 +8,11 @@ import xrspatial # noqa: F401 -- registers .xrs accessor from xrspatial.geotiff import open_geotiff, to_geotiff - # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- + def _make_da(height=8, width=10, crs=4326, name='elevation'): """Build a georeferenced DataArray for testing.""" arr = np.arange(height * width, dtype=np.float32).reshape(height, width) diff --git a/xrspatial/geotiff/tests/test_accuracy_1081.py b/xrspatial/geotiff/tests/test_accuracy_1081.py index 626119eb8..60eb5862c 100644 --- a/xrspatial/geotiff/tests/test_accuracy_1081.py +++ b/xrspatial/geotiff/tests/test_accuracy_1081.py @@ -6,20 +6,13 @@ """ from __future__ import annotations -import struct - import numpy as np import pytest import xarray as xr from xrspatial.geotiff import open_geotiff, to_geotiff -from xrspatial.geotiff._geotags import ( - RASTER_PIXEL_IS_POINT, - TAG_GEO_ASCII_PARAMS, - TAG_GEO_KEY_DIRECTORY, - extract_geo_info, -) -from xrspatial.geotiff._header import parse_header, parse_all_ifds +from xrspatial.geotiff._geotags import RASTER_PIXEL_IS_POINT, TAG_GEO_ASCII_PARAMS, extract_geo_info +from xrspatial.geotiff._header import parse_all_ifds, parse_header from xrspatial.geotiff._reader import read_to_array from xrspatial.geotiff._writer import write diff --git a/xrspatial/geotiff/tests/test_allow_rotated_geotiff_2115.py b/xrspatial/geotiff/tests/test_allow_rotated_geotiff_2115.py index 127ea580b..a78ccbf6b 100644 --- a/xrspatial/geotiff/tests/test_allow_rotated_geotiff_2115.py +++ b/xrspatial/geotiff/tests/test_allow_rotated_geotiff_2115.py @@ -26,11 +26,7 @@ from xrspatial.geotiff import open_geotiff from xrspatial.geotiff._errors import RotatedTransformError -from xrspatial.geotiff._geotags import ( - GeoTransform, - TAG_MODEL_TRANSFORMATION, - _extract_transform, -) +from xrspatial.geotiff._geotags import TAG_MODEL_TRANSFORMATION, GeoTransform, _extract_transform from xrspatial.geotiff._header import IFD, IFDEntry diff --git a/xrspatial/geotiff/tests/test_allow_rotated_no_crs_2122.py b/xrspatial/geotiff/tests/test_allow_rotated_no_crs_2122.py index eeed17afa..a51575f18 100644 --- a/xrspatial/geotiff/tests/test_allow_rotated_no_crs_2122.py +++ b/xrspatial/geotiff/tests/test_allow_rotated_no_crs_2122.py @@ -24,7 +24,6 @@ from xrspatial.geotiff import open_geotiff from xrspatial.geotiff._geotags import _NO_GEOREF_KEY, TAG_MODEL_TRANSFORMATION - # Rotated 4x4 ModelTransformation: pixel_width 1.0, b=0.1 (column-axis # rotation), pixel_height -1.0, origin (100, 200). Identical structure # to the fixture in ``test_allow_rotated_geotiff_2115.py`` but with the @@ -43,7 +42,7 @@ 1, 1, 0, 3, 1024, 0, 1, 2, # GTModelTypeGeoKey = Geographic 1025, 0, 1, 1, # GTRasterTypeGeoKey = Area - 2048, 0, 1, 4326, # GeographicTypeGeoKey + 2048, 0, 1, 4326, # GeographicTypeGeoKey ) diff --git a/xrspatial/geotiff/tests/test_ambiguous_metadata_hooks_1987.py b/xrspatial/geotiff/tests/test_ambiguous_metadata_hooks_1987.py index 2790b2c33..cae7b4401 100644 --- a/xrspatial/geotiff/tests/test_ambiguous_metadata_hooks_1987.py +++ b/xrspatial/geotiff/tests/test_ambiguous_metadata_hooks_1987.py @@ -18,27 +18,18 @@ import pytest -from xrspatial.geotiff._errors import ( - ConflictingCRSError, - ConflictingNodataError, - GeoTIFFAmbiguousMetadataError, - InvalidCRSCodeError, - MixedBandMetadataError, - NonUniformCoordsError, - RotatedTransformError, - UnparseableCRSError, -) from xrspatial.geotiff import _validation as _validation_mod -from xrspatial.geotiff._validation import ( - _registered_read_metadata_checks, - _registered_write_metadata_checks, - register_read_metadata_check, - register_write_metadata_check, - unregister_read_metadata_check, - unregister_write_metadata_check, - validate_read_metadata, - validate_write_metadata, -) +from xrspatial.geotiff._errors import (ConflictingCRSError, ConflictingNodataError, + GeoTIFFAmbiguousMetadataError, InvalidCRSCodeError, + MixedBandMetadataError, NonUniformCoordsError, + RotatedTransformError, UnparseableCRSError) +from xrspatial.geotiff._validation import (_registered_read_metadata_checks, + _registered_write_metadata_checks, + register_read_metadata_check, + register_write_metadata_check, + unregister_read_metadata_check, + unregister_write_metadata_check, validate_read_metadata, + validate_write_metadata) @pytest.fixture(autouse=True) diff --git a/xrspatial/geotiff/tests/test_apply_nodata_mask_gpu_with_presence_removed_2208.py b/xrspatial/geotiff/tests/test_apply_nodata_mask_gpu_with_presence_removed_2208.py index b50ddeef6..9a4b27207 100644 --- a/xrspatial/geotiff/tests/test_apply_nodata_mask_gpu_with_presence_removed_2208.py +++ b/xrspatial/geotiff/tests/test_apply_nodata_mask_gpu_with_presence_removed_2208.py @@ -22,9 +22,8 @@ def test_apply_nodata_mask_gpu_with_presence_not_importable_2208(): # #2207 routed all GPU eager sites through _finalize_eager_read; # the helper had zero remaining callers. with pytest.raises(ImportError): - from xrspatial.geotiff._backends._gpu_helpers import ( # noqa: F401 - _apply_nodata_mask_gpu_with_presence, - ) + from xrspatial.geotiff._backends._gpu_helpers import \ + _apply_nodata_mask_gpu_with_presence # noqa: F401 def test_apply_nodata_mask_gpu_still_present_2208(): diff --git a/xrspatial/geotiff/tests/test_assemble_layout_no_bytes_copy_1756.py b/xrspatial/geotiff/tests/test_assemble_layout_no_bytes_copy_1756.py index aa895225a..3c3c6c8c1 100644 --- a/xrspatial/geotiff/tests/test_assemble_layout_no_bytes_copy_1756.py +++ b/xrspatial/geotiff/tests/test_assemble_layout_no_bytes_copy_1756.py @@ -26,13 +26,8 @@ from xrspatial.geotiff import open_geotiff, to_geotiff from xrspatial.geotiff._compression import COMPRESSION_NONE -from xrspatial.geotiff._writer import ( - _assemble_cog_layout, - _assemble_standard_layout, - _assemble_tiff, - _write_stripped, - _write_tiled, -) +from xrspatial.geotiff._writer import (_assemble_cog_layout, _assemble_standard_layout, + _assemble_tiff, _write_stripped, _write_tiled) def _build_parts(arr: np.ndarray): @@ -48,12 +43,10 @@ def test_assemble_standard_layout_returns_bytearray(): # Minimal tag set sufficient for the assembler to lay out the file. from xrspatial.geotiff._dtypes import LONG, SHORT, numpy_to_tiff_dtype - from xrspatial.geotiff._header import ( - TAG_BITS_PER_SAMPLE, TAG_COMPRESSION, TAG_IMAGE_LENGTH, - TAG_IMAGE_WIDTH, TAG_PHOTOMETRIC, TAG_ROWS_PER_STRIP, - TAG_SAMPLE_FORMAT, TAG_SAMPLES_PER_PIXEL, TAG_STRIP_BYTE_COUNTS, - TAG_STRIP_OFFSETS, - ) + from xrspatial.geotiff._header import (TAG_BITS_PER_SAMPLE, TAG_COMPRESSION, TAG_IMAGE_LENGTH, + TAG_IMAGE_WIDTH, TAG_PHOTOMETRIC, TAG_ROWS_PER_STRIP, + TAG_SAMPLE_FORMAT, TAG_SAMPLES_PER_PIXEL, + TAG_STRIP_BYTE_COUNTS, TAG_STRIP_OFFSETS) bps, sf = numpy_to_tiff_dtype(arr.dtype) rel_off, bc, _ = parts[0][3], parts[0][4], parts[0][5] tags = [ @@ -83,12 +76,10 @@ def test_assemble_cog_layout_returns_bytearray(): (arr[:8, :8], 8, 8, rel_off, bc, chunks), # mock overview ] from xrspatial.geotiff._dtypes import LONG, SHORT, numpy_to_tiff_dtype - from xrspatial.geotiff._header import ( - TAG_BITS_PER_SAMPLE, TAG_COMPRESSION, TAG_IMAGE_LENGTH, - TAG_IMAGE_WIDTH, TAG_PHOTOMETRIC, TAG_SAMPLE_FORMAT, - TAG_SAMPLES_PER_PIXEL, TAG_TILE_BYTE_COUNTS, TAG_TILE_LENGTH, - TAG_TILE_OFFSETS, TAG_TILE_WIDTH, - ) + from xrspatial.geotiff._header import (TAG_BITS_PER_SAMPLE, TAG_COMPRESSION, TAG_IMAGE_LENGTH, + TAG_IMAGE_WIDTH, TAG_PHOTOMETRIC, TAG_SAMPLE_FORMAT, + TAG_SAMPLES_PER_PIXEL, TAG_TILE_BYTE_COUNTS, + TAG_TILE_LENGTH, TAG_TILE_OFFSETS, TAG_TILE_WIDTH) bps, sf = numpy_to_tiff_dtype(arr.dtype) def _build_tags(w, h): diff --git a/xrspatial/geotiff/tests/test_attrs_contract_aliases_1984.py b/xrspatial/geotiff/tests/test_attrs_contract_aliases_1984.py index 7f646a0f3..0b15f7d0f 100644 --- a/xrspatial/geotiff/tests/test_attrs_contract_aliases_1984.py +++ b/xrspatial/geotiff/tests/test_attrs_contract_aliases_1984.py @@ -28,7 +28,6 @@ from xrspatial.geotiff import open_geotiff, to_geotiff from xrspatial.geotiff._attrs import _resolve_nodata_attr - # Arbitrary float-castable sentinel that is distinct from any data value # used below. The value itself does not matter; the tests assert it # survives the alias resolver and the round-trip. diff --git a/xrspatial/geotiff/tests/test_attrs_contract_canonical_1984.py b/xrspatial/geotiff/tests/test_attrs_contract_canonical_1984.py index 58d0d36a6..cfc01b28a 100644 --- a/xrspatial/geotiff/tests/test_attrs_contract_canonical_1984.py +++ b/xrspatial/geotiff/tests/test_attrs_contract_canonical_1984.py @@ -59,7 +59,6 @@ from xrspatial.geotiff import open_geotiff, to_geotiff from xrspatial.geotiff._attrs import _ATTRS_CONTRACT_VERSION - _CONTRACT_KEY = '_xrspatial_geotiff_contract' # Every key the canonical tier guarantees round-trip stable. Keep the diff --git a/xrspatial/geotiff/tests/test_attrs_contract_passthrough_1984.py b/xrspatial/geotiff/tests/test_attrs_contract_passthrough_1984.py index 7375033dc..41ef1a7ea 100644 --- a/xrspatial/geotiff/tests/test_attrs_contract_passthrough_1984.py +++ b/xrspatial/geotiff/tests/test_attrs_contract_passthrough_1984.py @@ -45,7 +45,6 @@ from xrspatial.geotiff import open_geotiff, to_geotiff - # Full set of pass-through keys defined by the contract. Contract v2 # (issue #2016) trimmed this set to the three TIFF-tag-derived keys # that actually round-trip via ``_merge_friendly_extra_tags``. @@ -123,8 +122,11 @@ def _roundtrip(tmp_path, da, name='roundtrip.tif'): # by-equality after the round-trip; if the writer ever rescales 8-bit # input to 16-bit (or vice versa), update this fixture rather than # the contract. - ('colormap', 4326, tuple([0] * 256 + [128] * 256 + [255] * 256), - 'reconstructible'), + ( + 'colormap', 4326, + tuple([0] * 256 + [128] * 256 + [255] * 256), + 'reconstructible', + ), ] diff --git a/xrspatial/geotiff/tests/test_attrs_contract_version_1984.py b/xrspatial/geotiff/tests/test_attrs_contract_version_1984.py index c347a29bb..348954302 100644 --- a/xrspatial/geotiff/tests/test_attrs_contract_version_1984.py +++ b/xrspatial/geotiff/tests/test_attrs_contract_version_1984.py @@ -29,8 +29,8 @@ import numpy as np import pytest -from xrspatial.geotiff import open_geotiff, read_vrt from xrspatial.geotiff import _attrs as _attrs_module +from xrspatial.geotiff import open_geotiff, read_vrt from xrspatial.geotiff._attrs import _ATTRS_CONTRACT_VERSION tifffile = pytest.importorskip("tifffile") diff --git a/xrspatial/geotiff/tests/test_attrs_finalization_parity_2211.py b/xrspatial/geotiff/tests/test_attrs_finalization_parity_2211.py index 88aca756e..4db88fcad 100644 --- a/xrspatial/geotiff/tests/test_attrs_finalization_parity_2211.py +++ b/xrspatial/geotiff/tests/test_attrs_finalization_parity_2211.py @@ -44,12 +44,10 @@ import numpy as np import pytest - import xarray as xr from xrspatial.geotiff import open_geotiff, read_vrt, to_geotiff - tifffile = pytest.importorskip("tifffile") @@ -296,6 +294,7 @@ def _open_vrt(path, meta): in :func:`open_geotiff` cannot propagate into this helper. """ import os + from pyproj import CRS height = _FIX_HEIGHT diff --git a/xrspatial/geotiff/tests/test_backend_full_parity_2211.py b/xrspatial/geotiff/tests/test_backend_full_parity_2211.py index f3272eb66..d1e525d57 100644 --- a/xrspatial/geotiff/tests/test_backend_full_parity_2211.py +++ b/xrspatial/geotiff/tests/test_backend_full_parity_2211.py @@ -71,10 +71,7 @@ from xrspatial.geotiff import open_geotiff, write_vrt # noqa: E402 from xrspatial.geotiff.tests.golden_corpus import generate # noqa: E402 -from xrspatial.geotiff.tests.golden_corpus._marks import ( # noqa: E402 - fast_slow_marks_for, -) - +from xrspatial.geotiff.tests.golden_corpus._marks import fast_slow_marks_for # noqa: E402 FIXTURES_DIR = ( pathlib.Path(generate.__file__).resolve().parent / "fixtures" diff --git a/xrspatial/geotiff/tests/test_backend_kwarg_parity_1561.py b/xrspatial/geotiff/tests/test_backend_kwarg_parity_1561.py index 6501375ed..e3f622027 100644 --- a/xrspatial/geotiff/tests/test_backend_kwarg_parity_1561.py +++ b/xrspatial/geotiff/tests/test_backend_kwarg_parity_1561.py @@ -188,7 +188,8 @@ def test_write_geotiff_gpu_rejects_nonzero_max_z_error(tmp_path): def test_write_geotiff_gpu_accepts_streaming_buffer_bytes_as_noop(tmp_path): """``streaming_buffer_bytes`` is accepted for API parity (no-op).""" import cupy - from xrspatial.geotiff import write_geotiff_gpu, open_geotiff + + from xrspatial.geotiff import open_geotiff, write_geotiff_gpu arr = cupy.arange(16, dtype=cupy.float32).reshape(4, 4) da = xr.DataArray(arr, dims=['y', 'x'], @@ -206,6 +207,7 @@ def test_write_geotiff_gpu_accepts_streaming_buffer_bytes_as_noop(tmp_path): def test_to_geotiff_threads_tiled_false_into_gpu_dispatcher(tmp_path): """``to_geotiff(..., gpu=True, tiled=False)`` rejects, not silently flips.""" import cupy + from xrspatial.geotiff import to_geotiff arr = cupy.zeros((2, 2), dtype=cupy.float32) diff --git a/xrspatial/geotiff/tests/test_backend_parity_matrix.py b/xrspatial/geotiff/tests/test_backend_parity_matrix.py index 6de22fb04..e2fbd876e 100644 --- a/xrspatial/geotiff/tests/test_backend_parity_matrix.py +++ b/xrspatial/geotiff/tests/test_backend_parity_matrix.py @@ -65,11 +65,11 @@ from xrspatial.geotiff import open_geotiff, to_geotiff, write_vrt from xrspatial.geotiff._errors import RotatedTransformError - # --------------------------------------------------------------------------- # Environment gating # --------------------------------------------------------------------------- + def _gpu_available() -> bool: """True iff cupy is importable and the CUDA runtime is available. diff --git a/xrspatial/geotiff/tests/test_backend_pixel_parity_matrix_1813.py b/xrspatial/geotiff/tests/test_backend_pixel_parity_matrix_1813.py index 43ffdcde3..4069894c4 100644 --- a/xrspatial/geotiff/tests/test_backend_pixel_parity_matrix_1813.py +++ b/xrspatial/geotiff/tests/test_backend_pixel_parity_matrix_1813.py @@ -21,20 +21,14 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - open_geotiff, - read_geotiff_dask, - read_geotiff_gpu, - read_vrt, - to_geotiff, - write_vrt, -) - +from xrspatial.geotiff import (open_geotiff, read_geotiff_dask, read_geotiff_gpu, read_vrt, + to_geotiff, write_vrt) # --------------------------------------------------------------------------- # Environment gating # --------------------------------------------------------------------------- + def _gpu_available() -> bool: if importlib.util.find_spec("cupy") is None: return False diff --git a/xrspatial/geotiff/tests/test_chunked_gpu_declared_dtype_1909.py b/xrspatial/geotiff/tests/test_chunked_gpu_declared_dtype_1909.py index da6ef86a3..51c45d631 100644 --- a/xrspatial/geotiff/tests/test_chunked_gpu_declared_dtype_1909.py +++ b/xrspatial/geotiff/tests/test_chunked_gpu_declared_dtype_1909.py @@ -45,13 +45,9 @@ def _gpu_available() -> bool: def _parse_for_gds(path: str): """Return ``(ifd, geo_info, header)`` for the GDS entry point.""" + from xrspatial.geotiff._geotags import extract_geo_info_with_overview_inheritance + from xrspatial.geotiff._header import parse_all_ifds, parse_header, select_overview_ifd from xrspatial.geotiff._reader import _FileSource - from xrspatial.geotiff._header import ( - parse_header, parse_all_ifds, select_overview_ifd, - ) - from xrspatial.geotiff._geotags import ( - extract_geo_info_with_overview_inheritance, - ) fs = _FileSource(path) try: diff --git a/xrspatial/geotiff/tests/test_cloud_read_byte_limit_1928.py b/xrspatial/geotiff/tests/test_cloud_read_byte_limit_1928.py index ddae777b8..2c6499917 100644 --- a/xrspatial/geotiff/tests/test_cloud_read_byte_limit_1928.py +++ b/xrspatial/geotiff/tests/test_cloud_read_byte_limit_1928.py @@ -16,17 +16,13 @@ import numpy as np import pytest - fsspec = pytest.importorskip("fsspec") from xrspatial.geotiff import open_geotiff, to_geotiff # noqa: E402 -from xrspatial.geotiff._reader import ( # noqa: E402 - MAX_CLOUD_BYTES_DEFAULT, - CloudSizeLimitError, - _MAX_CLOUD_BYTES_SENTINEL, - _resolve_max_cloud_bytes, - read_to_array, -) +from xrspatial.geotiff._reader import _MAX_CLOUD_BYTES_SENTINEL # noqa: E402 +from xrspatial.geotiff._reader import MAX_CLOUD_BYTES_DEFAULT # noqa: E402 +from xrspatial.geotiff._reader import (CloudSizeLimitError, _resolve_max_cloud_bytes, # noqa: E402 + read_to_array) def _put_in_memory_fs(path: str, payload: bytes) -> None: diff --git a/xrspatial/geotiff/tests/test_cog.py b/xrspatial/geotiff/tests/test_cog.py index e4f71fa44..bab8fa5dc 100644 --- a/xrspatial/geotiff/tests/test_cog.py +++ b/xrspatial/geotiff/tests/test_cog.py @@ -6,9 +6,11 @@ import xarray as xr from xrspatial.geotiff import open_geotiff, to_geotiff -from xrspatial.geotiff._header import parse_header, parse_all_ifds +from xrspatial.geotiff._geotags import GeoTransform +from xrspatial.geotiff._header import parse_all_ifds, parse_header from xrspatial.geotiff._writer import write -from xrspatial.geotiff._geotags import GeoTransform, extract_geo_info + +from .conftest import gpu_available class TestCOGWriter: @@ -277,8 +279,6 @@ def test_to_geotiff_cog_auto_overviews(self, tmp_path): assert len(ifds) >= 2 -from .conftest import gpu_available - _HAS_GPU = gpu_available() @@ -338,6 +338,7 @@ def test_gpu_overview_resampling_nearest(self, tmp_path): def test_gpu_make_overview_values(self): """GPU overview block-reduce matches CPU for simple case.""" import cupy + from xrspatial.geotiff._gpu_decode import make_overview_gpu from xrspatial.geotiff._writer import _make_overview diff --git a/xrspatial/geotiff/tests/test_cog_cubic_int_overview_nodata_1975.py b/xrspatial/geotiff/tests/test_cog_cubic_int_overview_nodata_1975.py index 872b3ba65..2207998d3 100644 --- a/xrspatial/geotiff/tests/test_cog_cubic_int_overview_nodata_1975.py +++ b/xrspatial/geotiff/tests/test_cog_cubic_int_overview_nodata_1975.py @@ -32,11 +32,11 @@ from xrspatial.geotiff import open_geotiff, to_geotiff from xrspatial.geotiff._writer import _block_reduce_2d - # --------------------------------------------------------------------------- # Helper-level: _block_reduce_2d cubic + integer + sentinel # --------------------------------------------------------------------------- + def _make_block_with_nodata_corner(dtype, nodata_value, size=64, corner=16, fill=100): """Return a (size, size) ``dtype`` array with a corner of nodata.""" diff --git a/xrspatial/geotiff/tests/test_cog_cubic_overview_nodata_1623.py b/xrspatial/geotiff/tests/test_cog_cubic_overview_nodata_1623.py index 9441986f7..7ec2728c0 100644 --- a/xrspatial/geotiff/tests/test_cog_cubic_overview_nodata_1623.py +++ b/xrspatial/geotiff/tests/test_cog_cubic_overview_nodata_1623.py @@ -120,9 +120,10 @@ def test_block_reduce_cubic_no_nodata_unchanged(): def test_block_reduce_cubic_nodata_unset_is_zoom(): """nodata=None goes through the original zoom path, no prefilter change.""" pytest.importorskip("scipy") - from xrspatial.geotiff._writer import _block_reduce_2d from scipy.ndimage import zoom + from xrspatial.geotiff._writer import _block_reduce_2d + arr = np.linspace(0.0, 1.0, 64, dtype=np.float32).reshape(8, 8) out = _block_reduce_2d(arr, 'cubic', nodata=None) expected = zoom(arr, 0.5, order=3).astype(arr.dtype) @@ -132,7 +133,7 @@ def test_block_reduce_cubic_nodata_unset_is_zoom(): def test_to_geotiff_cog_cubic_nodata_round_trip(tmp_path): """End-to-end: writing a COG with cubic + nodata produces a clean overview.""" pytest.importorskip("scipy") - from xrspatial.geotiff import to_geotiff, open_geotiff + from xrspatial.geotiff import open_geotiff, to_geotiff arr = _flat_with_corner_nan() da = xr.DataArray(arr, dims=['y', 'x']) @@ -159,7 +160,7 @@ def test_to_geotiff_cog_cubic_nodata_round_trip(tmp_path): def test_to_geotiff_cog_cubic_no_nodata_round_trip(tmp_path): """Regression guard: cubic without nodata still produces the same overview.""" pytest.importorskip("scipy") - from xrspatial.geotiff import to_geotiff, open_geotiff + from xrspatial.geotiff import open_geotiff, to_geotiff arr = np.arange(256, dtype=np.float32).reshape(16, 16) da = xr.DataArray(arr, dims=['y', 'x']) @@ -192,9 +193,10 @@ def test_block_reduce_cubic_inf_nodata_is_masked(): def test_block_reduce_cubic_nan_sentinel_skips_mask(): """nodata=NaN is a no-op (matches the existing nan-pass-through gate).""" pytest.importorskip("scipy") - from xrspatial.geotiff._writer import _block_reduce_2d from scipy.ndimage import zoom + from xrspatial.geotiff._writer import _block_reduce_2d + arr = np.linspace(0.0, 1.0, 64, dtype=np.float32).reshape(8, 8) out = _block_reduce_2d(arr, 'cubic', nodata=np.nan) expected = zoom(arr, 0.5, order=3).astype(arr.dtype) @@ -213,6 +215,7 @@ def test_gpu_block_reduce_cubic_falls_back_to_cpu(): """GPU cubic must route through the CPU helper and return cupy data.""" pytest.importorskip("scipy") import cupy + from xrspatial.geotiff._gpu_decode import _block_reduce_2d_gpu from xrspatial.geotiff._writer import _block_reduce_2d @@ -232,7 +235,8 @@ def test_to_geotiff_cog_cubic_nodata_gpu_round_trip(tmp_path): """End-to-end GPU writer: cubic + nodata produces a clean overview.""" pytest.importorskip("scipy") import cupy - from xrspatial.geotiff import to_geotiff, open_geotiff + + from xrspatial.geotiff import open_geotiff, to_geotiff arr = _flat_with_corner_nan() da = xr.DataArray(cupy.asarray(arr), dims=['y', 'x']) @@ -258,7 +262,8 @@ def test_gpu_cpu_cubic_overview_bytes_match(tmp_path): """CPU and GPU writers produce the same cubic overview pixels.""" pytest.importorskip("scipy") import cupy - from xrspatial.geotiff import to_geotiff, open_geotiff + + from xrspatial.geotiff import open_geotiff, to_geotiff arr = _flat_with_corner_nan() cpu_da = xr.DataArray(arr, dims=['y', 'x']) diff --git a/xrspatial/geotiff/tests/test_cog_http_close_on_error_1816.py b/xrspatial/geotiff/tests/test_cog_http_close_on_error_1816.py index 2fae4b171..e6bc4e0ca 100644 --- a/xrspatial/geotiff/tests/test_cog_http_close_on_error_1816.py +++ b/xrspatial/geotiff/tests/test_cog_http_close_on_error_1816.py @@ -26,11 +26,11 @@ from xrspatial.geotiff._reader import _read_cog_http from xrspatial.geotiff._writer import write - # --------------------------------------------------------------------------- # Loopback HTTP server with Range support (mirrors #1695 pattern). # --------------------------------------------------------------------------- + class _RangeHandler(http.server.BaseHTTPRequestHandler): payload: bytes = b'' diff --git a/xrspatial/geotiff/tests/test_cog_http_concurrent.py b/xrspatial/geotiff/tests/test_cog_http_concurrent.py index 57bf3d02f..8a1244e3b 100644 --- a/xrspatial/geotiff/tests/test_cog_http_concurrent.py +++ b/xrspatial/geotiff/tests/test_cog_http_concurrent.py @@ -9,18 +9,14 @@ import numpy as np import pytest -from xrspatial.geotiff._reader import ( - _HTTPSource, - _read_cog_http, - read_to_array, -) +from xrspatial.geotiff._reader import _HTTPSource, _read_cog_http, read_to_array from xrspatial.geotiff._writer import write - # --------------------------------------------------------------------------- # read_ranges: ordering and concurrency # --------------------------------------------------------------------------- + class _FakeHTTPSource(_HTTPSource): """_HTTPSource that fakes read_range with a configurable sleep. diff --git a/xrspatial/geotiff/tests/test_cog_http_parallel_decode_2026_05_15.py b/xrspatial/geotiff/tests/test_cog_http_parallel_decode_2026_05_15.py index 2a3d79073..ae70303d3 100644 --- a/xrspatial/geotiff/tests/test_cog_http_parallel_decode_2026_05_15.py +++ b/xrspatial/geotiff/tests/test_cog_http_parallel_decode_2026_05_15.py @@ -25,25 +25,19 @@ import http.server import socketserver import threading -from concurrent.futures import ThreadPoolExecutor as _RealPool import numpy as np import pytest -from xrspatial.geotiff._reader import ( - _HTTPSource, - _fetch_decode_cog_http_tiles, - _parse_cog_http_meta, - read_to_array, -) +from xrspatial.geotiff._reader import read_to_array from xrspatial.geotiff._writer import write - # --------------------------------------------------------------------------- # Local HTTP server fixture (range-aware) -- copied minimal pattern from # test_cog_http_concurrent.py. # --------------------------------------------------------------------------- + class _RangeHandler(http.server.BaseHTTPRequestHandler): payload: bytes = b'' @@ -196,7 +190,6 @@ def test_serial_path_below_threshold(monkeypatch, cog_http_url_small_tiles): decode-sized pools. """ import concurrent.futures as _cf - import os pool_made = [] orig = _cf.ThreadPoolExecutor diff --git a/xrspatial/geotiff/tests/test_cog_int_overview_nodata_2026_05_12.py b/xrspatial/geotiff/tests/test_cog_int_overview_nodata_2026_05_12.py index fcf1a34aa..c8a62c4f5 100644 --- a/xrspatial/geotiff/tests/test_cog_int_overview_nodata_2026_05_12.py +++ b/xrspatial/geotiff/tests/test_cog_int_overview_nodata_2026_05_12.py @@ -192,7 +192,7 @@ def test_cpu_int_cog_overview_not_poisoned(_int_cog_inputs, method): sentinel, 100, 100) cast back to uint16. The reader can't mask them because they don't equal 65535. """ - from xrspatial.geotiff import to_geotiff, open_geotiff + from xrspatial.geotiff import open_geotiff, to_geotiff da, tmp_path = _int_cog_inputs path = str(tmp_path / f'int_overview_{method}_2026_05_12.tif') @@ -210,7 +210,7 @@ def test_cpu_int_cog_overview_not_poisoned(_int_cog_inputs, method): def test_cpu_int_cog_overview_3band_not_poisoned(tmp_path): """3-band integer COG: same fix applies via the 3D _make_overview branch.""" - from xrspatial.geotiff import to_geotiff, open_geotiff + from xrspatial.geotiff import open_geotiff, to_geotiff H, W = 256, 256 data = np.full((H, W, 3), 100, dtype=np.uint16) @@ -239,7 +239,7 @@ def test_cpu_int_cog_overview_3band_not_poisoned(tmp_path): def test_cpu_int_cog_no_nodata_unchanged(tmp_path): """No nodata kwarg: integer overview path stays as it was.""" - from xrspatial.geotiff import to_geotiff, open_geotiff + from xrspatial.geotiff import open_geotiff, to_geotiff H, W = 256, 256 data = np.full((H, W), 100, dtype=np.uint16) @@ -279,6 +279,7 @@ def test_cpu_int_cog_no_nodata_unchanged(tmp_path): def test_gpu_block_reduce_int_sentinel_masked(method, dtype, sentinel): """GPU mirror of the CPU integer sentinel-mask fix.""" import cupy + from xrspatial.geotiff._gpu_decode import _block_reduce_2d_gpu arr = _int_block_partial_sentinel(sentinel, dtype) @@ -304,8 +305,9 @@ def test_gpu_cpu_int_overview_byte_match(method): sentinels -- two backends disagreeing on identical input. """ import cupy - from xrspatial.geotiff._writer import _block_reduce_2d + from xrspatial.geotiff._gpu_decode import _block_reduce_2d_gpu + from xrspatial.geotiff._writer import _block_reduce_2d arr = _int_block_partial_sentinel(-9999, np.int16) cpu_out = _block_reduce_2d(arr, method, nodata=-9999) diff --git a/xrspatial/geotiff/tests/test_cog_overview_ceil_2105.py b/xrspatial/geotiff/tests/test_cog_overview_ceil_2105.py index 48d0fc4d4..5ee9100f6 100644 --- a/xrspatial/geotiff/tests/test_cog_overview_ceil_2105.py +++ b/xrspatial/geotiff/tests/test_cog_overview_ceil_2105.py @@ -307,6 +307,7 @@ def test_even_input_matches_legacy_2x2_behaviour(method): @pytest.mark.parametrize("shape", [(5, 5), (5, 4), (4, 5), (7, 3)]) def test_gpu_block_reduce_matches_cpu_on_odd_shapes(method, shape): import cupy + from xrspatial.geotiff._gpu_decode import _block_reduce_2d_gpu rng = np.random.default_rng(2105) @@ -320,6 +321,7 @@ def test_gpu_block_reduce_matches_cpu_on_odd_shapes(method, shape): @_gpu_only def test_gpu_block_reduce_int_5x5_with_nodata(): import cupy + from xrspatial.geotiff._gpu_decode import _block_reduce_2d_gpu sentinel = -9999 diff --git a/xrspatial/geotiff/tests/test_cog_overview_nodata_1613.py b/xrspatial/geotiff/tests/test_cog_overview_nodata_1613.py index 960893926..ff38a1288 100644 --- a/xrspatial/geotiff/tests/test_cog_overview_nodata_1613.py +++ b/xrspatial/geotiff/tests/test_cog_overview_nodata_1613.py @@ -59,7 +59,7 @@ def _arr_with_full_nan_block(): def test_cpu_cog_overview_mean_ignores_sentinel(tmp_path): """CPU writer: overview 'mean' must skip sentinel pixels (issue #1613).""" - from xrspatial.geotiff import to_geotiff, open_geotiff + from xrspatial.geotiff import open_geotiff, to_geotiff arr = _arr_with_partial_nan() da = xr.DataArray(arr, dims=['y', 'x']) @@ -75,7 +75,7 @@ def test_cpu_cog_overview_mean_ignores_sentinel(tmp_path): def test_cpu_cog_overview_mean_partial_block(tmp_path): """CPU writer: partial-NaN 2x2 block averages over the finite cells only.""" - from xrspatial.geotiff import to_geotiff, open_geotiff + from xrspatial.geotiff import open_geotiff, to_geotiff arr = _arr_with_full_nan_block() da = xr.DataArray(arr, dims=['y', 'x']) @@ -108,7 +108,7 @@ def test_cpu_cog_overview_mean_partial_block(tmp_path): def test_cpu_cog_overview_aggregations_ignore_sentinel( tmp_path, method, expected): """min/max/median overview reductions must also skip the sentinel.""" - from xrspatial.geotiff import to_geotiff, open_geotiff + from xrspatial.geotiff import open_geotiff, to_geotiff arr = _arr_with_partial_nan() da = xr.DataArray(arr, dims=['y', 'x']) @@ -123,7 +123,7 @@ def test_cpu_cog_overview_aggregations_ignore_sentinel( def test_cpu_cog_overview_mean_no_nodata_passes(tmp_path): """When nodata is unset the reducer behaves as before.""" - from xrspatial.geotiff import to_geotiff, open_geotiff + from xrspatial.geotiff import open_geotiff, to_geotiff arr = np.arange(16, dtype=np.float32).reshape(4, 4) da = xr.DataArray(arr, dims=['y', 'x']) @@ -212,7 +212,8 @@ def test_block_reduce_2d_all_nan_block_does_not_warn(): def test_gpu_cog_overview_mean_ignores_sentinel(tmp_path): """GPU writer: overview 'mean' must skip sentinel pixels (issue #1613).""" import cupy - from xrspatial.geotiff import to_geotiff, open_geotiff + + from xrspatial.geotiff import open_geotiff, to_geotiff arr_cpu = _arr_with_partial_nan() arr_gpu = cupy.asarray(arr_cpu) @@ -232,6 +233,7 @@ def test_gpu_cog_overview_mean_ignores_sentinel(tmp_path): def test_gpu_block_reduce_nodata_kwarg_directly(): """Exercise the GPU helper directly so a regression is caught fast.""" import cupy + from xrspatial.geotiff._gpu_decode import _block_reduce_2d_gpu arr_cpu = _arr_with_partial_nan() @@ -250,6 +252,7 @@ def test_gpu_block_reduce_nodata_kwarg_directly(): def test_gpu_block_reduce_inf_nodata_is_masked(): """GPU helper mirrors the CPU isnan-only gate for nodata=inf.""" import cupy + from xrspatial.geotiff._gpu_decode import _block_reduce_2d_gpu arr_cpu = np.array([ @@ -269,7 +272,8 @@ def test_gpu_block_reduce_inf_nodata_is_masked(): def test_gpu_cog_overview_matches_cpu(tmp_path): """CPU and GPU overview pyramids must agree on nodata-masked data.""" import cupy - from xrspatial.geotiff import to_geotiff, open_geotiff + + from xrspatial.geotiff import open_geotiff, to_geotiff arr = _arr_with_partial_nan() diff --git a/xrspatial/geotiff/tests/test_compression.py b/xrspatial/geotiff/tests/test_compression.py index a296ab883..b8f5bc5d1 100644 --- a/xrspatial/geotiff/tests/test_compression.py +++ b/xrspatial/geotiff/tests/test_compression.py @@ -1,24 +1,13 @@ """Tests for compression codecs.""" from __future__ import annotations -import zlib - import numpy as np import pytest -from xrspatial.geotiff._compression import ( - COMPRESSION_DEFLATE, - COMPRESSION_LZW, - COMPRESSION_NONE, - compress, - decompress, - deflate_compress, - deflate_decompress, - lzw_compress, - lzw_decompress, - predictor_decode, - predictor_encode, -) +from xrspatial.geotiff._compression import (COMPRESSION_DEFLATE, COMPRESSION_LZW, COMPRESSION_NONE, + compress, decompress, deflate_compress, + deflate_decompress, lzw_compress, lzw_decompress, + predictor_decode, predictor_encode) class TestDeflate: diff --git a/xrspatial/geotiff/tests/test_compression_level.py b/xrspatial/geotiff/tests/test_compression_level.py index 6698a0110..a0b01cffb 100644 --- a/xrspatial/geotiff/tests/test_compression_level.py +++ b/xrspatial/geotiff/tests/test_compression_level.py @@ -9,11 +9,11 @@ from xrspatial.geotiff import open_geotiff, to_geotiff - # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- + def _make_da(seed: int = 0, shape: tuple = (64, 64)) -> xr.DataArray: """Return a small float32 DataArray with reproducible content.""" rng = np.random.default_rng(seed) diff --git a/xrspatial/geotiff/tests/test_conflicting_crs_write_1987.py b/xrspatial/geotiff/tests/test_conflicting_crs_write_1987.py index 7a30ad3df..79dcc3855 100644 --- a/xrspatial/geotiff/tests/test_conflicting_crs_write_1987.py +++ b/xrspatial/geotiff/tests/test_conflicting_crs_write_1987.py @@ -35,15 +35,9 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - ConflictingCRSError, - GeoTIFFAmbiguousMetadataError, - to_geotiff, -) -from xrspatial.geotiff._validation import ( - _check_write_conflicting_crs, - _registered_write_metadata_checks, -) +from xrspatial.geotiff import ConflictingCRSError, GeoTIFFAmbiguousMetadataError, to_geotiff +from xrspatial.geotiff._validation import (_check_write_conflicting_crs, + _registered_write_metadata_checks) pyproj = pytest.importorskip("pyproj") diff --git a/xrspatial/geotiff/tests/test_coords_1813.py b/xrspatial/geotiff/tests/test_coords_1813.py index 44438e169..090f3c43e 100644 --- a/xrspatial/geotiff/tests/test_coords_1813.py +++ b/xrspatial/geotiff/tests/test_coords_1813.py @@ -11,16 +11,9 @@ import numpy as np import pytest -from xrspatial.geotiff._coords import ( - coords_from_geo_info, - coords_from_pixel_geometry, - transform_tuple_from_pixel_geometry, -) -from xrspatial.geotiff._geotags import ( - GeoTransform, - RASTER_PIXEL_IS_AREA, - RASTER_PIXEL_IS_POINT, -) +from xrspatial.geotiff._coords import (coords_from_geo_info, coords_from_pixel_geometry, + transform_tuple_from_pixel_geometry) +from xrspatial.geotiff._geotags import RASTER_PIXEL_IS_AREA, RASTER_PIXEL_IS_POINT, GeoTransform class TestCoordsFromPixelGeometry: diff --git a/xrspatial/geotiff/tests/test_crs_fail_closed_1929.py b/xrspatial/geotiff/tests/test_crs_fail_closed_1929.py index 114b8f961..56d8a54ca 100644 --- a/xrspatial/geotiff/tests/test_crs_fail_closed_1929.py +++ b/xrspatial/geotiff/tests/test_crs_fail_closed_1929.py @@ -28,11 +28,7 @@ import xarray as xr from xrspatial.geotiff import to_geotiff -from xrspatial.geotiff._crs import ( - _looks_like_wkt, - _validate_crs_fallback, - _WKT_ROOT_KEYWORDS, -) +from xrspatial.geotiff._crs import _WKT_ROOT_KEYWORDS, _looks_like_wkt, _validate_crs_fallback def _make_da() -> xr.DataArray: diff --git a/xrspatial/geotiff/tests/test_dask_chunk_tile_misalignment.py b/xrspatial/geotiff/tests/test_dask_chunk_tile_misalignment.py index cc5808b09..6d37fd964 100644 --- a/xrspatial/geotiff/tests/test_dask_chunk_tile_misalignment.py +++ b/xrspatial/geotiff/tests/test_dask_chunk_tile_misalignment.py @@ -26,7 +26,6 @@ import numpy as np import pytest - tifffile = pytest.importorskip("tifffile") dask_array = pytest.importorskip("dask.array") diff --git a/xrspatial/geotiff/tests/test_dask_max_pixels_default_guard_1838.py b/xrspatial/geotiff/tests/test_dask_max_pixels_default_guard_1838.py index 6326d9050..d86ddf618 100644 --- a/xrspatial/geotiff/tests/test_dask_max_pixels_default_guard_1838.py +++ b/xrspatial/geotiff/tests/test_dask_max_pixels_default_guard_1838.py @@ -16,8 +16,8 @@ tifffile = pytest.importorskip("tifffile") -from xrspatial.geotiff import read_geotiff_dask -from xrspatial.geotiff._reader import MAX_PIXELS_DEFAULT +from xrspatial.geotiff import read_geotiff_dask # noqa: E402 +from xrspatial.geotiff._reader import MAX_PIXELS_DEFAULT # noqa: E402 def _write_oversized(path, *, h: int, w: int) -> None: diff --git a/xrspatial/geotiff/tests/test_dask_overview_level.py b/xrspatial/geotiff/tests/test_dask_overview_level.py index 103bc2c33..3c0563766 100644 --- a/xrspatial/geotiff/tests/test_dask_overview_level.py +++ b/xrspatial/geotiff/tests/test_dask_overview_level.py @@ -23,7 +23,6 @@ import numpy as np import pytest - tifffile = pytest.importorskip("tifffile") dask_array = pytest.importorskip("dask.array") @@ -63,8 +62,7 @@ def test_dask_overview_level_zero_matches_full_res(tmp_path): def test_dask_overview_level_one_returns_half_res(tmp_path): """``overview_level=1`` materialises the half-resolution overview.""" - from xrspatial.geotiff import read_geotiff_dask - from xrspatial.geotiff import open_geotiff + from xrspatial.geotiff import open_geotiff, read_geotiff_dask rng = np.random.RandomState(0xD0E) arr = rng.randint(0, 256, size=(128, 192), dtype=np.uint8) @@ -86,8 +84,7 @@ def test_dask_overview_level_one_returns_half_res(tmp_path): def test_dask_overview_level_two_returns_quarter_res(tmp_path): """``overview_level=2`` materialises the quarter-resolution overview.""" - from xrspatial.geotiff import read_geotiff_dask - from xrspatial.geotiff import open_geotiff + from xrspatial.geotiff import open_geotiff, read_geotiff_dask rng = np.random.RandomState(0xD0E) arr = rng.randint(0, 256, size=(128, 192), dtype=np.uint8) diff --git a/xrspatial/geotiff/tests/test_dask_planar_multiband.py b/xrspatial/geotiff/tests/test_dask_planar_multiband.py index 197aa7434..e2d07f52e 100644 --- a/xrspatial/geotiff/tests/test_dask_planar_multiband.py +++ b/xrspatial/geotiff/tests/test_dask_planar_multiband.py @@ -27,7 +27,6 @@ import numpy as np import pytest - tifffile = pytest.importorskip("tifffile") dask_array = pytest.importorskip("dask.array") diff --git a/xrspatial/geotiff/tests/test_decompression_caps.py b/xrspatial/geotiff/tests/test_decompression_caps.py index b3aae94ee..fc4e2c521 100644 --- a/xrspatial/geotiff/tests/test_decompression_caps.py +++ b/xrspatial/geotiff/tests/test_decompression_caps.py @@ -17,19 +17,16 @@ import importlib.util import struct +import zlib import numpy as np import pytest -import zlib -from xrspatial.geotiff._compression import ( - deflate_decompress, - lz4_decompress, - packbits_decompress, - zstd_decompress, -) +from xrspatial.geotiff._compression import (deflate_decompress, lz4_decompress, packbits_decompress, + zstd_decompress) from xrspatial.geotiff._reader import read_to_array + def _module_available(name: str) -> bool: """True iff ``import name`` would succeed. @@ -397,6 +394,7 @@ def test_jpeg2000_bomb_raises(self, tmp_path): ``jp2[:]``. """ import glymur + # Build a real 2000x2000 uint8 codestream (~150 bytes for zeros). arr = np.zeros((2000, 2000), dtype=np.uint8) tmp = tmp_path / "src.j2k" @@ -413,6 +411,7 @@ def test_jpeg2000_bomb_raises(self, tmp_path): def test_jpeg2000_legitimate_passes(self, tmp_path): """A JPEG 2000 blob whose declared output matches expected_size passes.""" import glymur + # Use a 64x64 raster: large enough for the default 6-resolution # OpenJPEG pyramid without tripping its min-tile-size check. arr = (np.arange(64 * 64, dtype=np.uint8) % 200).reshape(64, 64) @@ -502,8 +501,9 @@ def _forge_jpeg_with_sof_dimensions(real_h: int, real_w: int, The decoder never gets the chance to fail on the mismatch because the pre-decode cap fires first -- which is the property under test. """ - from PIL import Image import io + + from PIL import Image mode = 'RGB' if real_c == 3 else 'L' img = Image.new(mode, (real_w, real_h), color=0) buf = io.BytesIO() @@ -551,6 +551,7 @@ def test_jpeg_bomb_raises(self): declared_h=8000, declared_w=8000, ) from xrspatial.geotiff._compression import jpeg_decompress + # Match the full diagnostic so a regression that swaps in a # different error path (e.g. Pillow's own DecompressionBombError # with a different wording, or a numeric overflow before the @@ -563,8 +564,9 @@ def test_jpeg_bomb_raises(self): def test_jpeg_legitimate_passes(self): """A JPEG whose SOF dimensions match the expected tile size passes.""" - from PIL import Image import io + + from PIL import Image img = Image.new('RGB', (32, 32), color=(10, 20, 30)) buf = io.BytesIO() img.save(buf, format='JPEG', quality=90) @@ -583,13 +585,14 @@ def test_jpeg_no_cap_when_size_kwargs_default(self): real_h=16, real_w=16, real_c=3, declared_h=64, declared_w=64, ) - from xrspatial.geotiff._compression import jpeg_decompress # With no dimension kwargs, the cap is disabled. The forged JPEG # declares 64x64 but encodes only 16x16 of payload -- libjpeg # raises on the truncation; the bomb cap is what we're checking # is *not* the source of any exception here. Catch whatever # Pillow raises and assert it isn't our bomb message. from PIL import Image as _Img # noqa: F401 + + from xrspatial.geotiff._compression import jpeg_decompress try: jpeg_decompress(blob) except ValueError as exc: @@ -610,6 +613,7 @@ def test_jpeg_malformed_falls_through_to_pillow(self): # with no SOF marker. blob = bytes([0xFF, 0xD8, 0xFF, 0xD9]) from xrspatial.geotiff._compression import jpeg_decompress + # No SOF -> bomb cap returns None -> Pillow raises on the empty # stream. with pytest.raises(Exception): diff --git a/xrspatial/geotiff/tests/test_degenerate_georef_1945.py b/xrspatial/geotiff/tests/test_degenerate_georef_1945.py index 939876cf1..b60e4ca69 100644 --- a/xrspatial/geotiff/tests/test_degenerate_georef_1945.py +++ b/xrspatial/geotiff/tests/test_degenerate_georef_1945.py @@ -19,11 +19,7 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - open_geotiff, - to_geotiff, - write_geotiff_gpu, -) +from xrspatial.geotiff import open_geotiff, to_geotiff, write_geotiff_gpu def _gpu_available() -> bool: diff --git a/xrspatial/geotiff/tests/test_degenerate_shapes_backends_2026_05_11.py b/xrspatial/geotiff/tests/test_degenerate_shapes_backends_2026_05_11.py index f91108267..e78cb389d 100644 --- a/xrspatial/geotiff/tests/test_degenerate_shapes_backends_2026_05_11.py +++ b/xrspatial/geotiff/tests/test_degenerate_shapes_backends_2026_05_11.py @@ -29,19 +29,14 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - open_geotiff, - read_geotiff_dask, - read_geotiff_gpu, - to_geotiff, - write_geotiff_gpu, -) - +from xrspatial.geotiff import (open_geotiff, read_geotiff_dask, read_geotiff_gpu, to_geotiff, + write_geotiff_gpu) # --------------------------------------------------------------------------- # GPU gating: matches the predicate the rest of the geotiff test suite uses. # --------------------------------------------------------------------------- + def _gpu_available() -> bool: if importlib.util.find_spec("cupy") is None: return False diff --git a/xrspatial/geotiff/tests/test_descending_coords_1716.py b/xrspatial/geotiff/tests/test_descending_coords_1716.py index 3903e4879..ff045b820 100644 --- a/xrspatial/geotiff/tests/test_descending_coords_1716.py +++ b/xrspatial/geotiff/tests/test_descending_coords_1716.py @@ -8,18 +8,12 @@ """ from __future__ import annotations -import os - import numpy as np -import pytest import xarray as xr from xrspatial.geotiff import open_geotiff, to_geotiff -from xrspatial.geotiff._geotags import ( - TAG_MODEL_PIXEL_SCALE, - TAG_MODEL_TIEPOINT, - TAG_MODEL_TRANSFORMATION, -) +from xrspatial.geotiff._geotags import (TAG_MODEL_PIXEL_SCALE, TAG_MODEL_TIEPOINT, + TAG_MODEL_TRANSFORMATION) from xrspatial.geotiff._header import parse_all_ifds, parse_header diff --git a/xrspatial/geotiff/tests/test_dispatch_validation_parity_2162.py b/xrspatial/geotiff/tests/test_dispatch_validation_parity_2162.py index 6106caa51..015604891 100644 --- a/xrspatial/geotiff/tests/test_dispatch_validation_parity_2162.py +++ b/xrspatial/geotiff/tests/test_dispatch_validation_parity_2162.py @@ -27,15 +27,8 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - open_geotiff, - read_geotiff_dask, - read_geotiff_gpu, - read_vrt, - to_geotiff, - write_vrt, -) - +from xrspatial.geotiff import (open_geotiff, read_geotiff_dask, read_geotiff_gpu, read_vrt, + to_geotiff, write_vrt) # -------------------------------------------------------------------------- # Skip helpers + small fixtures diff --git a/xrspatial/geotiff/tests/test_dns_rebinding_pin_issue_1846.py b/xrspatial/geotiff/tests/test_dns_rebinding_pin_issue_1846.py index e143ed344..7b6d0bd32 100644 --- a/xrspatial/geotiff/tests/test_dns_rebinding_pin_issue_1846.py +++ b/xrspatial/geotiff/tests/test_dns_rebinding_pin_issue_1846.py @@ -27,7 +27,6 @@ from xrspatial.geotiff import UnsafeURLError from xrspatial.geotiff import _reader as _reader_mod - # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- @@ -331,10 +330,8 @@ def test_redirect_to_private_still_rejected(self, monkeypatch): # Use the existing _pool slot for mocking (matches the rest of # the SSRF tests in this codebase). - from xrspatial.geotiff.tests.test_ssrf_hardening_1664 import ( - _MockPool as _SsrfMockPool, - _MockPoolResponse as _SsrfResp, - ) + from xrspatial.geotiff.tests.test_ssrf_hardening_1664 import _MockPool as _SsrfMockPool + from xrspatial.geotiff.tests.test_ssrf_hardening_1664 import _MockPoolResponse as _SsrfResp src._pool = _SsrfMockPool([ _SsrfResp(302, location='http://attacker.test/inner.tif'), ]) diff --git a/xrspatial/geotiff/tests/test_dtype_read.py b/xrspatial/geotiff/tests/test_dtype_read.py index 538aa8828..0026e1364 100644 --- a/xrspatial/geotiff/tests/test_dtype_read.py +++ b/xrspatial/geotiff/tests/test_dtype_read.py @@ -69,9 +69,8 @@ def test_dtype_none_preserves_native(self, float64_tif): result = open_geotiff(path, dtype=None) assert result.dtype == np.float64 - def test_int_with_nodata_float_to_int_raises(self, tmp_path): - """uint16 file with nodata: nodata masking promotes to float64, so float->int validation fires.""" + """uint16 file with nodata: nodata masking promotes to float64, so float->int validation fires.""" # noqa: E501 arr = np.array([[1, 2], [3, 9999]], dtype=np.uint16) y = np.linspace(40.0, 41.0, 2) x = np.linspace(-105.0, -104.0, 2) @@ -104,7 +103,7 @@ def test_float_to_int_raises_dask(self, float64_tif): open_geotiff(path, dtype='int32', chunks=40) def test_int_with_nodata_float_to_int_raises_dask(self, tmp_path): - """uint16 file with nodata: nodata masking promotes to float64, so float->int validation fires.""" + """uint16 file with nodata: nodata masking promotes to float64, so float->int validation fires.""" # noqa: E501 arr = np.array([[1, 2], [3, 9999]], dtype=np.uint16) y = np.linspace(40.0, 41.0, 2) x = np.linspace(-105.0, -104.0, 2) diff --git a/xrspatial/geotiff/tests/test_eager_bigtiff_overhead_exact_1905.py b/xrspatial/geotiff/tests/test_eager_bigtiff_overhead_exact_1905.py index c39084e3c..45ab772a3 100644 --- a/xrspatial/geotiff/tests/test_eager_bigtiff_overhead_exact_1905.py +++ b/xrspatial/geotiff/tests/test_eager_bigtiff_overhead_exact_1905.py @@ -26,15 +26,11 @@ import struct import numpy as np -import pytest import xarray as xr from xrspatial.geotiff import open_geotiff, to_geotiff -from xrspatial.geotiff._dtypes import ASCII, LONG, SHORT -from xrspatial.geotiff._writer import ( - _build_ifd, - _compute_classic_ifd_overhead, -) +from xrspatial.geotiff._dtypes import ASCII, LONG +from xrspatial.geotiff._writer import _build_ifd, _compute_classic_ifd_overhead def _make_4x4_float32( diff --git a/xrspatial/geotiff/tests/test_eager_finalization_parity_2162.py b/xrspatial/geotiff/tests/test_eager_finalization_parity_2162.py index b7e2c6667..5549bed66 100644 --- a/xrspatial/geotiff/tests/test_eager_finalization_parity_2162.py +++ b/xrspatial/geotiff/tests/test_eager_finalization_parity_2162.py @@ -346,6 +346,7 @@ def test_miniswhite_post_inversion_sentinel_parity(tmp_path): land on the same NaN positions and the same lifecycle attrs. """ import tifffile + # uint8 + nodata=0; MinIsWhite inverts the stored value to 255 # before masking, and 255 is the post-inversion sentinel. stored = np.array([[0, 100, 200], [50, 0, 255]], dtype=np.uint8) @@ -388,6 +389,7 @@ def test_multiband_stripped_parity(tmp_path): path = str(tmp_path / 'eager_parity_2179_multiband.tif') from xrspatial.geotiff import to_geotiff + # Stripped (tiled=False) routes the GPU read through the # CPU-fallback eager site, which is one of the three sites this # PR migrated. diff --git a/xrspatial/geotiff/tests/test_edge_cases.py b/xrspatial/geotiff/tests/test_edge_cases.py index 88174e913..4eb98c6a6 100644 --- a/xrspatial/geotiff/tests/test_edge_cases.py +++ b/xrspatial/geotiff/tests/test_edge_cases.py @@ -9,26 +9,19 @@ import xarray as xr from xrspatial.geotiff import open_geotiff, to_geotiff -from xrspatial.geotiff._compression import ( - COMPRESSION_DEFLATE, - COMPRESSION_LZW, - COMPRESSION_NONE, - compress, - decompress, - deflate_decompress, - lzw_compress, - lzw_decompress, -) +from xrspatial.geotiff._compression import (COMPRESSION_DEFLATE, COMPRESSION_LZW, COMPRESSION_NONE, + compress, decompress, deflate_decompress, lzw_compress, + lzw_decompress) from xrspatial.geotiff._dtypes import numpy_to_tiff_dtype, tiff_dtype_to_numpy -from xrspatial.geotiff._header import parse_all_ifds, parse_header +from xrspatial.geotiff._header import parse_header from xrspatial.geotiff._reader import read_to_array from xrspatial.geotiff._writer import write - # ----------------------------------------------------------------------- # Writer: invalid inputs # ----------------------------------------------------------------------- + class TestWriteInvalidInputs: """Writer should reject or gracefully handle bad inputs.""" @@ -256,7 +249,7 @@ class TestReadCorruptFiles: def test_empty_file(self, tmp_path): path = str(tmp_path / 'empty.tif') - with open(path, 'wb') as f: + with open(path, 'wb'): pass # 0 bytes with pytest.raises((ValueError, Exception)): read_to_array(path) diff --git a/xrspatial/geotiff/tests/test_features.py b/xrspatial/geotiff/tests/test_features.py index 178e2cd10..4d5b54bd7 100644 --- a/xrspatial/geotiff/tests/test_features.py +++ b/xrspatial/geotiff/tests/test_features.py @@ -8,22 +8,17 @@ import xarray as xr from xrspatial.geotiff import open_geotiff, to_geotiff -from xrspatial.geotiff._compression import ( - COMPRESSION_PACKBITS, - packbits_compress, - packbits_decompress, - zstd_compress, - zstd_decompress, -) -from xrspatial.geotiff._header import parse_header, parse_all_ifds +from xrspatial.geotiff._compression import (packbits_compress, packbits_decompress, zstd_compress, + zstd_decompress) +from xrspatial.geotiff._header import parse_header from xrspatial.geotiff._reader import read_to_array from xrspatial.geotiff._writer import write - # ----------------------------------------------------------------------- # Multi-band write and read # ----------------------------------------------------------------------- + class TestMultiBand: def test_rgb_uint8_round_trip(self, tmp_path): @@ -387,7 +382,7 @@ def test_write_with_proj_string(self, tmp_path): arr = np.ones((4, 4), dtype=np.float32) path = str(tmp_path / 'proj_in.tif') to_geotiff(arr, path, crs='+proj=utm +zone=18 +datum=NAD83', - compression='none') + compression='none') da = open_geotiff(path) # pyproj should resolve this to EPSG:26918 @@ -484,7 +479,6 @@ def test_band_last_dataarray_unchanged(self, tmp_path): def test_min_is_white_inversion(self, tmp_path): """MinIsWhite (photometric=0) inverts grayscale values on read.""" - from .conftest import make_minimal_tiff import struct # Build a minimal TIFF with photometric=0 @@ -494,8 +488,10 @@ def test_min_is_white_inversion(self, tmp_path): pixels = np.array([[0, 50, 100, 200]], dtype=np.uint8).repeat(4, axis=0) tag_list = [] + def add_short(tag, val): tag_list.append((tag, 3, 1, struct.pack(f'{bo}H', val))) + def add_long(tag, val): tag_list.append((tag, 4, 1, struct.pack(f'{bo}I', val))) @@ -552,7 +548,7 @@ def test_extra_samples_rgba(self, tmp_path): wrong for multispectral data and has been replaced with MinIsBlack. """ - from xrspatial.geotiff._header import parse_header, parse_all_ifds, TAG_EXTRA_SAMPLES + from xrspatial.geotiff._header import TAG_EXTRA_SAMPLES, parse_all_ifds, parse_header arr = np.ones((4, 4, 4), dtype=np.uint8) * 128 path = str(tmp_path / 'rgba.tif') write(arr, path, compression='none', tiled=False, photometric='rgba') @@ -616,7 +612,7 @@ def test_dask_vrt(self, tmp_path): '\n' ' \n' ' \n' - f' {os.path.basename(tile_path)}\n' + f' {os.path.basename(tile_path)}\n' # noqa: E501 ' 1\n' ' \n' ' \n' @@ -654,7 +650,7 @@ def _make_mosaic_vrt(self, tmp_path, tile_paths, tile_shapes, ] for path, (th, tw), (yo, xo) in zip(tile_paths, tile_shapes, tile_offsets): lines.append(' ') - lines.append(f' {os.path.basename(path)}') + lines.append(f' {os.path.basename(path)}') # noqa: E501 lines.append(' 1') lines.append(f' ') lines.append(f' ') @@ -757,7 +753,7 @@ def test_vrt_with_crs(self, tmp_path): ' -120.0, 0.001, 0.0, 45.0, 0.0, -0.001\n' ' \n' ' \n' - f' {os.path.basename(tile_path)}\n' + f' {os.path.basename(tile_path)}\n' # noqa: E501 ' 1\n' ' \n' ' \n' @@ -784,7 +780,7 @@ def test_vrt_nodata(self, tmp_path): ' \n' ' -9999\n' ' \n' - f' {os.path.basename(tile_path)}\n' + f' {os.path.basename(tile_path)}\n' # noqa: E501 ' 1\n' ' \n' ' \n' @@ -883,7 +879,7 @@ def test_vrt_float64_fractional_nodata_masked(self, tmp_path): ' \n' ' -9999.1\n' ' \n' - f' {os.path.basename(tile_path)}\n' + f' {os.path.basename(tile_path)}\n' # noqa: E501 ' 1\n' ' \n' ' \n' @@ -932,7 +928,7 @@ def test_vrt_pixel_is_point_no_half_pixel_shift(self, tmp_path): f'{origin_y}, 0.0, {pixel_h}\n' f' \n' f' \n' - f' {os.path.basename(tile_path)}\n' + f' {os.path.basename(tile_path)}\n' # noqa: E501 f' 1\n' f' \n' f' \n' @@ -975,7 +971,7 @@ def test_vrt_pixel_is_area_still_shifts(self, tmp_path): f'{origin_y}, 0.0, {pixel_h}\n' f' \n' f' \n' - f' {os.path.basename(tile_path)}\n' + f' {os.path.basename(tile_path)}\n' # noqa: E501 f' 1\n' f' \n' f' \n' @@ -1019,9 +1015,7 @@ def test_memory_filesystem_read_write(self, tmp_path): arr = np.arange(16, dtype=np.float32).reshape(4, 4) # Write to memory filesystem via fsspec - from xrspatial.geotiff._writer import write, _write_bytes - from xrspatial.geotiff._writer import _assemble_tiff, _write_stripped - from xrspatial.geotiff._compression import COMPRESSION_NONE + from xrspatial.geotiff._writer import write # First write locally, then copy to memory fs local_path = str(tmp_path / 'test.tif') @@ -1104,7 +1098,7 @@ def test_dask_path_fsspec_uri_1749(self, tmp_path): fs.rm('/dask_1749_full.tif') def test_dask_path_fsspec_uri_no_full_download_1749(self, tmp_path, - monkeypatch): + monkeypatch): """Dask graph build for fsspec URIs must not pull the whole file. ``_read_geo_info`` previously called ``_CloudSource.read_all`` to @@ -1155,6 +1149,7 @@ def test_writer_cloud_scheme_detection(self): def test_write_to_memory_filesystem(self, tmp_path): """_write_bytes can write to fsspec memory filesystem.""" import fsspec + from xrspatial.geotiff._writer import write arr = np.arange(16, dtype=np.float32).reshape(4, 4) @@ -1181,7 +1176,7 @@ def test_float32_big_endian(self, tmp_path): from .conftest import make_minimal_tiff expected = np.arange(16, dtype=np.float32).reshape(4, 4) tiff_data = make_minimal_tiff(4, 4, np.dtype('float32'), - pixel_data=expected, big_endian=True) + pixel_data=expected, big_endian=True) path = str(tmp_path / 'be_f32.tif') with open(path, 'wb') as f: f.write(tiff_data) @@ -1195,7 +1190,7 @@ def test_uint16_big_endian(self, tmp_path): from .conftest import make_minimal_tiff expected = np.arange(20, dtype=np.uint16).reshape(4, 5) * 1000 tiff_data = make_minimal_tiff(5, 4, np.dtype('uint16'), - pixel_data=expected, big_endian=True) + pixel_data=expected, big_endian=True) path = str(tmp_path / 'be_u16.tif') with open(path, 'wb') as f: f.write(tiff_data) @@ -1209,7 +1204,7 @@ def test_int32_big_endian(self, tmp_path): from .conftest import make_minimal_tiff expected = np.arange(16, dtype=np.int32).reshape(4, 4) - 8 tiff_data = make_minimal_tiff(4, 4, np.dtype('int32'), - pixel_data=expected, big_endian=True) + pixel_data=expected, big_endian=True) path = str(tmp_path / 'be_i32.tif') with open(path, 'wb') as f: f.write(tiff_data) @@ -1223,7 +1218,7 @@ def test_float64_big_endian(self, tmp_path): from .conftest import make_minimal_tiff expected = np.linspace(-1.0, 1.0, 16, dtype=np.float64).reshape(4, 4) tiff_data = make_minimal_tiff(4, 4, np.dtype('float64'), - pixel_data=expected, big_endian=True) + pixel_data=expected, big_endian=True) path = str(tmp_path / 'be_f64.tif') with open(path, 'wb') as f: f.write(tiff_data) @@ -1237,7 +1232,7 @@ def test_uint8_big_endian_no_swap_needed(self, tmp_path): from .conftest import make_minimal_tiff expected = np.arange(16, dtype=np.uint8).reshape(4, 4) tiff_data = make_minimal_tiff(4, 4, np.dtype('uint8'), - pixel_data=expected, big_endian=True) + pixel_data=expected, big_endian=True) path = str(tmp_path / 'be_u8.tif') with open(path, 'wb') as f: f.write(tiff_data) @@ -1250,7 +1245,7 @@ def test_big_endian_windowed(self, tmp_path): from .conftest import make_minimal_tiff expected = np.arange(64, dtype=np.float32).reshape(8, 8) tiff_data = make_minimal_tiff(8, 8, np.dtype('float32'), - pixel_data=expected, big_endian=True) + pixel_data=expected, big_endian=True) path = str(tmp_path / 'be_window.tif') with open(path, 'wb') as f: f.write(tiff_data) @@ -1286,10 +1281,13 @@ def _make_tiff_with_extra_tags(self, tmp_path): pixel_bytes = pixels.tobytes() tag_list = [] + def add_short(tag, val): tag_list.append((tag, 3, 1, struct.pack(f'{bo}H', val))) + def add_long(tag, val): tag_list.append((tag, 4, 1, struct.pack(f'{bo}I', val))) + def add_ascii(tag, text): raw = text.encode('ascii') + b'\x00' tag_list.append((tag, 2, len(raw), raw)) @@ -1430,8 +1428,7 @@ def test_parse_gdal_metadata_xml(self): def test_build_gdal_metadata_xml(self): """Dict serializes back to valid XML.""" - from xrspatial.geotiff._geotags import ( - _build_gdal_metadata_xml, _parse_gdal_metadata) + from xrspatial.geotiff._geotags import _build_gdal_metadata_xml, _parse_gdal_metadata meta = { 'DataType': 'Generic', ('STATS_MAX', 0): '42.0', @@ -1509,7 +1506,8 @@ def test_real_file_metadata(self): def test_real_file_round_trip(self): """GDAL metadata survives real-file round-trip.""" - import os, tempfile + import os + import tempfile path = '../rtxpy/examples/USGS_one_meter_x65y454_NY_LongIsland_Z18_2014.tif' if not os.path.exists(path): pytest.skip("Real test files not available") @@ -1727,7 +1725,7 @@ def test_to_geotiff_api(self, tmp_path): arr = np.arange(64, dtype=np.float32).reshape(8, 8) path = str(tmp_path / 'api_nearest.tif') to_geotiff(arr, path, compression='deflate', - cog=True, overview_resampling='nearest') + cog=True, overview_resampling='nearest') result = open_geotiff(path) np.testing.assert_array_equal(result.values, arr) @@ -1750,9 +1748,8 @@ def test_bigtiff_header_written(self, tmp_path): # We can't easily create a >4GB file in tests, but we can verify # the BigTIFF path works by writing a small file with bigtiff=True # through the internal API. - from xrspatial.geotiff._writer import _assemble_tiff, _write_stripped from xrspatial.geotiff._compression import COMPRESSION_NONE - from xrspatial.geotiff._geotags import GeoTransform + from xrspatial.geotiff._writer import _assemble_tiff, _write_stripped arr = np.arange(16, dtype=np.float32).reshape(4, 4) rel_off, bc, chunks = _write_stripped(arr, COMPRESSION_NONE, False) @@ -1768,17 +1765,14 @@ def test_bigtiff_header_written(self, tmp_path): def test_bigtiff_read_write_round_trip(self, tmp_path): """Test that BigTIFF files produced internally can be read back.""" - from xrspatial.geotiff._writer import ( - _assemble_tiff, _write_stripped, _assemble_standard_layout, - ) from xrspatial.geotiff._compression import COMPRESSION_NONE - from xrspatial.geotiff._dtypes import numpy_to_tiff_dtype, SHORT, LONG, DOUBLE - from xrspatial.geotiff._header import ( - TAG_IMAGE_WIDTH, TAG_IMAGE_LENGTH, TAG_BITS_PER_SAMPLE, - TAG_COMPRESSION, TAG_PHOTOMETRIC, TAG_SAMPLES_PER_PIXEL, - TAG_SAMPLE_FORMAT, TAG_ROWS_PER_STRIP, - TAG_STRIP_OFFSETS, TAG_STRIP_BYTE_COUNTS, - ) + from xrspatial.geotiff._dtypes import LONG, SHORT, numpy_to_tiff_dtype + from xrspatial.geotiff._header import (TAG_BITS_PER_SAMPLE, TAG_COMPRESSION, + TAG_IMAGE_LENGTH, TAG_IMAGE_WIDTH, TAG_PHOTOMETRIC, + TAG_ROWS_PER_STRIP, TAG_SAMPLE_FORMAT, + TAG_SAMPLES_PER_PIXEL, TAG_STRIP_BYTE_COUNTS, + TAG_STRIP_OFFSETS) + from xrspatial.geotiff._writer import _assemble_standard_layout, _write_stripped arr = np.arange(64, dtype=np.float32).reshape(8, 8) rel_off, bc, chunks = _write_stripped(arr, COMPRESSION_NONE, False) @@ -1846,11 +1840,10 @@ def test_force_bigtiff_false_stays_classic(self, tmp_path): def _assert_offset_tags_are_long8(self, path): """Parse *path*'s first IFD and assert offset tags use LONG8.""" - from xrspatial.geotiff._header import ( - parse_all_ifds, TAG_STRIP_OFFSETS, TAG_STRIP_BYTE_COUNTS, - TAG_TILE_OFFSETS, TAG_TILE_BYTE_COUNTS, - ) from xrspatial.geotiff._dtypes import LONG8 + from xrspatial.geotiff._header import (TAG_STRIP_BYTE_COUNTS, TAG_STRIP_OFFSETS, + TAG_TILE_BYTE_COUNTS, TAG_TILE_OFFSETS, + parse_all_ifds) with open(path, 'rb') as f: buf = f.read() @@ -1938,7 +1931,6 @@ def _make_sub_byte_tiff(width, height, bps, pixel_values): """ import struct bo = '<' - dtype_np = np.dtype('uint8') if bps <= 8 else np.dtype('uint16') # Pack pixel values into bytes flat = pixel_values.ravel() @@ -1979,8 +1971,10 @@ def _make_sub_byte_tiff(width, height, bps, pixel_values): # Build tags tag_list = [] + def add_short(tag, val): tag_list.append((tag, 3, 1, struct.pack(f'{bo}H', val))) + def add_long(tag, val): tag_list.append((tag, 4, 1, struct.pack(f'{bo}I', val))) @@ -2184,7 +2178,6 @@ def _make_planar_tiff(width, height, bands, dtype=np.uint8, tiled=False, tw = th = tile_size tiles_across = math.ceil(width / tw) tiles_down = math.ceil(height / th) - tiles_per_band = tiles_across * tiles_down # Build tile data: all tiles for band 0, then band 1, etc. tile_blobs = [] @@ -2212,12 +2205,16 @@ def _make_planar_tiff(width, height, bands, dtype=np.uint8, tiled=False, # Build tags tag_list = [] + def add_short(tag, val): tag_list.append((tag, 3, 1, struct.pack(f'{bo}H', val))) + def add_shorts(tag, vals): tag_list.append((tag, 3, len(vals), struct.pack(f'{bo}{len(vals)}H', *vals))) + def add_long(tag, val): tag_list.append((tag, 4, 1, struct.pack(f'{bo}I', val))) + def add_longs(tag, vals): tag_list.append((tag, 4, len(vals), struct.pack(f'{bo}{len(vals)}I', *vals))) @@ -2361,10 +2358,13 @@ def _make_palette_tiff(width, height, bps, pixel_values, palette_rgb): cmap_values = r_vals + g_vals + b_vals tag_list = [] + def add_short(tag, val): tag_list.append((tag, 3, 1, struct.pack(f'{bo}H', val))) + def add_long(tag, val): tag_list.append((tag, 4, 1, struct.pack(f'{bo}I', val))) + def add_shorts(tag, vals): tag_list.append((tag, 3, len(vals), struct.pack(f'{bo}{len(vals)}H', *vals))) @@ -2448,7 +2448,7 @@ def test_palette_8bit_read(self, tmp_path): (65535, 0, 0), # 0 = red (0, 65535, 0), # 1 = green (0, 0, 65535), # 2 = blue - (65535, 65535, 65535),# 3 = white + (65535, 65535, 65535), # 3 = white ] + [(0, 0, 0)] * 252 # pad to 256 entries for 8-bit pixels = np.array([[0, 1, 2, 3], @@ -2514,10 +2514,11 @@ def test_palette_cmap_works_with_plot(self, tmp_path): after the contract v2 removal.""" import matplotlib matplotlib.use('Agg') - import xrspatial.accessor # register .xrs accessor - from xrspatial.accessor import _listed_colormap_from_attrs from matplotlib.colors import ListedColormap + import xrspatial.accessor # register .xrs accessor # noqa: F401 + from xrspatial.accessor import _listed_colormap_from_attrs + palette = [ (65535, 0, 0), (0, 65535, 0), @@ -2545,7 +2546,7 @@ def test_xrs_plot_with_palette(self, tmp_path): """da.xrs.plot() uses the embedded colormap.""" import matplotlib matplotlib.use('Agg') - import xrspatial.accessor # register .xrs accessor + import xrspatial.accessor # register .xrs accessor # noqa: F401 palette = [ (65535, 0, 0), @@ -2571,7 +2572,7 @@ def test_xrs_plot_no_palette(self, tmp_path): """da.xrs.plot() falls through to normal plot for non-palette data.""" import matplotlib matplotlib.use('Agg') - import xrspatial.accessor + import xrspatial.accessor # noqa: F401 arr = np.random.RandomState(42).rand(4, 4).astype(np.float32) path = str(tmp_path / 'no_palette.tif') @@ -2587,7 +2588,7 @@ def test_plot_geotiff_deprecated(self, tmp_path): """plot_geotiff still works but emits a DeprecationWarning.""" import matplotlib matplotlib.use('Agg') - import xrspatial.accessor + import xrspatial.accessor # noqa: F401 from xrspatial.geotiff import plot_geotiff palette = [(65535, 0, 0), (0, 65535, 0)] + [(0, 0, 0)] * 254 @@ -2702,6 +2703,7 @@ class TestDaskReads: def test_dask_basic(self, tmp_path): """read_geotiff_dask returns a dask-backed DataArray.""" import dask.array as da + from xrspatial.geotiff import read_geotiff_dask arr = np.arange(256, dtype=np.float32).reshape(16, 16) @@ -2766,6 +2768,7 @@ class TestPublicAPI: def test_all_lists_supported_functions(self): import xrspatial.geotiff as g + # Frozen list of names that callers / tests treat as part of the # public API. If any of these gets removed or renamed, that is a # breaking change and should go through a deprecation cycle. diff --git a/xrspatial/geotiff/tests/test_finalization_helpers_2162.py b/xrspatial/geotiff/tests/test_finalization_helpers_2162.py index e0df77674..acd0ab43e 100644 --- a/xrspatial/geotiff/tests/test_finalization_helpers_2162.py +++ b/xrspatial/geotiff/tests/test_finalization_helpers_2162.py @@ -28,13 +28,9 @@ import numpy as np import pytest -from xrspatial.geotiff._attrs import ( - _finalize_eager_read, - _finalize_lazy_read_attrs, -) +from xrspatial.geotiff._attrs import _finalize_eager_read, _finalize_lazy_read_attrs from xrspatial.geotiff._errors import UnparseableCRSError - # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- diff --git a/xrspatial/geotiff/tests/test_float16_read_1941.py b/xrspatial/geotiff/tests/test_float16_read_1941.py index ca238493b..e642bc90d 100644 --- a/xrspatial/geotiff/tests/test_float16_read_1941.py +++ b/xrspatial/geotiff/tests/test_float16_read_1941.py @@ -23,13 +23,8 @@ import pytest from xrspatial.geotiff import open_geotiff, read_geotiff_dask -from xrspatial.geotiff._dtypes import ( - SAMPLE_FORMAT_FLOAT, - SAMPLE_FORMAT_INT, - SAMPLE_FORMAT_UINT, - tiff_dtype_to_numpy, - tiff_storage_dtype, -) +from xrspatial.geotiff._dtypes import (SAMPLE_FORMAT_FLOAT, SAMPLE_FORMAT_INT, SAMPLE_FORMAT_UINT, + tiff_dtype_to_numpy, tiff_storage_dtype) class TestDtypeMap: diff --git a/xrspatial/geotiff/tests/test_float16_read_gpu_1941.py b/xrspatial/geotiff/tests/test_float16_read_gpu_1941.py index 9b0aca294..3983e08e0 100644 --- a/xrspatial/geotiff/tests/test_float16_read_gpu_1941.py +++ b/xrspatial/geotiff/tests/test_float16_read_gpu_1941.py @@ -30,7 +30,6 @@ import numpy as np import pytest -import xarray as xr def _gpu_available() -> bool: diff --git a/xrspatial/geotiff/tests/test_fuzz_hypothesis_1661.py b/xrspatial/geotiff/tests/test_fuzz_hypothesis_1661.py index 6866382c7..9598b1a96 100644 --- a/xrspatial/geotiff/tests/test_fuzz_hypothesis_1661.py +++ b/xrspatial/geotiff/tests/test_fuzz_hypothesis_1661.py @@ -36,7 +36,6 @@ from .conftest import make_minimal_tiff # noqa: E402 - # Exception types the geotiff module is allowed to raise on invalid input. # Any other exception class indicates an undocumented failure mode -- either # the strategy generated something we should reject explicitly, or there's @@ -52,7 +51,7 @@ # not in the [tests] extras. Drop them from the strategy when those # packages are missing so the fuzz run does not flake the moment # Hypothesis happens to draw a codec the runner can't actually write. -from xrspatial.geotiff._compression import LZ4_AVAILABLE, ZSTD_AVAILABLE +from xrspatial.geotiff._compression import LZ4_AVAILABLE, ZSTD_AVAILABLE # noqa: E402 LOSSLESS_CODECS = ['none', 'deflate', 'lzw', 'packbits'] if ZSTD_AVAILABLE: diff --git a/xrspatial/geotiff/tests/test_gdal_metadata_xml_escape_1614.py b/xrspatial/geotiff/tests/test_gdal_metadata_xml_escape_1614.py index 581663eb8..3e9ad2d84 100644 --- a/xrspatial/geotiff/tests/test_gdal_metadata_xml_escape_1614.py +++ b/xrspatial/geotiff/tests/test_gdal_metadata_xml_escape_1614.py @@ -14,10 +14,7 @@ import numpy as np import pytest -from xrspatial.geotiff._geotags import ( - _build_gdal_metadata_xml, - _parse_gdal_metadata, -) +from xrspatial.geotiff._geotags import _build_gdal_metadata_xml, _parse_gdal_metadata class TestBuildGdalMetadataXMLEscape: @@ -108,6 +105,7 @@ class TestToGeotiffMetadataRoundTrip: def test_to_geotiff_special_chars_round_trip(self, tmp_path): import xarray as xr + from xrspatial.geotiff import open_geotiff, to_geotiff arr = np.ones((4, 4), dtype=np.float32) diff --git a/xrspatial/geotiff/tests/test_gds_chunked_gpu_parity_1896.py b/xrspatial/geotiff/tests/test_gds_chunked_gpu_parity_1896.py index 549921168..249564e65 100644 --- a/xrspatial/geotiff/tests/test_gds_chunked_gpu_parity_1896.py +++ b/xrspatial/geotiff/tests/test_gds_chunked_gpu_parity_1896.py @@ -49,13 +49,9 @@ def _gpu_available() -> bool: def _parse_for_gds(path: str): """Return ``(ifd, geo_info, header)`` for the GDS entry point.""" + from xrspatial.geotiff._geotags import extract_geo_info_with_overview_inheritance + from xrspatial.geotiff._header import parse_all_ifds, parse_header, select_overview_ifd from xrspatial.geotiff._reader import _FileSource - from xrspatial.geotiff._header import ( - parse_header, parse_all_ifds, select_overview_ifd, - ) - from xrspatial.geotiff._geotags import ( - extract_geo_info_with_overview_inheritance, - ) fs = _FileSource(path) try: @@ -164,7 +160,6 @@ def test_gds_chunked_band_int_still_works(multiband_tiff_1896): from xrspatial.geotiff._compression import LERC_AVAILABLE # noqa: E402 - _lerc_gpu_only = pytest.mark.skipif( not (_HAS_GPU and LERC_AVAILABLE), reason="cupy + CUDA + lerc required", @@ -219,9 +214,9 @@ def test_gds_chunked_lerc_mask_matches_eager(tmp_path, lerc_writer_with_mask_189 The eager GPU path resolves and forwards ``masked_fill``; this test pins the chunked path to the same behaviour. """ - from xrspatial.geotiff._writer import write from xrspatial.geotiff import read_geotiff_gpu from xrspatial.geotiff._backends.gpu import _read_geotiff_gpu_chunked_gds + from xrspatial.geotiff._writer import write arr = np.arange(1, 65, dtype=np.float32).reshape(8, 8) invalid_positions = {(0, 1), (3, 3), (7, 7)} @@ -262,9 +257,9 @@ def invalid_pred(a): def test_gds_chunked_lerc_mask_sentinel_nodata(tmp_path, lerc_writer_with_mask_1896): """Sentinel nodata (-9999) on float LERC: chunked path matches eager.""" - from xrspatial.geotiff._writer import write from xrspatial.geotiff import read_geotiff_gpu from xrspatial.geotiff._backends.gpu import _read_geotiff_gpu_chunked_gds + from xrspatial.geotiff._writer import write arr = np.arange(1, 65, dtype=np.float32).reshape(8, 8) invalid_positions = {(0, 1), (5, 4)} diff --git a/xrspatial/geotiff/tests/test_gds_fallback_batched_d2h_1552.py b/xrspatial/geotiff/tests/test_gds_fallback_batched_d2h_1552.py index a135dd559..f69fb96a7 100644 --- a/xrspatial/geotiff/tests/test_gds_fallback_batched_d2h_1552.py +++ b/xrspatial/geotiff/tests/test_gds_fallback_batched_d2h_1552.py @@ -102,6 +102,7 @@ def test_batched_d2h_checks_gpu_memory_before_concat(monkeypatch): happens. """ import cupy + from xrspatial.geotiff import _gpu_decode seen = {"total_bytes": None, "what": None, "called": False} diff --git a/xrspatial/geotiff/tests/test_georef_edges.py b/xrspatial/geotiff/tests/test_georef_edges.py index 327d1ffcb..af194ff34 100644 --- a/xrspatial/geotiff/tests/test_georef_edges.py +++ b/xrspatial/geotiff/tests/test_georef_edges.py @@ -7,15 +7,12 @@ """ from __future__ import annotations -import os - import numpy as np -import pytest import xarray as xr from xrspatial.geotiff import open_geotiff, to_geotiff -from xrspatial.geotiff._header import parse_all_ifds, parse_header from xrspatial.geotiff._geotags import extract_geo_info +from xrspatial.geotiff._header import parse_all_ifds, parse_header from .conftest import make_minimal_tiff diff --git a/xrspatial/geotiff/tests/test_georef_resolver_parity_2211.py b/xrspatial/geotiff/tests/test_georef_resolver_parity_2211.py index 49585985f..97306a78f 100644 --- a/xrspatial/geotiff/tests/test_georef_resolver_parity_2211.py +++ b/xrspatial/geotiff/tests/test_georef_resolver_parity_2211.py @@ -51,32 +51,18 @@ import pytest import xarray as xr -from xrspatial.geotiff._attrs import ( - GEOREF_STATUS_CRS_ONLY, - GEOREF_STATUS_FULL, - GEOREF_STATUS_NONE, - GEOREF_STATUS_ROTATED_DROPPED, - GEOREF_STATUS_TRANSFORM_ONLY, - _compute_georef_status, - _compute_georef_status_from_parts, -) -from xrspatial.geotiff._coords import ( - GEOREF_STATUS_COORDS, - GeorefResolution, - resolve_georef, -) -from xrspatial.geotiff._geotags import ( - _NO_GEOREF_KEY, - GeoInfo, - GeoTransform, - RASTER_PIXEL_IS_AREA, -) - +from xrspatial.geotiff._attrs import (GEOREF_STATUS_CRS_ONLY, GEOREF_STATUS_FULL, + GEOREF_STATUS_NONE, GEOREF_STATUS_ROTATED_DROPPED, + GEOREF_STATUS_TRANSFORM_ONLY, _compute_georef_status, + _compute_georef_status_from_parts) +from xrspatial.geotiff._coords import GEOREF_STATUS_COORDS, GeorefResolution, resolve_georef +from xrspatial.geotiff._geotags import _NO_GEOREF_KEY, RASTER_PIXEL_IS_AREA, GeoInfo, GeoTransform # --------------------------------------------------------------------------- # Fixture builders # --------------------------------------------------------------------------- + def _make_axis_aligned_da(y_name: str, x_name: str) -> xr.DataArray: """Return a 4x5 DataArray with axis-aligned float pixel-center coords. diff --git a/xrspatial/geotiff/tests/test_georef_status_2136.py b/xrspatial/geotiff/tests/test_georef_status_2136.py index 851afa32b..6acb0ecc9 100644 --- a/xrspatial/geotiff/tests/test_georef_status_2136.py +++ b/xrspatial/geotiff/tests/test_georef_status_2136.py @@ -30,18 +30,12 @@ import xarray as xr from xrspatial.geotiff import open_geotiff, read_vrt, to_geotiff -from xrspatial.geotiff._errors import RotatedTransformError -from xrspatial.geotiff._attrs import ( - GEOREF_STATUS_CRS_ONLY, - GEOREF_STATUS_FULL, - GEOREF_STATUS_NONE, - GEOREF_STATUS_ROTATED_DROPPED, - GEOREF_STATUS_TRANSFORM_ONLY, - _ATTRS_CONTRACT_VERSION, - _compute_georef_status, - _compute_georef_status_from_parts, -) +from xrspatial.geotiff._attrs import (_ATTRS_CONTRACT_VERSION, GEOREF_STATUS_CRS_ONLY, + GEOREF_STATUS_FULL, GEOREF_STATUS_NONE, + GEOREF_STATUS_ROTATED_DROPPED, GEOREF_STATUS_TRANSFORM_ONLY, + _compute_georef_status, _compute_georef_status_from_parts) from xrspatial.geotiff._coords import _NO_GEOREF_KEY +from xrspatial.geotiff._errors import RotatedTransformError from xrspatial.geotiff._geotags import GeoInfo, GeoTransform tifffile = pytest.importorskip("tifffile") @@ -49,10 +43,8 @@ # Reuse the rotated-TIFF writer from the #2115 test rather than copying # the byte layout. The function is private to that test module but # the test runner sees the package directory so the import succeeds. -from xrspatial.geotiff.tests.test_allow_rotated_geotiff_2115 import ( # noqa: E402 - _write_rotated_tiff, -) - +from xrspatial.geotiff.tests.test_allow_rotated_geotiff_2115 import \ + _write_rotated_tiff # noqa: E402 _STATUS_KEY = 'georef_status' diff --git a/xrspatial/geotiff/tests/test_geotags.py b/xrspatial/geotiff/tests/test_geotags.py index 295c55bdf..1f12c6250 100644 --- a/xrspatial/geotiff/tests/test_geotags.py +++ b/xrspatial/geotiff/tests/test_geotags.py @@ -4,25 +4,14 @@ import numpy as np import pytest -from xrspatial.geotiff._geotags import ( - GeoInfo, - GeoTransform, - build_geo_tags, - extract_geo_info, - GEOKEY_GEOGRAPHIC_TYPE, - GEOKEY_MODEL_TYPE, - GEOKEY_PROJECTED_CS_TYPE, - GEOKEY_RASTER_TYPE, - MODEL_TYPE_GEOGRAPHIC, - MODEL_TYPE_PROJECTED, - RASTER_PIXEL_IS_AREA, - TAG_GEO_KEY_DIRECTORY, - TAG_GDAL_NODATA, - TAG_MODEL_PIXEL_SCALE, - TAG_MODEL_TIEPOINT, -) from xrspatial.geotiff._errors import RotatedTransformError +from xrspatial.geotiff._geotags import (GEOKEY_GEOGRAPHIC_TYPE, GEOKEY_PROJECTED_CS_TYPE, + MODEL_TYPE_GEOGRAPHIC, MODEL_TYPE_PROJECTED, + TAG_GDAL_NODATA, TAG_GEO_KEY_DIRECTORY, + TAG_MODEL_PIXEL_SCALE, TAG_MODEL_TIEPOINT, GeoTransform, + build_geo_tags, extract_geo_info) from xrspatial.geotiff._header import parse_all_ifds, parse_header + from .conftest import make_minimal_tiff @@ -185,6 +174,7 @@ def test_unknown_epsg_without_pyproj_raises(self, monkeypatch): than an explicit error. """ import builtins + from xrspatial.geotiff._errors import UnknownCRSModelTypeError real_import = builtins.__import__ @@ -231,11 +221,11 @@ def test_unknown_epsg_with_pyproj_raises(self, monkeypatch): date). The fallback set covers a small vetted allowlist; anything else should raise rather than guess. """ - from xrspatial.geotiff._errors import UnknownCRSModelTypeError - # Patch CRS.from_epsg to simulate a missing DB entry. import pyproj + from xrspatial.geotiff._errors import UnknownCRSModelTypeError + def boom(code): raise pyproj.exceptions.CRSError(f"simulated unknown code {code}") diff --git a/xrspatial/geotiff/tests/test_geotiff_metadata_2139.py b/xrspatial/geotiff/tests/test_geotiff_metadata_2139.py index 63bc7bd24..3f02e4cd7 100644 --- a/xrspatial/geotiff/tests/test_geotiff_metadata_2139.py +++ b/xrspatial/geotiff/tests/test_geotiff_metadata_2139.py @@ -18,19 +18,12 @@ """ from __future__ import annotations -import numpy as np import pytest -from xrspatial.geotiff._attrs import ( - GeoTIFFMetadata, - _ATTRS_CONTRACT_VERSION, - attrs_to_metadata, - geo_info_to_metadata, - metadata_to_attrs, -) +from xrspatial.geotiff._attrs import (_ATTRS_CONTRACT_VERSION, GeoTIFFMetadata, attrs_to_metadata, + geo_info_to_metadata, metadata_to_attrs) from xrspatial.geotiff._geotags import _NO_GEOREF_KEY - # --------------------------------------------------------------------------- # Builders # --------------------------------------------------------------------------- diff --git a/xrspatial/geotiff/tests/test_geotiff_planar_strip_truncation_1782.py b/xrspatial/geotiff/tests/test_geotiff_planar_strip_truncation_1782.py index d6bb4a0b0..9482202b9 100644 --- a/xrspatial/geotiff/tests/test_geotiff_planar_strip_truncation_1782.py +++ b/xrspatial/geotiff/tests/test_geotiff_planar_strip_truncation_1782.py @@ -33,11 +33,11 @@ from xrspatial.geotiff import open_geotiff from xrspatial.geotiff.tests.conftest import make_minimal_tiff - # --------------------------------------------------------------------------- # Hand-rolled planar=2 stripped TIFF builder # --------------------------------------------------------------------------- + def _make_planar2_stripped_tiff( width: int, height: int, diff --git a/xrspatial/geotiff/tests/test_gil_friendly_kwarg_1830.py b/xrspatial/geotiff/tests/test_gil_friendly_kwarg_1830.py index 417188cc4..80363b172 100644 --- a/xrspatial/geotiff/tests/test_gil_friendly_kwarg_1830.py +++ b/xrspatial/geotiff/tests/test_gil_friendly_kwarg_1830.py @@ -26,34 +26,19 @@ import pytest import xrspatial.geotiff._compression as comp_mod -from xrspatial.geotiff._compression import ( - COMPRESSION_DEFLATE, - COMPRESSION_LZW, - COMPRESSION_LZ4, - COMPRESSION_NONE, - COMPRESSION_PACKBITS, - COMPRESSION_ZSTD, - _HAVE_LIBDEFLATE, - LZ4_AVAILABLE, - compress, - deflate_compress, -) +from xrspatial.geotiff._compression import (_HAVE_LIBDEFLATE, COMPRESSION_DEFLATE, COMPRESSION_LZ4, + COMPRESSION_LZW, COMPRESSION_NONE, COMPRESSION_PACKBITS, + COMPRESSION_ZSTD, LZ4_AVAILABLE, compress, + deflate_compress) from xrspatial.geotiff._reader import read_to_array -from xrspatial.geotiff._writer import ( - _PARALLEL_MIN_BYTES, - _compress_block, - _prepare_strip, - _prepare_tile, - _write_stripped, - _write_tiled, - write, -) - +from xrspatial.geotiff._writer import (_PARALLEL_MIN_BYTES, _compress_block, _prepare_strip, + _prepare_tile, _write_stripped, _write_tiled, write) # --------------------------------------------------------------------------- # deflate_compress(gil_friendly=...) at the codec layer # --------------------------------------------------------------------------- + def _payload(n: int = 8192) -> bytes: """Repeatable payload large enough to exercise real codec branches.""" rng = np.random.RandomState(1830) diff --git a/xrspatial/geotiff/tests/test_golden_corpus_compression_1930.py b/xrspatial/geotiff/tests/test_golden_corpus_compression_1930.py index edfa3b1b6..523cbad6a 100644 --- a/xrspatial/geotiff/tests/test_golden_corpus_compression_1930.py +++ b/xrspatial/geotiff/tests/test_golden_corpus_compression_1930.py @@ -41,10 +41,7 @@ import xarray as xr # noqa: E402 -from xrspatial.geotiff.tests.golden_corpus._oracle import ( # noqa: E402 - compare_to_oracle, -) - +from xrspatial.geotiff.tests.golden_corpus._oracle import compare_to_oracle # noqa: E402 FIXTURES_DIR = ( pathlib.Path(__file__).resolve().parent diff --git a/xrspatial/geotiff/tests/test_golden_corpus_dask_gpu_1930.py b/xrspatial/geotiff/tests/test_golden_corpus_dask_gpu_1930.py index 1b1a3d667..175bf30d3 100644 --- a/xrspatial/geotiff/tests/test_golden_corpus_dask_gpu_1930.py +++ b/xrspatial/geotiff/tests/test_golden_corpus_dask_gpu_1930.py @@ -46,10 +46,7 @@ from xrspatial.geotiff import open_geotiff # noqa: E402 from xrspatial.geotiff.tests.golden_corpus import generate # noqa: E402 -from xrspatial.geotiff.tests.golden_corpus._oracle import ( # noqa: E402 - compare_to_oracle, -) - +from xrspatial.geotiff.tests.golden_corpus._oracle import compare_to_oracle # noqa: E402 FIXTURES_DIR = ( pathlib.Path(generate.__file__).resolve().parent / "fixtures" diff --git a/xrspatial/geotiff/tests/test_golden_corpus_dask_numpy_1930.py b/xrspatial/geotiff/tests/test_golden_corpus_dask_numpy_1930.py index 6d741aa51..c02a1bc60 100644 --- a/xrspatial/geotiff/tests/test_golden_corpus_dask_numpy_1930.py +++ b/xrspatial/geotiff/tests/test_golden_corpus_dask_numpy_1930.py @@ -42,10 +42,7 @@ from xrspatial.geotiff import open_geotiff # noqa: E402 from xrspatial.geotiff.tests.golden_corpus import generate # noqa: E402 -from xrspatial.geotiff.tests.golden_corpus._oracle import ( # noqa: E402 - compare_to_oracle, -) - +from xrspatial.geotiff.tests.golden_corpus._oracle import compare_to_oracle # noqa: E402 FIXTURES_DIR = ( pathlib.Path(generate.__file__).resolve().parent / "fixtures" diff --git a/xrspatial/geotiff/tests/test_golden_corpus_dtype_variants_1930.py b/xrspatial/geotiff/tests/test_golden_corpus_dtype_variants_1930.py index 2c530d3bf..09c31a0f6 100644 --- a/xrspatial/geotiff/tests/test_golden_corpus_dtype_variants_1930.py +++ b/xrspatial/geotiff/tests/test_golden_corpus_dtype_variants_1930.py @@ -31,9 +31,7 @@ import xarray as xr # noqa: E402 -from xrspatial.geotiff.tests.golden_corpus._oracle import ( # noqa: E402 - compare_to_oracle, -) +from xrspatial.geotiff.tests.golden_corpus._oracle import compare_to_oracle # noqa: E402 generate = importlib.import_module( "xrspatial.geotiff.tests.golden_corpus.generate" diff --git a/xrspatial/geotiff/tests/test_golden_corpus_eager_numpy_1930.py b/xrspatial/geotiff/tests/test_golden_corpus_eager_numpy_1930.py index 2699d666b..c4b7986bd 100644 --- a/xrspatial/geotiff/tests/test_golden_corpus_eager_numpy_1930.py +++ b/xrspatial/geotiff/tests/test_golden_corpus_eager_numpy_1930.py @@ -70,13 +70,8 @@ from xrspatial.geotiff import open_geotiff # noqa: E402 from xrspatial.geotiff.tests.golden_corpus import generate # noqa: E402 -from xrspatial.geotiff.tests.golden_corpus._marks import ( # noqa: E402 - fast_slow_marks_for, -) -from xrspatial.geotiff.tests.golden_corpus._oracle import ( # noqa: E402 - compare_to_oracle, -) - +from xrspatial.geotiff.tests.golden_corpus._marks import fast_slow_marks_for # noqa: E402 +from xrspatial.geotiff.tests.golden_corpus._oracle import compare_to_oracle # noqa: E402 FIXTURES_DIR = ( pathlib.Path(generate.__file__).resolve().parent / "fixtures" diff --git a/xrspatial/geotiff/tests/test_golden_corpus_fsspec_1930.py b/xrspatial/geotiff/tests/test_golden_corpus_fsspec_1930.py index 5b345b673..f98db2ad1 100644 --- a/xrspatial/geotiff/tests/test_golden_corpus_fsspec_1930.py +++ b/xrspatial/geotiff/tests/test_golden_corpus_fsspec_1930.py @@ -61,13 +61,8 @@ from xrspatial.geotiff import open_geotiff # noqa: E402 from xrspatial.geotiff.tests.golden_corpus import generate # noqa: E402 -from xrspatial.geotiff.tests.golden_corpus._marks import ( # noqa: E402 - fast_slow_marks_for, -) -from xrspatial.geotiff.tests.golden_corpus._oracle import ( # noqa: E402 - compare_to_oracle, -) - +from xrspatial.geotiff.tests.golden_corpus._marks import fast_slow_marks_for # noqa: E402 +from xrspatial.geotiff.tests.golden_corpus._oracle import compare_to_oracle # noqa: E402 FIXTURES_DIR = ( pathlib.Path(generate.__file__).resolve().parent / "fixtures" diff --git a/xrspatial/geotiff/tests/test_golden_corpus_gpu_1930.py b/xrspatial/geotiff/tests/test_golden_corpus_gpu_1930.py index bbab75654..f51dba383 100644 --- a/xrspatial/geotiff/tests/test_golden_corpus_gpu_1930.py +++ b/xrspatial/geotiff/tests/test_golden_corpus_gpu_1930.py @@ -55,10 +55,7 @@ from xrspatial.geotiff import open_geotiff # noqa: E402 from xrspatial.geotiff.tests.golden_corpus import generate # noqa: E402 -from xrspatial.geotiff.tests.golden_corpus._oracle import ( # noqa: E402 - compare_to_oracle, -) - +from xrspatial.geotiff.tests.golden_corpus._oracle import compare_to_oracle # noqa: E402 FIXTURES_DIR = ( pathlib.Path(generate.__file__).resolve().parent / "fixtures" diff --git a/xrspatial/geotiff/tests/test_golden_corpus_http_1930.py b/xrspatial/geotiff/tests/test_golden_corpus_http_1930.py index 72bdb105f..6eb7782cf 100644 --- a/xrspatial/geotiff/tests/test_golden_corpus_http_1930.py +++ b/xrspatial/geotiff/tests/test_golden_corpus_http_1930.py @@ -39,10 +39,7 @@ from xrspatial.geotiff import open_geotiff # noqa: E402 from xrspatial.geotiff.tests.golden_corpus import generate # noqa: E402 -from xrspatial.geotiff.tests.golden_corpus._oracle import ( # noqa: E402 - compare_to_oracle, -) - +from xrspatial.geotiff.tests.golden_corpus._oracle import compare_to_oracle # noqa: E402 FIXTURES_DIR = ( pathlib.Path(generate.__file__).resolve().parent / "fixtures" diff --git a/xrspatial/geotiff/tests/test_golden_corpus_layout_endian_1930.py b/xrspatial/geotiff/tests/test_golden_corpus_layout_endian_1930.py index 9d75f86c6..1d210b81f 100644 --- a/xrspatial/geotiff/tests/test_golden_corpus_layout_endian_1930.py +++ b/xrspatial/geotiff/tests/test_golden_corpus_layout_endian_1930.py @@ -19,7 +19,7 @@ pytest.importorskip("yaml") rasterio = pytest.importorskip("rasterio") -from xrspatial.geotiff.tests.golden_corpus import _oracle +from xrspatial.geotiff.tests.golden_corpus import _oracle # noqa: E402 generate = importlib.import_module( "xrspatial.geotiff.tests.golden_corpus.generate" diff --git a/xrspatial/geotiff/tests/test_golden_corpus_metadata_tags_1930.py b/xrspatial/geotiff/tests/test_golden_corpus_metadata_tags_1930.py index 452503d52..984e3cf19 100644 --- a/xrspatial/geotiff/tests/test_golden_corpus_metadata_tags_1930.py +++ b/xrspatial/geotiff/tests/test_golden_corpus_metadata_tags_1930.py @@ -34,10 +34,7 @@ tifffile = pytest.importorskip("tifffile") from xrspatial.geotiff.tests.golden_corpus import generate as _generate # noqa: E402 -from xrspatial.geotiff.tests.golden_corpus._oracle import ( # noqa: E402 - compare_to_oracle, -) - +from xrspatial.geotiff.tests.golden_corpus._oracle import compare_to_oracle # noqa: E402 # --------------------------------------------------------------------------- # Helpers diff --git a/xrspatial/geotiff/tests/test_golden_corpus_overview_cog_1930.py b/xrspatial/geotiff/tests/test_golden_corpus_overview_cog_1930.py index 33936f990..dcaad0450 100644 --- a/xrspatial/geotiff/tests/test_golden_corpus_overview_cog_1930.py +++ b/xrspatial/geotiff/tests/test_golden_corpus_overview_cog_1930.py @@ -30,10 +30,7 @@ rasterio = pytest.importorskip("rasterio") from xrspatial.geotiff.tests.golden_corpus import generate # noqa: E402 -from xrspatial.geotiff.tests.golden_corpus._oracle import ( # noqa: E402 - compare_to_oracle, -) - +from xrspatial.geotiff.tests.golden_corpus._oracle import compare_to_oracle # noqa: E402 FIXTURES_DIR = ( pathlib.Path(generate.__file__).resolve().parent / "fixtures" diff --git a/xrspatial/geotiff/tests/test_golden_corpus_vrt_1930.py b/xrspatial/geotiff/tests/test_golden_corpus_vrt_1930.py index bd3b5af33..3bdc05bfe 100644 --- a/xrspatial/geotiff/tests/test_golden_corpus_vrt_1930.py +++ b/xrspatial/geotiff/tests/test_golden_corpus_vrt_1930.py @@ -35,10 +35,7 @@ from xrspatial.geotiff import open_geotiff, write_vrt # noqa: E402 from xrspatial.geotiff.tests.golden_corpus import generate # noqa: E402 -from xrspatial.geotiff.tests.golden_corpus._oracle import ( # noqa: E402 - compare_to_oracle, -) - +from xrspatial.geotiff.tests.golden_corpus._oracle import compare_to_oracle # noqa: E402 FIXTURES_DIR = ( pathlib.Path(generate.__file__).resolve().parent / "fixtures" diff --git a/xrspatial/geotiff/tests/test_gpu_chunks_out_of_core_1876.py b/xrspatial/geotiff/tests/test_gpu_chunks_out_of_core_1876.py index 06dcd0613..a8ac235ff 100644 --- a/xrspatial/geotiff/tests/test_gpu_chunks_out_of_core_1876.py +++ b/xrspatial/geotiff/tests/test_gpu_chunks_out_of_core_1876.py @@ -50,9 +50,10 @@ def _kvikio_available() -> bool: @pytest.fixture def small_raster_path_1876(tmp_path): - from xrspatial.geotiff import to_geotiff import xarray as xr + from xrspatial.geotiff import to_geotiff + arr = np.arange(32 * 32, dtype=np.float32).reshape(32, 32) da = xr.DataArray(arr, dims=['y', 'x'], attrs={'crs': 4326, @@ -64,9 +65,10 @@ def small_raster_path_1876(tmp_path): @pytest.fixture def multi_band_path_1876(tmp_path): - from xrspatial.geotiff import to_geotiff import xarray as xr + from xrspatial.geotiff import to_geotiff + rng = np.random.RandomState(42) arr = rng.rand(3, 32, 32).astype(np.float32) da = xr.DataArray(arr, dims=['band', 'y', 'x'], @@ -241,9 +243,10 @@ def test_read_geotiff_gpu_chunks_fallback_when_kvikio_absent( """When kvikio is reported missing, the chunked path falls back to the CPU-decode + cupy.asarray graph and still produces a Dask+CuPy DataArray with correct values.""" - import cupy import importlib.util as _ilu + import cupy + from xrspatial.geotiff import read_geotiff_gpu from xrspatial.geotiff._backends import gpu as gtmod diff --git a/xrspatial/geotiff/tests/test_gpu_cuda_preflight_1903.py b/xrspatial/geotiff/tests/test_gpu_cuda_preflight_1903.py index 30c4b3c73..7c3b3b4d5 100644 --- a/xrspatial/geotiff/tests/test_gpu_cuda_preflight_1903.py +++ b/xrspatial/geotiff/tests/test_gpu_cuda_preflight_1903.py @@ -19,7 +19,6 @@ import pytest - _CUPY_AVAILABLE = importlib.util.find_spec("cupy") is not None @@ -72,6 +71,7 @@ def test_preflight_returns_silently_when_device_present(monkeypatch): _install_cupy_stub(monkeypatch, get_device_count=lambda: 1) import cupy + # Should not raise. gpu_mod._preflight_cuda_runtime(cupy) @@ -85,6 +85,7 @@ def test_read_geotiff_gpu_preflight_surface(monkeypatch, tmp_path): """ import numpy as np import xarray as xr + from xrspatial.geotiff import to_geotiff from xrspatial.geotiff._backends.gpu import read_geotiff_gpu @@ -121,6 +122,7 @@ def test_preflight_when_real_cupy_present(monkeypatch): works the same way -- the import in read_geotiff_gpu finds the patched attribute.""" import cupy + from xrspatial.geotiff._backends import gpu as gpu_mod class FakeCudaError(RuntimeError): diff --git a/xrspatial/geotiff/tests/test_gpu_fallback_forwards_kwargs_2238.py b/xrspatial/geotiff/tests/test_gpu_fallback_forwards_kwargs_2238.py index 46fe83bbc..d45296232 100644 --- a/xrspatial/geotiff/tests/test_gpu_fallback_forwards_kwargs_2238.py +++ b/xrspatial/geotiff/tests/test_gpu_fallback_forwards_kwargs_2238.py @@ -293,10 +293,9 @@ def test_sparse_tile_fallback_forwards_all_kwargs(tmp_path, monkeypatch): @_gpu_only def test_gpu_decode_failure_fallback_forwards_all_kwargs(tmp_path, - monkeypatch): + monkeypatch): """``gpu_decode_tiles`` failure routes through fallback with kwargs.""" - from xrspatial.geotiff import read_geotiff_gpu - from xrspatial.geotiff import _gpu_decode + from xrspatial.geotiff import _gpu_decode, read_geotiff_gpu from xrspatial.geotiff._backends import gpu as gpu_backend src = tmp_path / "2238_decode_fail.tif" @@ -434,8 +433,7 @@ def test_decode_failure_fallback_applies_window_band(tmp_path, monkeypatch): """ tifffile = pytest.importorskip("tifffile") - from xrspatial.geotiff import read_geotiff_gpu - from xrspatial.geotiff import _gpu_decode + from xrspatial.geotiff import _gpu_decode, read_geotiff_gpu src = tmp_path / "2238_windowed_fallback.tif" bands, h, w = 3, 64, 64 diff --git a/xrspatial/geotiff/tests/test_gpu_miniswhite_band_first_2097.py b/xrspatial/geotiff/tests/test_gpu_miniswhite_band_first_2097.py index fbd1d0122..2638919c3 100644 --- a/xrspatial/geotiff/tests/test_gpu_miniswhite_band_first_2097.py +++ b/xrspatial/geotiff/tests/test_gpu_miniswhite_band_first_2097.py @@ -17,7 +17,6 @@ import importlib.util -import numpy as np import pytest import xarray as xr diff --git a/xrspatial/geotiff/tests/test_gpu_strict_fallback_1516.py b/xrspatial/geotiff/tests/test_gpu_strict_fallback_1516.py index 6ba2114d8..6b68a8eb7 100644 --- a/xrspatial/geotiff/tests/test_gpu_strict_fallback_1516.py +++ b/xrspatial/geotiff/tests/test_gpu_strict_fallback_1516.py @@ -32,7 +32,6 @@ from .conftest import make_minimal_tiff - _CUPY_ORIG_SENTINEL = object() _cupy_saved = _CUPY_ORIG_SENTINEL _cupy_cuda_saved = _CUPY_ORIG_SENTINEL diff --git a/xrspatial/geotiff/tests/test_gpu_stripped_forwarding_1732.py b/xrspatial/geotiff/tests/test_gpu_stripped_forwarding_1732.py index fcf1b7ced..07cc8d395 100644 --- a/xrspatial/geotiff/tests/test_gpu_stripped_forwarding_1732.py +++ b/xrspatial/geotiff/tests/test_gpu_stripped_forwarding_1732.py @@ -46,7 +46,7 @@ def _gpu_available() -> bool: @_gpu_only def test_stripped_max_pixels_cap_is_enforced(): """max_pixels smaller than the file must raise before full decode.""" - from xrspatial.geotiff import to_geotiff, read_geotiff_gpu + from xrspatial.geotiff import read_geotiff_gpu, to_geotiff rng = np.random.RandomState(20260512) data = rng.randint(0, 200, size=(64, 96)).astype(np.uint8) @@ -71,7 +71,7 @@ def test_stripped_window_returns_only_window(): regression in the coord-only branch -- or a drift in the windowed ``attrs['transform']`` -- shows up here. """ - from xrspatial.geotiff import to_geotiff, open_geotiff, read_geotiff_gpu + from xrspatial.geotiff import open_geotiff, read_geotiff_gpu, to_geotiff rng = np.random.RandomState(20260512) data = rng.randint(0, 200, size=(64, 96)).astype(np.uint8) @@ -127,7 +127,7 @@ def test_stripped_window_returns_only_window(): def test_stripped_band_selection_returns_2d(): """Selecting band=1 on a 3-band stripped file returns a 2D array matching the requested band.""" - from xrspatial.geotiff import to_geotiff, read_geotiff_gpu + from xrspatial.geotiff import read_geotiff_gpu, to_geotiff rng = np.random.RandomState(20260512) data = rng.randint(0, 200, size=(48, 80, 3)).astype(np.uint8) @@ -145,7 +145,7 @@ def test_stripped_band_selection_returns_2d(): @_gpu_only def test_stripped_window_plus_band(): """Windowed read with band selection composes correctly.""" - from xrspatial.geotiff import to_geotiff, read_geotiff_gpu + from xrspatial.geotiff import read_geotiff_gpu, to_geotiff rng = np.random.RandomState(20260512) data = rng.randint(0, 200, size=(48, 80, 3)).astype(np.uint8) diff --git a/xrspatial/geotiff/tests/test_gpu_stripped_multiband.py b/xrspatial/geotiff/tests/test_gpu_stripped_multiband.py index 4e03f9d45..c4c2fd247 100644 --- a/xrspatial/geotiff/tests/test_gpu_stripped_multiband.py +++ b/xrspatial/geotiff/tests/test_gpu_stripped_multiband.py @@ -42,7 +42,7 @@ def _gpu_available() -> bool: @_gpu_only def test_stripped_3band_uint8(): """3-band uint8 stripped TIFF reads as (y, x, band).""" - from xrspatial.geotiff import to_geotiff, read_geotiff_gpu + from xrspatial.geotiff import read_geotiff_gpu, to_geotiff rng = np.random.RandomState(20260508) data = rng.randint(0, 200, size=(64, 96, 3)).astype(np.uint8) @@ -60,7 +60,7 @@ def test_stripped_3band_uint8(): @_gpu_only def test_stripped_2band_uint16(): """2-band uint16 stripped TIFF reads as (y, x, band).""" - from xrspatial.geotiff import to_geotiff, read_geotiff_gpu + from xrspatial.geotiff import read_geotiff_gpu, to_geotiff rng = np.random.RandomState(20260508) data = rng.randint(0, 60000, size=(48, 80, 2)).astype(np.uint16) @@ -79,7 +79,7 @@ def test_stripped_2band_uint16(): @_gpu_only def test_stripped_singleband_still_2d(): """Single-band stripped TIFF still produces a 2-D (y, x) DataArray.""" - from xrspatial.geotiff import to_geotiff, read_geotiff_gpu + from xrspatial.geotiff import read_geotiff_gpu, to_geotiff rng = np.random.RandomState(20260508) data = rng.randint(0, 200, size=(40, 60)).astype(np.uint8) diff --git a/xrspatial/geotiff/tests/test_gpu_window_band_1605.py b/xrspatial/geotiff/tests/test_gpu_window_band_1605.py index bb0695d84..ffffeee8f 100644 --- a/xrspatial/geotiff/tests/test_gpu_window_band_1605.py +++ b/xrspatial/geotiff/tests/test_gpu_window_band_1605.py @@ -183,7 +183,7 @@ def test_read_geotiff_gpu_window_bounds_validation(single_band_tiff): @_gpu_only def test_read_geotiff_gpu_band_bounds_validation(multi_band_tiff, - single_band_tiff): + single_band_tiff): """Out-of-range band raises IndexError.""" multi_path, _ = multi_band_tiff single_path, _ = single_band_tiff @@ -231,6 +231,7 @@ def test_read_geotiff_gpu_stripped_chunks_produces_dask(tmp_path): """ tifffile = pytest.importorskip("tifffile") import dask.array as dask_array + from xrspatial.geotiff import read_geotiff_gpu arr = np.arange(16 * 20, dtype=np.float32).reshape(16, 20) @@ -257,6 +258,7 @@ def test_read_geotiff_gpu_stripped_chunks_tuple(tmp_path): """Stripped branch accepts the (rh, cw) tuple chunks spec too.""" tifffile = pytest.importorskip("tifffile") import dask.array as dask_array + from xrspatial.geotiff import read_geotiff_gpu arr = np.arange(16 * 20, dtype=np.float32).reshape(16, 20) diff --git a/xrspatial/geotiff/tests/test_gpu_writer_compression_modes_2026_05_11.py b/xrspatial/geotiff/tests/test_gpu_writer_compression_modes_2026_05_11.py index 081d39d9b..1e699b47c 100644 --- a/xrspatial/geotiff/tests/test_gpu_writer_compression_modes_2026_05_11.py +++ b/xrspatial/geotiff/tests/test_gpu_writer_compression_modes_2026_05_11.py @@ -31,11 +31,7 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - open_geotiff, - write_geotiff_gpu, -) -from xrspatial.geotiff import _gpu_decode +from xrspatial.geotiff import _gpu_decode, open_geotiff, write_geotiff_gpu from xrspatial.geotiff._header import parse_header, parse_ifd @@ -273,7 +269,7 @@ def test_write_geotiff_gpu_jpeg_uint8_single_band_roundtrip(tmp_path): @_nvjpeg_only def test_write_geotiff_gpu_jpeg_uses_nvjpeg_when_available(tmp_path, - monkeypatch): + monkeypatch): """When libnvjpeg is present the writer must hit ``_nvjpeg_batch_encode``, not silently fall back to Pillow. diff --git a/xrspatial/geotiff/tests/test_gpu_writer_cpu_fallback_codecs_2026_05_12.py b/xrspatial/geotiff/tests/test_gpu_writer_cpu_fallback_codecs_2026_05_12.py index b271d890d..b479b0f78 100644 --- a/xrspatial/geotiff/tests/test_gpu_writer_cpu_fallback_codecs_2026_05_12.py +++ b/xrspatial/geotiff/tests/test_gpu_writer_cpu_fallback_codecs_2026_05_12.py @@ -43,19 +43,10 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - open_geotiff, - to_geotiff, - write_geotiff_gpu, -) -from xrspatial.geotiff._compression import ( - JPEG2000_AVAILABLE, - LERC_AVAILABLE, - LZ4_AVAILABLE, -) +from xrspatial.geotiff import open_geotiff, to_geotiff, write_geotiff_gpu +from xrspatial.geotiff._compression import JPEG2000_AVAILABLE, LERC_AVAILABLE, LZ4_AVAILABLE from xrspatial.geotiff._header import parse_header, parse_ifd - # -------------------------------------------------------------------------- # GPU gating # -------------------------------------------------------------------------- diff --git a/xrspatial/geotiff/tests/test_gpu_writer_overview_mode_and_compression_level_1740.py b/xrspatial/geotiff/tests/test_gpu_writer_overview_mode_and_compression_level_1740.py index b55f07b23..d1aacc634 100644 --- a/xrspatial/geotiff/tests/test_gpu_writer_overview_mode_and_compression_level_1740.py +++ b/xrspatial/geotiff/tests/test_gpu_writer_overview_mode_and_compression_level_1740.py @@ -38,14 +38,9 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - open_geotiff, - to_geotiff, - write_geotiff_gpu, -) +from xrspatial.geotiff import open_geotiff, to_geotiff, write_geotiff_gpu from xrspatial.geotiff._writer import _block_reduce_2d - # --------------------------------------------------------------------------- # GPU gating # --------------------------------------------------------------------------- diff --git a/xrspatial/geotiff/tests/test_header.py b/xrspatial/geotiff/tests/test_header.py index 22008cbd3..fab75b384 100644 --- a/xrspatial/geotiff/tests/test_header.py +++ b/xrspatial/geotiff/tests/test_header.py @@ -7,18 +7,9 @@ import pytest from xrspatial.geotiff._dtypes import RATIONAL, SRATIONAL -from xrspatial.geotiff._header import ( - IFD, - TIFFHeader, - _read_value, - parse_all_ifds, - parse_header, - parse_ifd, - TAG_IMAGE_WIDTH, - TAG_IMAGE_LENGTH, - TAG_BITS_PER_SAMPLE, - TAG_COMPRESSION, -) +from xrspatial.geotiff._header import (IFD, TAG_IMAGE_WIDTH, _read_value, parse_all_ifds, + parse_header, parse_ifd) + from .conftest import make_minimal_tiff @@ -134,7 +125,7 @@ class TestPlanarConfigValidation: """ def _ifd_with_planar(self, value) -> IFD: - from xrspatial.geotiff._header import IFDEntry, TAG_PLANAR_CONFIG + from xrspatial.geotiff._header import TAG_PLANAR_CONFIG, IFDEntry ifd = IFD() ifd.entries[TAG_PLANAR_CONFIG] = IFDEntry( tag=TAG_PLANAR_CONFIG, type_id=3, count=1, value=value, @@ -289,7 +280,6 @@ def test_rational_count_overflow_raises(self): _read_value(buf, 0, RATIONAL, 4, '<') - class TestTruncatedIFD: """T-8: a truncated IFD entry buffer should fail cleanly, not crash silently.""" diff --git a/xrspatial/geotiff/tests/test_helper_band_nodata_2210.py b/xrspatial/geotiff/tests/test_helper_band_nodata_2210.py index 4c15a81a7..4fa28c5b1 100644 --- a/xrspatial/geotiff/tests/test_helper_band_nodata_2210.py +++ b/xrspatial/geotiff/tests/test_helper_band_nodata_2210.py @@ -19,10 +19,7 @@ import pytest -from xrspatial.geotiff._attrs import ( - _finalize_lazy_read_attrs, - _validate_read_geo_info, -) +from xrspatial.geotiff._attrs import _finalize_lazy_read_attrs, _validate_read_geo_info from xrspatial.geotiff._errors import MixedBandMetadataError diff --git a/xrspatial/geotiff/tests/test_http_band_validation_1695.py b/xrspatial/geotiff/tests/test_http_band_validation_1695.py index ee13a5c76..ef872e399 100644 --- a/xrspatial/geotiff/tests/test_http_band_validation_1695.py +++ b/xrspatial/geotiff/tests/test_http_band_validation_1695.py @@ -33,7 +33,6 @@ from xrspatial.geotiff._reader import _read_cog_http, read_to_array from xrspatial.geotiff._writer import write - # --------------------------------------------------------------------------- # Loopback HTTP server with Range support # --------------------------------------------------------------------------- @@ -43,6 +42,7 @@ # live network. Each server holds one payload and shuts down at test # teardown. + class _RangeHandler(http.server.BaseHTTPRequestHandler): payload: bytes = b'' diff --git a/xrspatial/geotiff/tests/test_http_cog_coalesce.py b/xrspatial/geotiff/tests/test_http_cog_coalesce.py index 7674a3ba8..17107644d 100644 --- a/xrspatial/geotiff/tests/test_http_cog_coalesce.py +++ b/xrspatial/geotiff/tests/test_http_cog_coalesce.py @@ -17,21 +17,15 @@ import numpy as np import pytest -from xrspatial.geotiff import open_geotiff -from xrspatial.geotiff import read_geotiff_dask -from xrspatial.geotiff._reader import ( - COALESCE_GAP_THRESHOLD_DEFAULT, - _HTTPSource, - coalesce_ranges, - split_coalesced_bytes, -) +from xrspatial.geotiff import open_geotiff, read_geotiff_dask +from xrspatial.geotiff._reader import _HTTPSource, coalesce_ranges, split_coalesced_bytes from xrspatial.geotiff._writer import write - # --------------------------------------------------------------------------- # Pure unit tests on the coalescer # --------------------------------------------------------------------------- + def test_coalesce_empty_input(): merged, mapping = coalesce_ranges([]) assert merged == [] @@ -162,9 +156,7 @@ def test_coalesce_default_cap_bounds_adversarial_input_2266(): # with offsets spaced 1 MiB apart. Without the cap this collapses # into one ~4 GiB merged range. With the default cap nothing # exceeds MAX_COALESCED_RANGE_BYTES_DEFAULT. - from xrspatial.geotiff._sources import ( - MAX_COALESCED_RANGE_BYTES_DEFAULT, - ) + from xrspatial.geotiff._sources import MAX_COALESCED_RANGE_BYTES_DEFAULT one_mib = 1 << 20 ranges = [(i * one_mib, 1024) for i in range(4096)] diff --git a/xrspatial/geotiff/tests/test_http_dask_orientation_1794.py b/xrspatial/geotiff/tests/test_http_dask_orientation_1794.py index 45d353d68..074aa27db 100644 --- a/xrspatial/geotiff/tests/test_http_dask_orientation_1794.py +++ b/xrspatial/geotiff/tests/test_http_dask_orientation_1794.py @@ -78,4 +78,3 @@ def test_http_dask_read_rejects_non_default_orientation(tmp_path, monkeypatch): finally: httpd.shutdown() httpd.server_close() - diff --git a/xrspatial/geotiff/tests/test_http_meta_buffer_1718.py b/xrspatial/geotiff/tests/test_http_meta_buffer_1718.py index 7ec593724..fe8e2c715 100644 --- a/xrspatial/geotiff/tests/test_http_meta_buffer_1718.py +++ b/xrspatial/geotiff/tests/test_http_meta_buffer_1718.py @@ -15,20 +15,15 @@ import pytest from xrspatial.geotiff._header import parse_all_ifds, parse_header -from xrspatial.geotiff._reader import ( - INITIAL_HTTP_HEADER_BYTES, - MAX_HTTP_HEADER_BYTES, - _HTTPSource, - _parse_cog_http_meta, - _read_cog_http, -) +from xrspatial.geotiff._reader import (INITIAL_HTTP_HEADER_BYTES, MAX_HTTP_HEADER_BYTES, + _HTTPSource, _parse_cog_http_meta, _read_cog_http) from xrspatial.geotiff._writer import write - # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- + class _InMemoryHTTPSource(_HTTPSource): """_HTTPSource backed by an in-memory bytes buffer. diff --git a/xrspatial/geotiff/tests/test_http_no_stdlib_fallback_2050.py b/xrspatial/geotiff/tests/test_http_no_stdlib_fallback_2050.py index a7a802993..0596bc9b9 100644 --- a/xrspatial/geotiff/tests/test_http_no_stdlib_fallback_2050.py +++ b/xrspatial/geotiff/tests/test_http_no_stdlib_fallback_2050.py @@ -19,7 +19,6 @@ from xrspatial.geotiff import _reader as _reader_mod - # --------------------------------------------------------------------------- # urllib3 is a hard runtime dependency # --------------------------------------------------------------------------- diff --git a/xrspatial/geotiff/tests/test_http_read_all_bounded_2051.py b/xrspatial/geotiff/tests/test_http_read_all_bounded_2051.py index 379bc8d82..624a390cc 100644 --- a/xrspatial/geotiff/tests/test_http_read_all_bounded_2051.py +++ b/xrspatial/geotiff/tests/test_http_read_all_bounded_2051.py @@ -25,19 +25,15 @@ import numpy as np import pytest -from xrspatial.geotiff._reader import ( - _HTTPSource, - _compute_full_image_byte_budget, - _FULL_IMAGE_BUDGET_HEADER_SLACK, - _read_cog_http, -) +from xrspatial.geotiff._reader import (_FULL_IMAGE_BUDGET_HEADER_SLACK, + _compute_full_image_byte_budget, _HTTPSource, _read_cog_http) from xrspatial.geotiff._writer import write - # --------------------------------------------------------------------------- # Server helpers # --------------------------------------------------------------------------- + class _BaseHandler(http.server.BaseHTTPRequestHandler): payload: bytes = b'' # Subclasses override these to fake misbehaviour. diff --git a/xrspatial/geotiff/tests/test_http_stripped_window_max_pixels_issue_A_1842.py b/xrspatial/geotiff/tests/test_http_stripped_window_max_pixels_issue_A_1842.py index 030922d37..5662fadbc 100644 --- a/xrspatial/geotiff/tests/test_http_stripped_window_max_pixels_issue_A_1842.py +++ b/xrspatial/geotiff/tests/test_http_stripped_window_max_pixels_issue_A_1842.py @@ -20,11 +20,7 @@ import pytest from xrspatial.geotiff import _reader as reader_mod -from xrspatial.geotiff._reader import ( - PixelSafetyLimitError, - _HTTPSource, - _read_cog_http, -) +from xrspatial.geotiff._reader import PixelSafetyLimitError, _HTTPSource, _read_cog_http from xrspatial.geotiff._writer import write diff --git a/xrspatial/geotiff/tests/test_http_window_band_planar_1669.py b/xrspatial/geotiff/tests/test_http_window_band_planar_1669.py index d7e865b4b..c2a837ae3 100644 --- a/xrspatial/geotiff/tests/test_http_window_band_planar_1669.py +++ b/xrspatial/geotiff/tests/test_http_window_band_planar_1669.py @@ -37,11 +37,11 @@ from xrspatial.geotiff._reader import _read_cog_http, read_to_array from xrspatial.geotiff._writer import write - # --------------------------------------------------------------------------- # Loopback HTTP server with Range support # --------------------------------------------------------------------------- + class _RangeHandler(http.server.BaseHTTPRequestHandler): """Serve a single in-memory bytes payload with HTTP Range support.""" diff --git a/xrspatial/geotiff/tests/test_ifd_chain_cap.py b/xrspatial/geotiff/tests/test_ifd_chain_cap.py index 616ea0c7e..e0ade159b 100644 --- a/xrspatial/geotiff/tests/test_ifd_chain_cap.py +++ b/xrspatial/geotiff/tests/test_ifd_chain_cap.py @@ -14,12 +14,7 @@ import pytest from xrspatial.geotiff import to_geotiff -from xrspatial.geotiff._header import ( - MAX_IFDS, - TAG_IMAGE_WIDTH, - parse_all_ifds, - parse_header, -) +from xrspatial.geotiff._header import MAX_IFDS, TAG_IMAGE_WIDTH, parse_all_ifds, parse_header def _build_chained_ifd_bytes(n_ifds: int, big_endian: bool = False) -> bytes: diff --git a/xrspatial/geotiff/tests/test_ifd_cycle_1913.py b/xrspatial/geotiff/tests/test_ifd_cycle_1913.py index 8a0a5cb0d..2975cf9b1 100644 --- a/xrspatial/geotiff/tests/test_ifd_cycle_1913.py +++ b/xrspatial/geotiff/tests/test_ifd_cycle_1913.py @@ -18,12 +18,7 @@ import pytest -from xrspatial.geotiff._header import ( - MAX_IFDS, - TAG_IMAGE_WIDTH, - parse_all_ifds, - parse_header, -) +from xrspatial.geotiff._header import MAX_IFDS, TAG_IMAGE_WIDTH, parse_all_ifds, parse_header def _build_cyclic_ifd_bytes(big_endian: bool = False) -> bytes: diff --git a/xrspatial/geotiff/tests/test_ifd_entry_table_bounds_1672.py b/xrspatial/geotiff/tests/test_ifd_entry_table_bounds_1672.py index 2af97bd24..1212d3f26 100644 --- a/xrspatial/geotiff/tests/test_ifd_entry_table_bounds_1672.py +++ b/xrspatial/geotiff/tests/test_ifd_entry_table_bounds_1672.py @@ -23,12 +23,7 @@ import pytest from xrspatial.geotiff._dtypes import LONG -from xrspatial.geotiff._header import ( - TAG_IMAGE_WIDTH, - parse_header, - parse_ifd, -) - +from xrspatial.geotiff._header import TAG_IMAGE_WIDTH, parse_header, parse_ifd # --- Classic TIFF --- diff --git a/xrspatial/geotiff/tests/test_int_coord_sentinel_2087.py b/xrspatial/geotiff/tests/test_int_coord_sentinel_2087.py index 8d4e7fe6a..8622d49a7 100644 --- a/xrspatial/geotiff/tests/test_int_coord_sentinel_2087.py +++ b/xrspatial/geotiff/tests/test_int_coord_sentinel_2087.py @@ -32,7 +32,6 @@ from xrspatial.geotiff._coords import _has_no_georef_marker from xrspatial.geotiff._geotags import _NO_GEOREF_KEY - # --- Unit checks on the no-georef marker predicate ---------------------- # # Pre-#2133, ``xrspatial.geotiff._coords`` exported an diff --git a/xrspatial/geotiff/tests/test_jpeg.py b/xrspatial/geotiff/tests/test_jpeg.py index 55c447033..67a79434a 100644 --- a/xrspatial/geotiff/tests/test_jpeg.py +++ b/xrspatial/geotiff/tests/test_jpeg.py @@ -7,14 +7,10 @@ import pytest import xarray as xr -from xrspatial.geotiff._compression import ( - COMPRESSION_JPEG, - _splice_jpeg_tables, - jpeg_compress, - jpeg_decompress, -) -from xrspatial.geotiff._writer import write, _compression_tag +from xrspatial.geotiff._compression import (COMPRESSION_JPEG, _splice_jpeg_tables, jpeg_compress, + jpeg_decompress) from xrspatial.geotiff._reader import read_to_array +from xrspatial.geotiff._writer import _compression_tag, write class TestJpegCodec: @@ -171,9 +167,10 @@ class TestJpegTablesSplice: def test_splice_reconstructs_complete_jpeg(self): # Build a complete JPEG, then split it into a tables stream + a # tile fragment. Splicing should recover a decodable stream. - from PIL import Image import io + from PIL import Image + rng = np.random.RandomState(1502) arr = rng.randint(50, 200, (16, 16, 3), dtype=np.uint8) img = Image.fromarray(arr, mode='RGB') @@ -201,9 +198,10 @@ def test_splice_passthrough_on_invalid_input(self): assert _splice_jpeg_tables(b'no soi', b'\xff\xd8\xff\xd9') == b'no soi' def test_jpeg_decompress_accepts_jpeg_tables_kwarg(self): - from PIL import Image import io + from PIL import Image + rng = np.random.RandomState(1502) arr = rng.randint(50, 200, (16, 16, 3), dtype=np.uint8) img = Image.fromarray(arr, mode='RGB') @@ -242,9 +240,8 @@ def _gradient_rgb(self, size=128): def test_tiled_ycbcr_jpeg(self, tmp_path): import rasterio as rio - from xrspatial.geotiff._header import ( - parse_header, parse_all_ifds, TAG_JPEG_TABLES, - ) + + from xrspatial.geotiff._header import TAG_JPEG_TABLES, parse_all_ifds, parse_header size = 128 data = self._gradient_rgb(size) diff --git a/xrspatial/geotiff/tests/test_jpeg2000.py b/xrspatial/geotiff/tests/test_jpeg2000.py index fe16b60d3..bc664d8ce 100644 --- a/xrspatial/geotiff/tests/test_jpeg2000.py +++ b/xrspatial/geotiff/tests/test_jpeg2000.py @@ -4,13 +4,8 @@ import numpy as np import pytest -from xrspatial.geotiff._compression import ( - COMPRESSION_JPEG2000, - JPEG2000_AVAILABLE, - jpeg2000_compress, - jpeg2000_decompress, - decompress, -) +from xrspatial.geotiff._compression import (COMPRESSION_JPEG2000, JPEG2000_AVAILABLE, decompress, + jpeg2000_compress, jpeg2000_decompress) pytestmark = pytest.mark.skipif( not JPEG2000_AVAILABLE, @@ -89,8 +84,8 @@ class TestJPEG2000WriteRoundTrip: """Write-read roundtrip using the TIFF writer with JPEG 2000 compression.""" def test_tiled_uint8(self, tmp_path): - from xrspatial.geotiff._writer import write from xrspatial.geotiff._reader import read_to_array + from xrspatial.geotiff._writer import write expected = np.arange(64, dtype=np.uint8).reshape(8, 8) path = str(tmp_path / 'j2k_1048_tiled_uint8.tif') @@ -100,8 +95,8 @@ def test_tiled_uint8(self, tmp_path): np.testing.assert_array_equal(arr, expected) def test_tiled_uint16(self, tmp_path): - from xrspatial.geotiff._writer import write from xrspatial.geotiff._reader import read_to_array + from xrspatial.geotiff._writer import write expected = np.arange(64, dtype=np.uint16).reshape(8, 8) path = str(tmp_path / 'j2k_1048_tiled_uint16.tif') @@ -111,8 +106,8 @@ def test_tiled_uint16(self, tmp_path): np.testing.assert_array_equal(arr, expected) def test_stripped_uint8(self, tmp_path): - from xrspatial.geotiff._writer import write from xrspatial.geotiff._reader import read_to_array + from xrspatial.geotiff._writer import write expected = np.arange(64, dtype=np.uint8).reshape(8, 8) path = str(tmp_path / 'j2k_1048_stripped.tif') @@ -122,9 +117,9 @@ def test_stripped_uint8(self, tmp_path): np.testing.assert_array_equal(arr, expected) def test_with_geo_info(self, tmp_path): - from xrspatial.geotiff._writer import write - from xrspatial.geotiff._reader import read_to_array from xrspatial.geotiff._geotags import GeoTransform + from xrspatial.geotiff._reader import read_to_array + from xrspatial.geotiff._writer import write expected = np.ones((8, 8), dtype=np.uint8) * 100 gt = GeoTransform(-120.0, 45.0, 0.001, -0.001) @@ -139,6 +134,7 @@ def test_with_geo_info(self, tmp_path): def test_public_api_roundtrip(self, tmp_path): """Test via open_geotiff / to_geotiff public API.""" import xarray as xr + from xrspatial.geotiff import open_geotiff, to_geotiff data = np.arange(64, dtype=np.uint8).reshape(8, 8) @@ -174,9 +170,8 @@ def test_compression_tag_mapping(self): def test_unavailable_raises_import_error(self): """If glymur is missing, codec functions raise ImportError.""" - import unittest.mock - import importlib import xrspatial.geotiff._compression as comp_mod + # Temporarily pretend glymur is unavailable orig = comp_mod.JPEG2000_AVAILABLE comp_mod.JPEG2000_AVAILABLE = False diff --git a/xrspatial/geotiff/tests/test_jpeg_gpu_1549.py b/xrspatial/geotiff/tests/test_jpeg_gpu_1549.py index 607a8361e..6e1036604 100644 --- a/xrspatial/geotiff/tests/test_jpeg_gpu_1549.py +++ b/xrspatial/geotiff/tests/test_jpeg_gpu_1549.py @@ -81,7 +81,7 @@ def _nvjpeg_available() -> bool: def _write_jpeg_rgb_tiff(path: str, seed: int = 0, - noise: bool = True) -> np.ndarray: + noise: bool = True) -> np.ndarray: """Write a 3-band 256x256 tiled JPEG TIFF using tifffile. tifffile emits a complete JFIF stream (SOI + APP0 + DQT + DHT + SOF0 @@ -137,10 +137,10 @@ def test_rgb_jpeg_gpu_no_crash(tmp_path, monkeypatch): guard the test would pass on a system whose nvJPEG returned None for any reason, defeating the point of the regression test. """ - from xrspatial.geotiff import read_geotiff_gpu - from xrspatial.geotiff import _gpu_decode import cupy + from xrspatial.geotiff import _gpu_decode, read_geotiff_gpu + spy = {"calls": 0, "successes": 0} original = _gpu_decode._try_nvjpeg_batch_decode @@ -242,6 +242,7 @@ def test_cuda_context_survives_after_jpeg_gpu_read(tmp_path): operation and an unrelated GPU read and asserts both succeed. """ import cupy + from xrspatial.geotiff import open_geotiff path = str(tmp_path / "rgb_ctx_1549.tif") diff --git a/xrspatial/geotiff/tests/test_kwarg_behaviour_2026_05_12.py b/xrspatial/geotiff/tests/test_kwarg_behaviour_2026_05_12.py index c7971c7f9..9307834bb 100644 --- a/xrspatial/geotiff/tests/test_kwarg_behaviour_2026_05_12.py +++ b/xrspatial/geotiff/tests/test_kwarg_behaviour_2026_05_12.py @@ -46,17 +46,11 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - open_geotiff, - read_geotiff_gpu, - to_geotiff, - write_geotiff_gpu, - write_vrt, -) +from xrspatial.geotiff import (open_geotiff, read_geotiff_gpu, to_geotiff, write_geotiff_gpu, + write_vrt) from xrspatial.geotiff._header import parse_header from xrspatial.geotiff._vrt import parse_vrt - # -------------------------------------------------------------------------- # GPU gating # -------------------------------------------------------------------------- diff --git a/xrspatial/geotiff/tests/test_kwarg_behaviour_2026_05_12_v2.py b/xrspatial/geotiff/tests/test_kwarg_behaviour_2026_05_12_v2.py index a917b21a5..9fa9d4bb0 100644 --- a/xrspatial/geotiff/tests/test_kwarg_behaviour_2026_05_12_v2.py +++ b/xrspatial/geotiff/tests/test_kwarg_behaviour_2026_05_12_v2.py @@ -44,15 +44,9 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - open_geotiff, - read_vrt, - to_geotiff, - write_geotiff_gpu, -) +from xrspatial.geotiff import open_geotiff, read_vrt, to_geotiff, write_geotiff_gpu from xrspatial.geotiff._vrt import write_vrt as _write_vrt_internal - # -------------------------------------------------------------------------- # GPU gating # -------------------------------------------------------------------------- diff --git a/xrspatial/geotiff/tests/test_kwarg_coverage_2026_05_11_r4.py b/xrspatial/geotiff/tests/test_kwarg_coverage_2026_05_11_r4.py index cc6266faf..0d0b23e7f 100644 --- a/xrspatial/geotiff/tests/test_kwarg_coverage_2026_05_11_r4.py +++ b/xrspatial/geotiff/tests/test_kwarg_coverage_2026_05_11_r4.py @@ -32,12 +32,7 @@ import numpy as np import pytest -from xrspatial.geotiff import ( - open_geotiff, - read_geotiff_dask, - read_geotiff_gpu, - to_geotiff, -) +from xrspatial.geotiff import open_geotiff, read_geotiff_dask, read_geotiff_gpu, to_geotiff def _gpu_available() -> bool: diff --git a/xrspatial/geotiff/tests/test_lazy_finalization_parity_2162.py b/xrspatial/geotiff/tests/test_lazy_finalization_parity_2162.py index bb7b6dacc..4c22972a5 100644 --- a/xrspatial/geotiff/tests/test_lazy_finalization_parity_2162.py +++ b/xrspatial/geotiff/tests/test_lazy_finalization_parity_2162.py @@ -32,24 +32,16 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - read_geotiff_dask, - to_geotiff, -) -from xrspatial.geotiff._attrs import ( - GEOREF_STATUS_CRS_ONLY, - GEOREF_STATUS_FULL, - GEOREF_STATUS_NONE, - GEOREF_STATUS_ROTATED_DROPPED, - GEOREF_STATUS_TRANSFORM_ONLY, -) +from xrspatial.geotiff import read_geotiff_dask, to_geotiff +from xrspatial.geotiff._attrs import (GEOREF_STATUS_CRS_ONLY, GEOREF_STATUS_FULL, + GEOREF_STATUS_NONE, GEOREF_STATUS_ROTATED_DROPPED, + GEOREF_STATUS_TRANSFORM_ONLY) from xrspatial.geotiff._coords import _NO_GEOREF_KEY tifffile = pytest.importorskip("tifffile") -from xrspatial.geotiff.tests.test_allow_rotated_geotiff_2115 import ( # noqa: E402 - _write_rotated_tiff, -) +from xrspatial.geotiff.tests.test_allow_rotated_geotiff_2115 import \ + _write_rotated_tiff # noqa: E402 def _gpu_available() -> bool: diff --git a/xrspatial/geotiff/tests/test_lerc.py b/xrspatial/geotiff/tests/test_lerc.py index c1f73c6bc..af1638c9d 100644 --- a/xrspatial/geotiff/tests/test_lerc.py +++ b/xrspatial/geotiff/tests/test_lerc.py @@ -4,13 +4,8 @@ import numpy as np import pytest -from xrspatial.geotiff._compression import ( - COMPRESSION_LERC, - LERC_AVAILABLE, - lerc_compress, - lerc_decompress, - decompress, -) +from xrspatial.geotiff._compression import (COMPRESSION_LERC, LERC_AVAILABLE, decompress, + lerc_compress, lerc_decompress) pytestmark = pytest.mark.skipif( not LERC_AVAILABLE, @@ -89,8 +84,8 @@ class TestLERCWriteRoundTrip: """Write-read roundtrip using the TIFF writer with LERC compression.""" def test_tiled_float32(self, tmp_path): - from xrspatial.geotiff._writer import write from xrspatial.geotiff._reader import read_to_array + from xrspatial.geotiff._writer import write expected = np.arange(64, dtype=np.float32).reshape(8, 8) path = str(tmp_path / 'lerc_1052_tiled_f32.tif') @@ -100,8 +95,8 @@ def test_tiled_float32(self, tmp_path): np.testing.assert_array_equal(arr, expected) def test_tiled_uint8(self, tmp_path): - from xrspatial.geotiff._writer import write from xrspatial.geotiff._reader import read_to_array + from xrspatial.geotiff._writer import write expected = np.arange(64, dtype=np.uint8).reshape(8, 8) path = str(tmp_path / 'lerc_1052_tiled_u8.tif') @@ -111,8 +106,8 @@ def test_tiled_uint8(self, tmp_path): np.testing.assert_array_equal(arr, expected) def test_stripped_float32(self, tmp_path): - from xrspatial.geotiff._writer import write from xrspatial.geotiff._reader import read_to_array + from xrspatial.geotiff._writer import write expected = np.arange(64, dtype=np.float32).reshape(8, 8) path = str(tmp_path / 'lerc_1052_stripped.tif') @@ -123,6 +118,7 @@ def test_stripped_float32(self, tmp_path): def test_public_api_roundtrip(self, tmp_path): import xarray as xr + from xrspatial.geotiff import open_geotiff, to_geotiff data = np.arange(64, dtype=np.float32).reshape(8, 8) diff --git a/xrspatial/geotiff/tests/test_lerc_max_z_error.py b/xrspatial/geotiff/tests/test_lerc_max_z_error.py index 25cc53245..1524e8a63 100644 --- a/xrspatial/geotiff/tests/test_lerc_max_z_error.py +++ b/xrspatial/geotiff/tests/test_lerc_max_z_error.py @@ -16,7 +16,6 @@ from xrspatial.geotiff import open_geotiff, to_geotiff from xrspatial.geotiff._compression import LERC_AVAILABLE - pytestmark = pytest.mark.skipif( not LERC_AVAILABLE, reason="lerc not installed", diff --git a/xrspatial/geotiff/tests/test_lerc_valid_mask.py b/xrspatial/geotiff/tests/test_lerc_valid_mask.py index 0aa5aa934..5c708def4 100644 --- a/xrspatial/geotiff/tests/test_lerc_valid_mask.py +++ b/xrspatial/geotiff/tests/test_lerc_valid_mask.py @@ -25,11 +25,8 @@ lerc = pytest.importorskip("lerc") -from xrspatial.geotiff._compression import ( # noqa: E402 - LERC_AVAILABLE, - lerc_decompress, - lerc_decompress_with_mask, -) +from xrspatial.geotiff._compression import (LERC_AVAILABLE, lerc_decompress, # noqa: E402 + lerc_decompress_with_mask) pytestmark = pytest.mark.skipif( not LERC_AVAILABLE, @@ -145,8 +142,8 @@ class TestLercTiffRoundTripWithMask: """End-to-end TIFF round-trips that exercise the reader's mask merge.""" def test_float32_nan_nodata(self, tmp_path, lerc_writer_with_mask): - from xrspatial.geotiff._writer import write from xrspatial.geotiff._reader import read_to_array + from xrspatial.geotiff._writer import write arr = np.arange(1, 65, dtype=np.float32).reshape(8, 8) # Mask positions (0, 1) and (5, 4); the rest stays valid. @@ -174,8 +171,8 @@ def invalid_pred(a): f"value mismatch at ({r},{c})" def test_float32_sentinel_nodata(self, tmp_path, lerc_writer_with_mask): - from xrspatial.geotiff._writer import write from xrspatial.geotiff._reader import read_to_array + from xrspatial.geotiff._writer import write arr = np.arange(1, 65, dtype=np.float32).reshape(8, 8) invalid_positions = {(0, 1), (3, 3), (7, 7)} @@ -201,8 +198,8 @@ def invalid_pred(a): assert out[r, c] == arr[r, c] def test_uint16_sentinel_nodata(self, tmp_path, lerc_writer_with_mask): - from xrspatial.geotiff._writer import write from xrspatial.geotiff._reader import read_to_array + from xrspatial.geotiff._writer import write arr = (np.arange(1, 65, dtype=np.uint16) * 100).reshape(8, 8) invalid_positions = {(0, 1), (4, 4)} @@ -228,8 +225,8 @@ def invalid_pred(a): def test_no_mask_roundtrip_bitexact(self, tmp_path): """Sanity: an all-valid LERC file (no mask) still round-trips.""" - from xrspatial.geotiff._writer import write from xrspatial.geotiff._reader import read_to_array + from xrspatial.geotiff._writer import write arr = np.arange(64, dtype=np.float32).reshape(8, 8) path = str(tmp_path / "lerc_no_mask.tif") diff --git a/xrspatial/geotiff/tests/test_local_tile_byte_cap_1664.py b/xrspatial/geotiff/tests/test_local_tile_byte_cap_1664.py index 852616dc3..51dbe7e2c 100644 --- a/xrspatial/geotiff/tests/test_local_tile_byte_cap_1664.py +++ b/xrspatial/geotiff/tests/test_local_tile_byte_cap_1664.py @@ -16,16 +16,15 @@ import pytest import xarray as xr -from xrspatial.geotiff import open_geotiff, to_geotiff from xrspatial.geotiff import _reader as _reader_mod +from xrspatial.geotiff import open_geotiff, to_geotiff +from ._tiff_surgery import patch_byte_counts as _patch_byte_counts # noqa: E402 # --------------------------------------------------------------------------- # Helpers -- patch in-place IFD entries for tile / strip byte counts # --------------------------------------------------------------------------- -from ._tiff_surgery import patch_byte_counts as _patch_byte_counts # noqa: E402 - def _build_forged_tiled_cog(tmp_path, byte_count_value: int) -> str: """Write a real tiled COG, patch every TileByteCounts entry, return path.""" diff --git a/xrspatial/geotiff/tests/test_lowlevel_write_pushdown_2138.py b/xrspatial/geotiff/tests/test_lowlevel_write_pushdown_2138.py index 1d4431909..48d0d3d72 100644 --- a/xrspatial/geotiff/tests/test_lowlevel_write_pushdown_2138.py +++ b/xrspatial/geotiff/tests/test_lowlevel_write_pushdown_2138.py @@ -31,8 +31,8 @@ import xarray as xr from xrspatial.geotiff import to_geotiff -from xrspatial.geotiff._writer import _write, _write_streaming from xrspatial.geotiff._reader import _read_to_array +from xrspatial.geotiff._writer import _write, _write_streaming def _codec_available(name: str) -> bool: diff --git a/xrspatial/geotiff/tests/test_lz4.py b/xrspatial/geotiff/tests/test_lz4.py index 57dd77905..d58a30d4a 100644 --- a/xrspatial/geotiff/tests/test_lz4.py +++ b/xrspatial/geotiff/tests/test_lz4.py @@ -4,14 +4,8 @@ import numpy as np import pytest -from xrspatial.geotiff._compression import ( - COMPRESSION_LZ4, - LZ4_AVAILABLE, - lz4_compress, - lz4_decompress, - compress, - decompress, -) +from xrspatial.geotiff._compression import (COMPRESSION_LZ4, LZ4_AVAILABLE, compress, decompress, + lz4_compress, lz4_decompress) pytestmark = pytest.mark.skipif( not LZ4_AVAILABLE, @@ -54,8 +48,8 @@ class TestLZ4WriteRoundTrip: """Write-read roundtrip using the TIFF writer with LZ4 compression.""" def test_tiled_uint8(self, tmp_path): - from xrspatial.geotiff._writer import write from xrspatial.geotiff._reader import read_to_array + from xrspatial.geotiff._writer import write expected = np.arange(64, dtype=np.uint8).reshape(8, 8) path = str(tmp_path / 'lz4_1051_tiled_uint8.tif') @@ -65,8 +59,8 @@ def test_tiled_uint8(self, tmp_path): np.testing.assert_array_equal(arr, expected) def test_tiled_float32(self, tmp_path): - from xrspatial.geotiff._writer import write from xrspatial.geotiff._reader import read_to_array + from xrspatial.geotiff._writer import write expected = np.random.RandomState(1051).rand(16, 16).astype(np.float32) path = str(tmp_path / 'lz4_1051_tiled_f32.tif') @@ -76,8 +70,8 @@ def test_tiled_float32(self, tmp_path): np.testing.assert_array_equal(arr, expected) def test_stripped_uint8(self, tmp_path): - from xrspatial.geotiff._writer import write from xrspatial.geotiff._reader import read_to_array + from xrspatial.geotiff._writer import write expected = np.arange(64, dtype=np.uint8).reshape(8, 8) path = str(tmp_path / 'lz4_1051_stripped.tif') @@ -87,8 +81,8 @@ def test_stripped_uint8(self, tmp_path): np.testing.assert_array_equal(arr, expected) def test_with_predictor(self, tmp_path): - from xrspatial.geotiff._writer import write from xrspatial.geotiff._reader import read_to_array + from xrspatial.geotiff._writer import write expected = np.random.RandomState(1051).rand(16, 16).astype(np.float32) path = str(tmp_path / 'lz4_1051_predictor.tif') @@ -100,6 +94,7 @@ def test_with_predictor(self, tmp_path): def test_public_api_roundtrip(self, tmp_path): import xarray as xr + from xrspatial.geotiff import open_geotiff, to_geotiff data = np.arange(64, dtype=np.uint8).reshape(8, 8) diff --git a/xrspatial/geotiff/tests/test_lz4_compression_level_2026_05_11.py b/xrspatial/geotiff/tests/test_lz4_compression_level_2026_05_11.py index d9fa2b9b9..326e0c224 100644 --- a/xrspatial/geotiff/tests/test_lz4_compression_level_2026_05_11.py +++ b/xrspatial/geotiff/tests/test_lz4_compression_level_2026_05_11.py @@ -36,7 +36,6 @@ from xrspatial.geotiff import open_geotiff, to_geotiff - _HAS_LZ4 = importlib.util.find_spec("lz4") is not None _HAS_DASK = importlib.util.find_spec("dask") is not None diff --git a/xrspatial/geotiff/tests/test_mask_nodata_gpu_vrt_2052.py b/xrspatial/geotiff/tests/test_mask_nodata_gpu_vrt_2052.py index 916ea8229..610080422 100644 --- a/xrspatial/geotiff/tests/test_mask_nodata_gpu_vrt_2052.py +++ b/xrspatial/geotiff/tests/test_mask_nodata_gpu_vrt_2052.py @@ -33,13 +33,7 @@ import numpy as np import pytest -from xrspatial.geotiff import ( - open_geotiff, - read_geotiff_dask, - read_geotiff_gpu, - read_vrt, - write_vrt, -) +from xrspatial.geotiff import open_geotiff, read_geotiff_dask, read_geotiff_gpu, read_vrt, write_vrt from xrspatial.geotiff._writer import write diff --git a/xrspatial/geotiff/tests/test_max_cloud_bytes_dispatcher_silent_drop_2026_05_15.py b/xrspatial/geotiff/tests/test_max_cloud_bytes_dispatcher_silent_drop_2026_05_15.py index d820b431b..6179ca943 100644 --- a/xrspatial/geotiff/tests/test_max_cloud_bytes_dispatcher_silent_drop_2026_05_15.py +++ b/xrspatial/geotiff/tests/test_max_cloud_bytes_dispatcher_silent_drop_2026_05_15.py @@ -35,11 +35,7 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - open_geotiff, - to_geotiff, - write_vrt, -) +from xrspatial.geotiff import open_geotiff, to_geotiff, write_vrt def _skip_if_no_cupy_cuda(): diff --git a/xrspatial/geotiff/tests/test_metadata_round_trip_1484.py b/xrspatial/geotiff/tests/test_metadata_round_trip_1484.py index 871e3656a..7d5c21854 100644 --- a/xrspatial/geotiff/tests/test_metadata_round_trip_1484.py +++ b/xrspatial/geotiff/tests/test_metadata_round_trip_1484.py @@ -23,11 +23,11 @@ from xrspatial.geotiff import open_geotiff, to_geotiff from xrspatial.geotiff._writer import write - # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- + def _make_palette_uint8_tiff(path, pixels, palette_rgb16): """Write an 8-bit, 256-entry palette TIFF directly (no writer support for ColorMap on the write side). @@ -285,6 +285,7 @@ def test_crs_string_input_still_tolerated(self, tmp_path): """Backward compat: passing a WKT string in attrs['crs'] still works on the write side. open_geotiff turns it back into an int EPSG.""" import xarray as xr + from xrspatial.geotiff._geotags import _epsg_to_wkt wkt = _epsg_to_wkt(4326) if wkt is None: diff --git a/xrspatial/geotiff/tests/test_miniswhite_writer_roundtrip_1836.py b/xrspatial/geotiff/tests/test_miniswhite_writer_roundtrip_1836.py index f2837e753..a145e5bc5 100644 --- a/xrspatial/geotiff/tests/test_miniswhite_writer_roundtrip_1836.py +++ b/xrspatial/geotiff/tests/test_miniswhite_writer_roundtrip_1836.py @@ -175,9 +175,9 @@ def test_miniswhite_rejected_with_extra_tags_photometric_override(tmp_path): be refused -- otherwise the writer transforms pixels for one tag while the IFD advertises a different one. """ - from xrspatial.geotiff._writer import write as _eager_write from xrspatial.geotiff._dtypes import SHORT from xrspatial.geotiff._header import TAG_PHOTOMETRIC + from xrspatial.geotiff._writer import write as _eager_write arr = np.array([[10, 20]], dtype=np.uint8) path = str(tmp_path / 'extras_msw_1836.tif') # photometric kwarg defaults to MinIsBlack (1), extra_tags forces diff --git a/xrspatial/geotiff/tests/test_mixed_band_metadata_fail_closed_1987.py b/xrspatial/geotiff/tests/test_mixed_band_metadata_fail_closed_1987.py index c8eff7ec7..0c2038b8b 100644 --- a/xrspatial/geotiff/tests/test_mixed_band_metadata_fail_closed_1987.py +++ b/xrspatial/geotiff/tests/test_mixed_band_metadata_fail_closed_1987.py @@ -21,19 +21,11 @@ """ from __future__ import annotations -import os - import numpy as np import pytest -from xrspatial.geotiff import ( - GeoTIFFAmbiguousMetadataError, - MixedBandMetadataError, - open_geotiff, - read_geotiff_dask, - read_vrt, - to_geotiff, -) +from xrspatial.geotiff import (GeoTIFFAmbiguousMetadataError, MixedBandMetadataError, open_geotiff, + read_geotiff_dask, read_vrt, to_geotiff) from xrspatial.geotiff._writer import write diff --git a/xrspatial/geotiff/tests/test_mixed_bps.py b/xrspatial/geotiff/tests/test_mixed_bps.py index 529a03e82..75b64297d 100644 --- a/xrspatial/geotiff/tests/test_mixed_bps.py +++ b/xrspatial/geotiff/tests/test_mixed_bps.py @@ -104,7 +104,7 @@ def add_shorts(tag, vals): out.extend(payload) else: out.extend(struct.pack(f'{bo}I', - overflow_start + tag_overflow_offsets[tag])) + overflow_start + tag_overflow_offsets[tag])) out.extend(struct.pack(f'{bo}I', 0)) # next IFD = 0 out.extend(overflow_buf) out.extend(pixel_bytes) diff --git a/xrspatial/geotiff/tests/test_mixed_sample_format.py b/xrspatial/geotiff/tests/test_mixed_sample_format.py index a7b0526fb..e9126fc3f 100644 --- a/xrspatial/geotiff/tests/test_mixed_sample_format.py +++ b/xrspatial/geotiff/tests/test_mixed_sample_format.py @@ -105,7 +105,7 @@ def add_shorts(tag, vals): out.extend(payload) else: out.extend(struct.pack(f'{bo}I', - overflow_start + tag_overflow_offsets[tag])) + overflow_start + tag_overflow_offsets[tag])) out.extend(struct.pack(f'{bo}I', 0)) out.extend(overflow_buf) out.extend(pixel_bytes) diff --git a/xrspatial/geotiff/tests/test_mode_overview_perf.py b/xrspatial/geotiff/tests/test_mode_overview_perf.py index 5b794727f..1674096ce 100644 --- a/xrspatial/geotiff/tests/test_mode_overview_perf.py +++ b/xrspatial/geotiff/tests/test_mode_overview_perf.py @@ -35,9 +35,9 @@ def _mode_resample_reference(arr2d): @pytest.mark.parametrize("dtype", [np.uint8, np.uint16, np.int16, - np.int32, np.uint32, np.int64]) + np.int32, np.uint32, np.int64]) @pytest.mark.parametrize("shape", [(16, 16), (17, 19), (100, 101), - (1, 1), (2, 2), (3, 3), (64, 65)]) + (1, 1), (2, 2), (3, 3), (64, 65)]) def test_bit_exact_match_reference(dtype, shape): rng = np.random.default_rng(seed=42) info = np.iinfo(dtype) diff --git a/xrspatial/geotiff/tests/test_multi_tiepoint_validation_2117.py b/xrspatial/geotiff/tests/test_multi_tiepoint_validation_2117.py index b7f45e824..58d7b8188 100644 --- a/xrspatial/geotiff/tests/test_multi_tiepoint_validation_2117.py +++ b/xrspatial/geotiff/tests/test_multi_tiepoint_validation_2117.py @@ -21,12 +21,8 @@ import pytest -from xrspatial.geotiff._geotags import ( - TAG_MODEL_PIXEL_SCALE, - TAG_MODEL_TIEPOINT, - _extract_transform, - _validate_tiepoint_consistency, -) +from xrspatial.geotiff._geotags import (TAG_MODEL_PIXEL_SCALE, TAG_MODEL_TIEPOINT, + _extract_transform, _validate_tiepoint_consistency) from xrspatial.geotiff._header import IFD, IFDEntry diff --git a/xrspatial/geotiff/tests/test_no_georef_attr_migration_2133.py b/xrspatial/geotiff/tests/test_no_georef_attr_migration_2133.py index 536a99df2..a11c3a741 100644 --- a/xrspatial/geotiff/tests/test_no_georef_attr_migration_2133.py +++ b/xrspatial/geotiff/tests/test_no_georef_attr_migration_2133.py @@ -31,11 +31,7 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - NonUniformCoordsError, - open_geotiff, - to_geotiff, -) +from xrspatial.geotiff import NonUniformCoordsError, open_geotiff, to_geotiff from xrspatial.geotiff._coords import _NO_GEOREF_KEY, _has_no_georef_marker from xrspatial.geotiff._writer import write diff --git a/xrspatial/geotiff/tests/test_nodata_attr_aliases_1582.py b/xrspatial/geotiff/tests/test_nodata_attr_aliases_1582.py index ed614d5d2..70cf76de0 100644 --- a/xrspatial/geotiff/tests/test_nodata_attr_aliases_1582.py +++ b/xrspatial/geotiff/tests/test_nodata_attr_aliases_1582.py @@ -26,7 +26,6 @@ from xrspatial.geotiff import open_geotiff, to_geotiff - _SENTINEL = -9999.0 @@ -70,7 +69,7 @@ def test_nodatavals_list_resolves_to_nodata_tag(tmp_path, _arr_with_sentinel): def test_nodatavals_scalar_resolves_to_nodata_tag(tmp_path, - _arr_with_sentinel): + _arr_with_sentinel): """Single-band variant where the attr is a scalar, not a sequence.""" da = _da_float(_arr_with_sentinel, crs=4326, nodatavals=_SENTINEL) @@ -179,9 +178,10 @@ def _gpu_available() -> bool: ("_FillValue", _SENTINEL), ]) def test_gpu_writer_resolves_alias(tmp_path, _arr_with_sentinel, - attr_key, attr_value): + attr_key, attr_value): """The GPU write path (write_geotiff_gpu) honours the same aliases.""" import cupy + from xrspatial.geotiff import write_geotiff_gpu da = xr.DataArray( diff --git a/xrspatial/geotiff/tests/test_nodata_int64_precision_1847.py b/xrspatial/geotiff/tests/test_nodata_int64_precision_1847.py index 7ecfe14bb..9fdcfaca0 100644 --- a/xrspatial/geotiff/tests/test_nodata_int64_precision_1847.py +++ b/xrspatial/geotiff/tests/test_nodata_int64_precision_1847.py @@ -25,16 +25,9 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - open_geotiff, - read_geotiff_dask, - read_vrt, - to_geotiff, - write_vrt, -) +from xrspatial.geotiff import open_geotiff, read_geotiff_dask, read_vrt, to_geotiff, write_vrt from xrspatial.geotiff._geotags import _parse_nodata_str - # --------------------------------------------------------------------------- # Unit-level helper # --------------------------------------------------------------------------- diff --git a/xrspatial/geotiff/tests/test_nodata_lifecycle_parity_2211.py b/xrspatial/geotiff/tests/test_nodata_lifecycle_parity_2211.py index a79370104..bffe80ada 100644 --- a/xrspatial/geotiff/tests/test_nodata_lifecycle_parity_2211.py +++ b/xrspatial/geotiff/tests/test_nodata_lifecycle_parity_2211.py @@ -27,11 +27,11 @@ from xrspatial.geotiff._nodata import NodataLifecycle - # --------------------------------------------------------------------------- # Skip helpers (loud, not silent: a missing-cupy test reports the skip reason) # --------------------------------------------------------------------------- + def _gpu_available() -> bool: if importlib.util.find_spec("cupy") is None: return False @@ -716,6 +716,7 @@ def test_dask_records_dtype_cast(self, int_tif): def simple_vrt(tmp_path): """A trivial VRT wrapping a single GeoTIFF with a -9999 sentinel.""" import os + import xarray as xr from xrspatial.geotiff import to_geotiff @@ -832,8 +833,8 @@ def test_masked_nodata_false_attr_blocks_restore(self, tmp_path): @_gpu_only def test_gpu_writer_matches_eager(self, tmp_path): - import xarray as xr import cupy + import xarray as xr from xrspatial.geotiff import open_geotiff, to_geotiff diff --git a/xrspatial/geotiff/tests/test_nodata_nan_int_1774.py b/xrspatial/geotiff/tests/test_nodata_nan_int_1774.py index d31b22ba2..7a18a6e86 100644 --- a/xrspatial/geotiff/tests/test_nodata_nan_int_1774.py +++ b/xrspatial/geotiff/tests/test_nodata_nan_int_1774.py @@ -30,7 +30,6 @@ import numpy as np import pytest - from xrspatial.geotiff import open_geotiff, read_geotiff_dask diff --git a/xrspatial/geotiff/tests/test_nodata_out_of_range_1581.py b/xrspatial/geotiff/tests/test_nodata_out_of_range_1581.py index 4c03e7b35..59f8ab6b2 100644 --- a/xrspatial/geotiff/tests/test_nodata_out_of_range_1581.py +++ b/xrspatial/geotiff/tests/test_nodata_out_of_range_1581.py @@ -17,15 +17,8 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - open_geotiff, - read_geotiff_dask, - to_geotiff, -) -from xrspatial.geotiff._reader import ( - _int_nodata_in_range, - _resolve_masked_fill, -) +from xrspatial.geotiff import open_geotiff, read_geotiff_dask, to_geotiff +from xrspatial.geotiff._reader import _int_nodata_in_range, _resolve_masked_fill def _gpu_available() -> bool: @@ -143,6 +136,7 @@ def test_apply_nodata_mask_gpu_out_of_range_no_crash(): cupy with CUDA available. """ import cupy + from xrspatial.geotiff import _apply_nodata_mask_gpu arr_gpu = cupy.array([[1, 2, 3], [4, 5, 6]], dtype=cupy.uint16) diff --git a/xrspatial/geotiff/tests/test_nodata_semantics_split_1988.py b/xrspatial/geotiff/tests/test_nodata_semantics_split_1988.py index 26a898b00..d70e54d67 100644 --- a/xrspatial/geotiff/tests/test_nodata_semantics_split_1988.py +++ b/xrspatial/geotiff/tests/test_nodata_semantics_split_1988.py @@ -31,7 +31,6 @@ rasterio = pytest.importorskip("rasterio") from rasterio.transform import from_origin # noqa: E402 - _SENTINEL = -9999.0 @@ -593,6 +592,7 @@ def test_non_mapping_defaults_to_true(self): def test_stray_truthy_value_is_true(self): """Only literal ``False`` disables. Stray ``0`` / ``''`` stays True.""" from xrspatial.geotiff._attrs import _should_restore_nan_sentinel + # Anything other than literal False should keep the default # behaviour. ``0`` is falsy but is not the contract value. assert _should_restore_nan_sentinel({"masked_nodata": 0}) is True @@ -605,9 +605,10 @@ class TestWriterRoundTripEager: def test_masked_nodata_true_restores_sentinel(self, tmp_path): """Reader-style attrs (masked_nodata=True): NaN -> sentinel on write.""" - from xrspatial.geotiff import to_geotiff import xarray as xr + from xrspatial.geotiff import to_geotiff + path = tmp_path / "test_1988_writer_masked.tif" arr = np.array( [[1.0, 2.0, np.nan, 4.0], @@ -636,9 +637,10 @@ def test_masked_nodata_true_restores_sentinel(self, tmp_path): def test_masked_nodata_false_preserves_nan(self, tmp_path): """``masked_nodata=False`` -> NaN survives, no silent sentinel rewrite.""" - from xrspatial.geotiff import to_geotiff import xarray as xr + from xrspatial.geotiff import to_geotiff + path = tmp_path / "test_1988_writer_unmasked.tif" arr = np.array( [[1.0, 2.0, np.nan, 4.0], @@ -672,9 +674,10 @@ def test_masked_nodata_false_preserves_nan(self, tmp_path): def test_missing_masked_nodata_attr_restores_sentinel(self, tmp_path): """External DataArrays without the attr keep pre-#1988 behaviour.""" - from xrspatial.geotiff import to_geotiff import xarray as xr + from xrspatial.geotiff import to_geotiff + path = tmp_path / "test_1988_writer_no_attr.tif" arr = np.array( [[1.0, 2.0, np.nan, 4.0], @@ -732,10 +735,11 @@ def test_round_trip_preserves_masked_nodata_true(self, tmp_path): def test_dask_streaming_path_respects_flag(self, tmp_path): """Dask + tiled streaming write must honour the gate too.""" - from xrspatial.geotiff import to_geotiff import dask.array as da_mod import xarray as xr + from xrspatial.geotiff import to_geotiff + path = tmp_path / "test_1988_writer_dask.tif" # 32x32 with NaN sprinkled in -- the tiled streaming writer # requires tile_size to be a positive multiple of 16. @@ -783,6 +787,7 @@ class TestWriteStreamingRestoreSentinelKwarg: def test_streaming_restore_sentinel_true_rewrites(self, tmp_path): import dask.array as da_mod + from xrspatial.geotiff._writer import write_streaming path = tmp_path / "test_1988_stream_true.tif" @@ -804,6 +809,7 @@ def test_streaming_restore_sentinel_true_rewrites(self, tmp_path): def test_streaming_restore_sentinel_false_preserves_nan(self, tmp_path): import dask.array as da_mod + from xrspatial.geotiff._writer import write_streaming path = tmp_path / "test_1988_stream_false.tif" @@ -828,6 +834,7 @@ def test_streaming_restore_sentinel_false_preserves_nan(self, tmp_path): def test_streaming_default_is_true(self, tmp_path): """Default preserves pre-#1988 behaviour.""" import dask.array as da_mod + from xrspatial.geotiff._writer import write_streaming path = tmp_path / "test_1988_stream_default.tif" @@ -849,6 +856,7 @@ def test_streaming_default_is_true(self, tmp_path): def test_streaming_strip_layout_restore_false_preserves_nan(self, tmp_path): """The strip-write branch in ``write_streaming`` must honour the gate.""" import dask.array as da_mod + from xrspatial.geotiff._writer import write_streaming path = tmp_path / "test_1988_stream_strip_false.tif" @@ -941,6 +949,7 @@ class TestWriterGPU: def test_masked_nodata_false_preserves_nan_gpu(self, tmp_path): import cupy import xarray as xr + from xrspatial.geotiff import write_geotiff_gpu path = tmp_path / "test_1988_writer_gpu_unmasked.tif" @@ -972,6 +981,7 @@ def test_masked_nodata_false_preserves_nan_gpu(self, tmp_path): def test_masked_nodata_true_restores_sentinel_gpu(self, tmp_path): import cupy import xarray as xr + from xrspatial.geotiff import write_geotiff_gpu path = tmp_path / "test_1988_writer_gpu_masked.tif" diff --git a/xrspatial/geotiff/tests/test_non_uniform_coords_alias_2215.py b/xrspatial/geotiff/tests/test_non_uniform_coords_alias_2215.py index fc6b29f9a..053606928 100644 --- a/xrspatial/geotiff/tests/test_non_uniform_coords_alias_2215.py +++ b/xrspatial/geotiff/tests/test_non_uniform_coords_alias_2215.py @@ -38,11 +38,7 @@ import xarray as xr from xrspatial.geotiff import NonUniformCoordsError, to_geotiff -from xrspatial.geotiff._runtime import ( - _X_DIM_NAMES, - _Y_DIM_NAMES, - _resolve_spatial_coords, -) +from xrspatial.geotiff._runtime import _X_DIM_NAMES, _Y_DIM_NAMES, _resolve_spatial_coords def _da_with_alias_coords(y_name, x_name, *, y_coord=None, x_coord=None, diff --git a/xrspatial/geotiff/tests/test_nvcomp_batch_compress_batched_1712.py b/xrspatial/geotiff/tests/test_nvcomp_batch_compress_batched_1712.py index c4a1fc815..df1902f74 100644 --- a/xrspatial/geotiff/tests/test_nvcomp_batch_compress_batched_1712.py +++ b/xrspatial/geotiff/tests/test_nvcomp_batch_compress_batched_1712.py @@ -49,7 +49,7 @@ def _gpu_available() -> bool: _HAS_GPU = _gpu_available() # nvCOMP is the entry point that exercises this code path. -from xrspatial.geotiff import _gpu_decode +from xrspatial.geotiff import _gpu_decode # noqa: E402 needs_cupy = pytest.mark.skipif( not _HAS_GPU, reason="cupy + CUDA required" @@ -103,7 +103,7 @@ def test_gpu_write_roundtrip_after_batched_compress(compression): buffer would scramble tile order, which a round-trip equality check picks up immediately. """ - from xrspatial.geotiff import write_geotiff_gpu, open_geotiff + from xrspatial.geotiff import open_geotiff, write_geotiff_gpu rng = np.random.default_rng(seed=1712) arr_cpu = rng.random((512, 512), dtype=np.float32) @@ -143,7 +143,7 @@ def test_gpu_write_zero_tile_edge_case(): # Instead, exercise the public writer with a tiny single-tile # input and confirm the fast path does not crash. Real n_tiles==0 # never occurs via the writer (every image has at least one tile). - from xrspatial.geotiff import write_geotiff_gpu, open_geotiff + from xrspatial.geotiff import open_geotiff, write_geotiff_gpu arr_gpu = cupy.zeros((32, 32), dtype=cupy.float32) darr = xr.DataArray(arr_gpu, dims=["y", "x"]) with tempfile.TemporaryDirectory(prefix="nvcomp_batch_1712_") as td: diff --git a/xrspatial/geotiff/tests/test_nvcomp_decompress_cumsum_offsets_1950.py b/xrspatial/geotiff/tests/test_nvcomp_decompress_cumsum_offsets_1950.py index 040043843..4e0524490 100644 --- a/xrspatial/geotiff/tests/test_nvcomp_decompress_cumsum_offsets_1950.py +++ b/xrspatial/geotiff/tests/test_nvcomp_decompress_cumsum_offsets_1950.py @@ -130,6 +130,7 @@ def test_nvcomp_batch_decompress_roundtrip_1950(): pytest.skip("CUDA device not available") import xarray as xr + from xrspatial.geotiff import open_geotiff, to_geotiff rng = np.random.RandomState(1950) diff --git a/xrspatial/geotiff/tests/test_nvcomp_from_device_bufs_single_alloc_1659.py b/xrspatial/geotiff/tests/test_nvcomp_from_device_bufs_single_alloc_1659.py index d57f0e60a..1144f58c1 100644 --- a/xrspatial/geotiff/tests/test_nvcomp_from_device_bufs_single_alloc_1659.py +++ b/xrspatial/geotiff/tests/test_nvcomp_from_device_bufs_single_alloc_1659.py @@ -61,6 +61,7 @@ def test_no_nvcomp_lib_returns_none(monkeypatch): deeper in the function. """ import cupy + from xrspatial.geotiff import _gpu_decode monkeypatch.setattr(_gpu_decode, "_get_nvcomp", lambda: None) @@ -79,6 +80,7 @@ def test_memory_guard_runs_with_full_decomp_size(monkeypatch): removed the guard would surface as an opaque CUDA OOM instead. """ import cupy + from xrspatial.geotiff import _gpu_decode seen = {"total_bytes": None, "what": None, "called": False} @@ -175,6 +177,7 @@ def test_no_orphan_decomp_buffers_after_call(monkeypatch): and inspecting it confirms ``result.size == n_tiles * tile_bytes``. """ import cupy + from xrspatial.geotiff import _gpu_decode # Stub the nvCOMP entry points so the decompress "succeeds" without an diff --git a/xrspatial/geotiff/tests/test_open_geotiff_max_cloud_bytes_annot_2106.py b/xrspatial/geotiff/tests/test_open_geotiff_max_cloud_bytes_annot_2106.py index 3bc613441..3d296c685 100644 --- a/xrspatial/geotiff/tests/test_open_geotiff_max_cloud_bytes_annot_2106.py +++ b/xrspatial/geotiff/tests/test_open_geotiff_max_cloud_bytes_annot_2106.py @@ -18,16 +18,8 @@ import pytest -from xrspatial.geotiff import ( - open_geotiff, - read_geotiff_dask, - read_geotiff_gpu, - read_vrt, - to_geotiff, - write_geotiff_gpu, - write_vrt, -) - +from xrspatial.geotiff import (open_geotiff, read_geotiff_dask, read_geotiff_gpu, read_vrt, + to_geotiff, write_geotiff_gpu, write_vrt) PUBLIC_ENTRY_POINTS = ( open_geotiff, diff --git a/xrspatial/geotiff/tests/test_open_geotiff_missing_sources_1810.py b/xrspatial/geotiff/tests/test_open_geotiff_missing_sources_1810.py index 5e23bacdb..c39fcf600 100644 --- a/xrspatial/geotiff/tests/test_open_geotiff_missing_sources_1810.py +++ b/xrspatial/geotiff/tests/test_open_geotiff_missing_sources_1810.py @@ -17,12 +17,7 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - GeoTIFFFallbackWarning, - open_geotiff, - to_geotiff, - write_vrt, -) +from xrspatial.geotiff import GeoTIFFFallbackWarning, open_geotiff, to_geotiff, write_vrt def _write_missing_source_vrt(path): diff --git a/xrspatial/geotiff/tests/test_overview_filter.py b/xrspatial/geotiff/tests/test_overview_filter.py index f015762bf..342ef7c9d 100644 --- a/xrspatial/geotiff/tests/test_overview_filter.py +++ b/xrspatial/geotiff/tests/test_overview_filter.py @@ -14,11 +14,8 @@ tifffile = pytest.importorskip("tifffile") from xrspatial.geotiff import open_geotiff # noqa: E402 -from xrspatial.geotiff._header import ( # noqa: E402 - parse_all_ifds, - parse_header, - select_overview_ifd, -) +from xrspatial.geotiff._header import (parse_all_ifds, parse_header, # noqa: E402 + select_overview_ifd) def _write_tiff_with_mask(path, full_res, mask, overview): diff --git a/xrspatial/geotiff/tests/test_overview_geo_inheritance_1640.py b/xrspatial/geotiff/tests/test_overview_geo_inheritance_1640.py index 3e3b27fb8..76d972690 100644 --- a/xrspatial/geotiff/tests/test_overview_geo_inheritance_1640.py +++ b/xrspatial/geotiff/tests/test_overview_geo_inheritance_1640.py @@ -48,7 +48,7 @@ def _make_cog_with_overviews(path: str) -> xr.DataArray: """ arr = np.arange(1024 * 1024, dtype=np.float32).reshape(1024, 1024) y = np.arange(1024, dtype=np.float64) * (-0.5) + 200.0 - x = np.arange(1024, dtype=np.float64) * 0.5 + 100.0 + x = np.arange(1024, dtype=np.float64) * 0.5 + 100.0 da = xr.DataArray(arr, dims=['y', 'x'], coords={'y': y, 'x': x}, attrs={'crs': 4326}) diff --git a/xrspatial/geotiff/tests/test_overview_level_type_validation_2074.py b/xrspatial/geotiff/tests/test_overview_level_type_validation_2074.py index 5cd7354f5..46edca407 100644 --- a/xrspatial/geotiff/tests/test_overview_level_type_validation_2074.py +++ b/xrspatial/geotiff/tests/test_overview_level_type_validation_2074.py @@ -20,7 +20,6 @@ import numpy as np import pytest - tifffile = pytest.importorskip("tifffile") diff --git a/xrspatial/geotiff/tests/test_overview_level_validation_backends_2160.py b/xrspatial/geotiff/tests/test_overview_level_validation_backends_2160.py index f944b58c9..fb5b1479b 100644 --- a/xrspatial/geotiff/tests/test_overview_level_validation_backends_2160.py +++ b/xrspatial/geotiff/tests/test_overview_level_validation_backends_2160.py @@ -20,7 +20,6 @@ import numpy as np import pytest - tifffile = pytest.importorskip("tifffile") diff --git a/xrspatial/geotiff/tests/test_overview_levels_decimation_factors_1766.py b/xrspatial/geotiff/tests/test_overview_levels_decimation_factors_1766.py index 05311c863..ea40bdb0c 100644 --- a/xrspatial/geotiff/tests/test_overview_levels_decimation_factors_1766.py +++ b/xrspatial/geotiff/tests/test_overview_levels_decimation_factors_1766.py @@ -15,11 +15,10 @@ import pytest from xrspatial.geotiff import to_geotiff -from xrspatial.geotiff._header import parse_header, parse_all_ifds +from xrspatial.geotiff._header import parse_all_ifds, parse_header from xrspatial.geotiff._reader import _FileSource from xrspatial.geotiff._writer import _validate_overview_levels, write - # A unique stem so temp paths cannot collide with sibling tests running # in parallel worktrees (rockout convention). STEM = "issue_1766" diff --git a/xrspatial/geotiff/tests/test_overview_nodata_inheritance_1739.py b/xrspatial/geotiff/tests/test_overview_nodata_inheritance_1739.py index ef3da6b83..64b907840 100644 --- a/xrspatial/geotiff/tests/test_overview_nodata_inheritance_1739.py +++ b/xrspatial/geotiff/tests/test_overview_nodata_inheritance_1739.py @@ -30,8 +30,8 @@ import xarray as xr from xrspatial.geotiff import open_geotiff, to_geotiff -from xrspatial.geotiff._writer import write from xrspatial.geotiff._geotags import GeoTransform +from xrspatial.geotiff._writer import write def _gpu_available() -> bool: @@ -274,10 +274,6 @@ def test_overview_with_own_nodata_keeps_own_value(tmp_path): own value" branch by simulating it directly against ``extract_geo_info_with_overview_inheritance``. """ - from xrspatial.geotiff._geotags import ( - extract_geo_info_with_overview_inheritance, - GeoInfo, GeoTransform as _GT, - ) # Drive the helper with fake IFD objects + a fake base IFD to # avoid having to hand-pack a TIFF with re-declared overview # nodata. The helper only inspects ``ifd.subfile_type`` / @@ -285,6 +281,9 @@ def test_overview_with_own_nodata_keeps_own_value(tmp_path): # ``extract_geo_info``; we monkeypatch that to return controlled # GeoInfo instances. import xrspatial.geotiff._geotags as _gt_mod + from xrspatial.geotiff._geotags import GeoInfo + from xrspatial.geotiff._geotags import GeoTransform as _GT + from xrspatial.geotiff._geotags import extract_geo_info_with_overview_inheritance class _StubIFD: def __init__(self, subfile_type, width, height): diff --git a/xrspatial/geotiff/tests/test_overview_pixel_is_point_1642.py b/xrspatial/geotiff/tests/test_overview_pixel_is_point_1642.py index 30a194a3e..26a6dba06 100644 --- a/xrspatial/geotiff/tests/test_overview_pixel_is_point_1642.py +++ b/xrspatial/geotiff/tests/test_overview_pixel_is_point_1642.py @@ -23,13 +23,8 @@ import xarray as xr from xrspatial.geotiff import open_geotiff, to_geotiff -from xrspatial.geotiff._geotags import ( - GeoTransform, - RASTER_PIXEL_IS_AREA, - RASTER_PIXEL_IS_POINT, - GeoInfo, - extract_geo_info_with_overview_inheritance, -) +from xrspatial.geotiff._geotags import (RASTER_PIXEL_IS_AREA, RASTER_PIXEL_IS_POINT, GeoInfo, + GeoTransform, extract_geo_info_with_overview_inheritance) def _gpu_available() -> bool: diff --git a/xrspatial/geotiff/tests/test_packbits_jit_2048.py b/xrspatial/geotiff/tests/test_packbits_jit_2048.py index b46a0ac8e..4d23e86e7 100644 --- a/xrspatial/geotiff/tests/test_packbits_jit_2048.py +++ b/xrspatial/geotiff/tests/test_packbits_jit_2048.py @@ -15,11 +15,7 @@ import pytest -from xrspatial.geotiff._compression import ( - packbits_compress, - packbits_decompress, -) - +from xrspatial.geotiff._compression import packbits_compress, packbits_decompress # -- Bit-exact decode against known PackBits encodings ----------------------- diff --git a/xrspatial/geotiff/tests/test_packbits_jit_2049.py b/xrspatial/geotiff/tests/test_packbits_jit_2049.py index 396a200fb..3f4ab9df7 100644 --- a/xrspatial/geotiff/tests/test_packbits_jit_2049.py +++ b/xrspatial/geotiff/tests/test_packbits_jit_2049.py @@ -8,11 +8,8 @@ import numpy as np import pytest -from xrspatial.geotiff._compression import ( - packbits_compress, - packbits_decompress, -) -from xrspatial.geotiff._compression import _packbits_encode_kernel +from xrspatial.geotiff._compression import (_packbits_encode_kernel, packbits_compress, + packbits_decompress) def _roundtrip(data: bytes) -> None: diff --git a/xrspatial/geotiff/tests/test_parallel_strip_decode_2100.py b/xrspatial/geotiff/tests/test_parallel_strip_decode_2100.py index b43f64d6f..f185fad41 100644 --- a/xrspatial/geotiff/tests/test_parallel_strip_decode_2100.py +++ b/xrspatial/geotiff/tests/test_parallel_strip_decode_2100.py @@ -20,9 +20,9 @@ import pytest import xarray as xr -from xrspatial.geotiff import to_geotiff -from xrspatial.geotiff import _reader as _reader_mod from xrspatial.geotiff import _decode as _decode_mod +from xrspatial.geotiff import _reader as _reader_mod +from xrspatial.geotiff import to_geotiff from xrspatial.geotiff._reader import read_to_array diff --git a/xrspatial/geotiff/tests/test_parallel_strip_decode_sparse_2100.py b/xrspatial/geotiff/tests/test_parallel_strip_decode_sparse_2100.py index e9b136b2e..e6eb8abbe 100644 --- a/xrspatial/geotiff/tests/test_parallel_strip_decode_sparse_2100.py +++ b/xrspatial/geotiff/tests/test_parallel_strip_decode_sparse_2100.py @@ -36,9 +36,7 @@ import concurrent.futures import http.server -import os import socket -import tempfile import threading from unittest.mock import patch @@ -51,13 +49,13 @@ # under test do not depend on rasterio at runtime. rasterio = pytest.importorskip("rasterio") -from xrspatial.geotiff._reader import read_to_array # noqa: E402 -from xrspatial.geotiff import _reader as _reader_mod # noqa: E402 from xrspatial.geotiff import _decode as _decode_mod # noqa: E402 - +from xrspatial.geotiff import _reader as _reader_mod # noqa: E402 +from xrspatial.geotiff._reader import read_to_array # noqa: E402 # Local-strip helpers ------------------------------------------------------- + def _write_sparse_stripped_large( path: str, *, diff --git a/xrspatial/geotiff/tests/test_parallel_writer_1800.py b/xrspatial/geotiff/tests/test_parallel_writer_1800.py index 62a576024..3ef5f6c81 100644 --- a/xrspatial/geotiff/tests/test_parallel_writer_1800.py +++ b/xrspatial/geotiff/tests/test_parallel_writer_1800.py @@ -4,20 +4,10 @@ import numpy as np import pytest -from xrspatial.geotiff._writer import ( - _PARALLEL_MIN_BYTES, - _write_stripped, - _write_tiled, - write, -) +from xrspatial.geotiff._compression import (_HAVE_LIBDEFLATE, COMPRESSION_DEFLATE, COMPRESSION_NONE, + deflate_compress) from xrspatial.geotiff._reader import read_to_array -from xrspatial.geotiff._compression import ( - COMPRESSION_DEFLATE, - COMPRESSION_NONE, - _HAVE_LIBDEFLATE, - deflate_compress, -) - +from xrspatial.geotiff._writer import _PARALLEL_MIN_BYTES, _write_stripped, _write_tiled, write # -- Strip writer parity -------------------------------------------------- diff --git a/xrspatial/geotiff/tests/test_parse_all_ifds_malformed_chain_1863.py b/xrspatial/geotiff/tests/test_parse_all_ifds_malformed_chain_1863.py index 9e6d0219f..0f9828895 100644 --- a/xrspatial/geotiff/tests/test_parse_all_ifds_malformed_chain_1863.py +++ b/xrspatial/geotiff/tests/test_parse_all_ifds_malformed_chain_1863.py @@ -13,12 +13,7 @@ import pytest -from xrspatial.geotiff._header import ( - TAG_IMAGE_WIDTH, - TIFFHeader, - parse_all_ifds, - parse_header, -) +from xrspatial.geotiff._header import TAG_IMAGE_WIDTH, TIFFHeader, parse_all_ifds, parse_header def _build_single_ifd_with_next_offset_1863(next_offset: int, diff --git a/xrspatial/geotiff/tests/test_parse_ifd_bounds.py b/xrspatial/geotiff/tests/test_parse_ifd_bounds.py index 34c80da70..63f83320c 100644 --- a/xrspatial/geotiff/tests/test_parse_ifd_bounds.py +++ b/xrspatial/geotiff/tests/test_parse_ifd_bounds.py @@ -28,15 +28,9 @@ from xrspatial.geotiff import _header from xrspatial.geotiff._dtypes import DOUBLE, LONG, SHORT -from xrspatial.geotiff._header import ( - MAX_IFD_ENTRY_BYTES, - MAX_IFD_ENTRY_COUNT, - TAG_IMAGE_WIDTH, - TAG_TILE_BYTE_COUNTS, - TAG_TILE_OFFSETS, - parse_header, - parse_ifd, -) +from xrspatial.geotiff._header import (MAX_IFD_ENTRY_BYTES, MAX_IFD_ENTRY_COUNT, TAG_IMAGE_WIDTH, + TAG_TILE_BYTE_COUNTS, TAG_TILE_OFFSETS, parse_header, + parse_ifd) def _build_tiff( diff --git a/xrspatial/geotiff/tests/test_photometric_kwarg_1769.py b/xrspatial/geotiff/tests/test_photometric_kwarg_1769.py index 850570ce1..88506b56a 100644 --- a/xrspatial/geotiff/tests/test_photometric_kwarg_1769.py +++ b/xrspatial/geotiff/tests/test_photometric_kwarg_1769.py @@ -29,12 +29,7 @@ from xrspatial.geotiff import to_geotiff from xrspatial.geotiff._dtypes import SHORT -from xrspatial.geotiff._header import ( - TAG_EXTRA_SAMPLES, - TAG_PHOTOMETRIC, - parse_header, - parse_ifd, -) +from xrspatial.geotiff._header import TAG_EXTRA_SAMPLES, TAG_PHOTOMETRIC, parse_header, parse_ifd def _read_primary_ifd(path: str): diff --git a/xrspatial/geotiff/tests/test_pixel_array_count_cap_1901.py b/xrspatial/geotiff/tests/test_pixel_array_count_cap_1901.py index 3a934a040..05939936d 100644 --- a/xrspatial/geotiff/tests/test_pixel_array_count_cap_1901.py +++ b/xrspatial/geotiff/tests/test_pixel_array_count_cap_1901.py @@ -24,24 +24,11 @@ from xrspatial.geotiff import _header from xrspatial.geotiff._dtypes import LONG, SHORT -from xrspatial.geotiff._header import ( - MAX_PIXEL_ARRAY_COUNT, - TAG_BITS_PER_SAMPLE, - TAG_COLORMAP, - TAG_IMAGE_LENGTH, - TAG_IMAGE_WIDTH, - TAG_PLANAR_CONFIG, - TAG_ROWS_PER_STRIP, - TAG_SAMPLES_PER_PIXEL, - TAG_STRIP_BYTE_COUNTS, - TAG_STRIP_OFFSETS, - TAG_TILE_BYTE_COUNTS, - TAG_TILE_LENGTH, - TAG_TILE_OFFSETS, - TAG_TILE_WIDTH, - parse_header, - parse_ifd, -) +from xrspatial.geotiff._header import (MAX_PIXEL_ARRAY_COUNT, TAG_BITS_PER_SAMPLE, TAG_COLORMAP, + TAG_IMAGE_LENGTH, TAG_IMAGE_WIDTH, TAG_PLANAR_CONFIG, + TAG_ROWS_PER_STRIP, TAG_SAMPLES_PER_PIXEL, + TAG_STRIP_BYTE_COUNTS, TAG_STRIP_OFFSETS, TAG_TILE_LENGTH, + TAG_TILE_OFFSETS, TAG_TILE_WIDTH, parse_header, parse_ifd) def _short_bytes(v: int) -> bytes: diff --git a/xrspatial/geotiff/tests/test_planar_multiband.py b/xrspatial/geotiff/tests/test_planar_multiband.py index 7162fb459..1d6ed9c67 100644 --- a/xrspatial/geotiff/tests/test_planar_multiband.py +++ b/xrspatial/geotiff/tests/test_planar_multiband.py @@ -31,7 +31,6 @@ import numpy as np import pytest - tifffile = pytest.importorskip("tifffile") diff --git a/xrspatial/geotiff/tests/test_polish_1488.py b/xrspatial/geotiff/tests/test_polish_1488.py index 132a02654..39fce7552 100644 --- a/xrspatial/geotiff/tests/test_polish_1488.py +++ b/xrspatial/geotiff/tests/test_polish_1488.py @@ -23,15 +23,15 @@ import numpy as np import pytest -from xrspatial.geotiff import to_geotiff, read_geotiff_dask, write_vrt +from xrspatial.geotiff import read_geotiff_dask, to_geotiff, write_vrt from xrspatial.geotiff._reader import _MmapCache, read_to_array from xrspatial.geotiff._writer import _MAX_OVERVIEW_LEVELS, write - # --------------------------------------------------------------------------- # C-1: early compression validation # --------------------------------------------------------------------------- + class TestC1CompressionValidation: def test_unknown_compression_raises_at_top(self, tmp_path): arr = np.arange(64, dtype=np.float32).reshape(8, 8) @@ -407,6 +407,7 @@ def memGetInfo(): def test_helper_noop_for_zero_or_negative(self): from xrspatial.geotiff import _gpu_decode + # Should not even try to query CUDA. _gpu_decode._check_gpu_memory(0, what="empty") _gpu_decode._check_gpu_memory(-100, what="negative") @@ -414,9 +415,10 @@ def test_helper_noop_for_zero_or_negative(self): def test_helper_silent_when_cupy_unavailable(self, monkeypatch): # When cupy isn't importable, the helper falls through silently # so the real allocation can produce its own error. - from xrspatial.geotiff import _gpu_decode import builtins + from xrspatial.geotiff import _gpu_decode + real_import = builtins.__import__ def fake_import(name, *args, **kwargs): @@ -447,7 +449,7 @@ def test_auto_overview_capped(self, tmp_path): cog=True) # Re-open and count IFDs (overviews + full-res). - from xrspatial.geotiff._header import parse_header, parse_all_ifds + from xrspatial.geotiff._header import parse_all_ifds, parse_header from xrspatial.geotiff._reader import _FileSource src = _FileSource(path) try: @@ -468,7 +470,7 @@ def test_explicit_overview_levels_not_capped(self, tmp_path): write(arr, path, compression='none', tiled=True, tile_size=64, cog=True, overview_levels=[2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]) - from xrspatial.geotiff._header import parse_header, parse_all_ifds + from xrspatial.geotiff._header import parse_all_ifds, parse_header from xrspatial.geotiff._reader import _FileSource src = _FileSource(path) try: diff --git a/xrspatial/geotiff/tests/test_predictor2_int8.py b/xrspatial/geotiff/tests/test_predictor2_int8.py index 05dd10184..23be9f99e 100644 --- a/xrspatial/geotiff/tests/test_predictor2_int8.py +++ b/xrspatial/geotiff/tests/test_predictor2_int8.py @@ -27,7 +27,6 @@ import numpy as np import pytest - tifffile = pytest.importorskip("tifffile") diff --git a/xrspatial/geotiff/tests/test_predictor3_int_dtype_1933.py b/xrspatial/geotiff/tests/test_predictor3_int_dtype_1933.py index 5f28eba99..92f42b1c0 100644 --- a/xrspatial/geotiff/tests/test_predictor3_int_dtype_1933.py +++ b/xrspatial/geotiff/tests/test_predictor3_int_dtype_1933.py @@ -19,24 +19,12 @@ from xrspatial.geotiff import open_geotiff from xrspatial.geotiff._compression import COMPRESSION_NONE from xrspatial.geotiff._dtypes import LONG, SHORT, numpy_to_tiff_dtype -from xrspatial.geotiff._header import ( - TAG_BITS_PER_SAMPLE, - TAG_COMPRESSION, - TAG_IMAGE_LENGTH, - TAG_IMAGE_WIDTH, - TAG_PHOTOMETRIC, - TAG_PREDICTOR, - TAG_ROWS_PER_STRIP, - TAG_SAMPLE_FORMAT, - TAG_SAMPLES_PER_PIXEL, - TAG_STRIP_BYTE_COUNTS, - TAG_STRIP_OFFSETS, -) +from xrspatial.geotiff._header import (TAG_BITS_PER_SAMPLE, TAG_COMPRESSION, TAG_IMAGE_LENGTH, + TAG_IMAGE_WIDTH, TAG_PHOTOMETRIC, TAG_PREDICTOR, + TAG_ROWS_PER_STRIP, TAG_SAMPLE_FORMAT, TAG_SAMPLES_PER_PIXEL, + TAG_STRIP_BYTE_COUNTS, TAG_STRIP_OFFSETS) from xrspatial.geotiff._validation import _validate_predictor_sample_format -from xrspatial.geotiff._writer import ( - _assemble_standard_layout, - _write_stripped, -) +from xrspatial.geotiff._writer import _assemble_standard_layout, _write_stripped def _build_predictor3_uint32_tiff(arr: np.ndarray) -> bytes: diff --git a/xrspatial/geotiff/tests/test_predictor3_int_dtype_gpu_1933.py b/xrspatial/geotiff/tests/test_predictor3_int_dtype_gpu_1933.py index 26b370629..456ab424a 100644 --- a/xrspatial/geotiff/tests/test_predictor3_int_dtype_gpu_1933.py +++ b/xrspatial/geotiff/tests/test_predictor3_int_dtype_gpu_1933.py @@ -33,27 +33,13 @@ from xrspatial.geotiff._compression import COMPRESSION_NONE from xrspatial.geotiff._dtypes import LONG, SHORT, numpy_to_tiff_dtype -from xrspatial.geotiff._header import ( - TAG_BITS_PER_SAMPLE, - TAG_COMPRESSION, - TAG_IMAGE_LENGTH, - TAG_IMAGE_WIDTH, - TAG_PHOTOMETRIC, - TAG_PREDICTOR, - TAG_SAMPLE_FORMAT, - TAG_SAMPLES_PER_PIXEL, - TAG_STRIP_BYTE_COUNTS, - TAG_STRIP_OFFSETS, - TAG_ROWS_PER_STRIP, - TAG_TILE_BYTE_COUNTS, - TAG_TILE_LENGTH, - TAG_TILE_OFFSETS, - TAG_TILE_WIDTH, -) -from xrspatial.geotiff._writer import ( - _assemble_standard_layout, - _write_stripped, -) +from xrspatial.geotiff._header import (TAG_BITS_PER_SAMPLE, TAG_COMPRESSION, TAG_IMAGE_LENGTH, + TAG_IMAGE_WIDTH, TAG_PHOTOMETRIC, TAG_PREDICTOR, + TAG_ROWS_PER_STRIP, TAG_SAMPLE_FORMAT, TAG_SAMPLES_PER_PIXEL, + TAG_STRIP_BYTE_COUNTS, TAG_STRIP_OFFSETS, + TAG_TILE_BYTE_COUNTS, TAG_TILE_LENGTH, TAG_TILE_OFFSETS, + TAG_TILE_WIDTH) +from xrspatial.geotiff._writer import _assemble_standard_layout, _write_stripped def _gpu_available() -> bool: diff --git a/xrspatial/geotiff/tests/test_predictor_multisample.py b/xrspatial/geotiff/tests/test_predictor_multisample.py index 273d28d53..b99703c83 100644 --- a/xrspatial/geotiff/tests/test_predictor_multisample.py +++ b/xrspatial/geotiff/tests/test_predictor_multisample.py @@ -20,9 +20,7 @@ """ from __future__ import annotations -import os import struct -import tempfile import zlib import numpy as np @@ -31,7 +29,6 @@ from xrspatial.geotiff import open_geotiff, to_geotiff - try: import cupy # noqa: F401 from numba import cuda @@ -238,7 +235,7 @@ def add_longs(tag, vals): patched.append((tag, 4, 1, new_raw)) else: new_raw = struct.pack(f'{bo}{len(strip_offsets)}I', - *strip_offsets) + *strip_offsets) patched.append((tag, 4, len(strip_offsets), new_raw)) else: patched.append((tag, typ, count, raw)) @@ -272,7 +269,7 @@ def add_longs(tag, vals): patched2.append((tag, 4, 1, new_raw)) else: new_raw = struct.pack(f'{bo}{len(strip_offsets)}I', - *strip_offsets) + *strip_offsets) patched2.append((tag, 4, len(strip_offsets), new_raw)) else: patched2.append((tag, typ, count, raw)) diff --git a/xrspatial/geotiff/tests/test_read_entry_points_doc_param_parity_2274.py b/xrspatial/geotiff/tests/test_read_entry_points_doc_param_parity_2274.py index a77f9757f..3a52abd80 100644 --- a/xrspatial/geotiff/tests/test_read_entry_points_doc_param_parity_2274.py +++ b/xrspatial/geotiff/tests/test_read_entry_points_doc_param_parity_2274.py @@ -22,13 +22,7 @@ import pytest -from xrspatial.geotiff import ( - open_geotiff, - read_geotiff_dask, - read_geotiff_gpu, - read_vrt, -) - +from xrspatial.geotiff import open_geotiff, read_geotiff_dask, read_geotiff_gpu, read_vrt READ_ENTRY_POINTS = ( open_geotiff, diff --git a/xrspatial/geotiff/tests/test_read_geotiff_dask_vrt_kwargs_1795.py b/xrspatial/geotiff/tests/test_read_geotiff_dask_vrt_kwargs_1795.py index eb148a7e6..a24887d0d 100644 --- a/xrspatial/geotiff/tests/test_read_geotiff_dask_vrt_kwargs_1795.py +++ b/xrspatial/geotiff/tests/test_read_geotiff_dask_vrt_kwargs_1795.py @@ -6,7 +6,7 @@ import numpy as np import pytest -from xrspatial.geotiff import to_geotiff, read_geotiff_dask +from xrspatial.geotiff import read_geotiff_dask, to_geotiff def _write_vrt(vrt_path, source_name, *, bands=1): diff --git a/xrspatial/geotiff/tests/test_read_vrt_default_missing_sources_1860.py b/xrspatial/geotiff/tests/test_read_vrt_default_missing_sources_1860.py index 515d1afad..7f278915f 100644 --- a/xrspatial/geotiff/tests/test_read_vrt_default_missing_sources_1860.py +++ b/xrspatial/geotiff/tests/test_read_vrt_default_missing_sources_1860.py @@ -12,8 +12,7 @@ import pytest -from xrspatial.geotiff import open_geotiff, read_vrt -from xrspatial.geotiff import GeoTIFFFallbackWarning +from xrspatial.geotiff import GeoTIFFFallbackWarning, open_geotiff, read_vrt def _write_missing_source_vrt(path): diff --git a/xrspatial/geotiff/tests/test_read_vrt_lazy_chunks_1798.py b/xrspatial/geotiff/tests/test_read_vrt_lazy_chunks_1798.py index 4969a02c1..f60162b5a 100644 --- a/xrspatial/geotiff/tests/test_read_vrt_lazy_chunks_1798.py +++ b/xrspatial/geotiff/tests/test_read_vrt_lazy_chunks_1798.py @@ -7,7 +7,7 @@ import numpy as np import pytest -from xrspatial.geotiff import to_geotiff, read_vrt +from xrspatial.geotiff import read_vrt, to_geotiff def _write_vrt(vrt_path, source_name): diff --git a/xrspatial/geotiff/tests/test_reader.py b/xrspatial/geotiff/tests/test_reader.py index ff2296a36..7dc3ade25 100644 --- a/xrspatial/geotiff/tests/test_reader.py +++ b/xrspatial/geotiff/tests/test_reader.py @@ -1,16 +1,13 @@ """Tests for the TIFF reader.""" from __future__ import annotations -import os -import tempfile - import numpy as np import pytest -from xrspatial.geotiff._reader import read_to_array, _read_strips, _read_tiles -from xrspatial.geotiff._header import parse_header, parse_all_ifds from xrspatial.geotiff._dtypes import tiff_dtype_to_numpy -from xrspatial.geotiff._geotags import extract_geo_info +from xrspatial.geotiff._header import parse_all_ifds, parse_header +from xrspatial.geotiff._reader import _read_strips, _read_tiles, read_to_array + from .conftest import make_minimal_tiff diff --git a/xrspatial/geotiff/tests/test_reader_kwarg_order_1935.py b/xrspatial/geotiff/tests/test_reader_kwarg_order_1935.py index 36378a63e..eb93e2ead 100644 --- a/xrspatial/geotiff/tests/test_reader_kwarg_order_1935.py +++ b/xrspatial/geotiff/tests/test_reader_kwarg_order_1935.py @@ -21,13 +21,7 @@ import inspect -from xrspatial.geotiff import ( - open_geotiff, - read_geotiff_dask, - read_geotiff_gpu, - read_vrt, -) - +from xrspatial.geotiff import open_geotiff, read_geotiff_dask, read_geotiff_gpu, read_vrt # Canonical order taken from ``open_geotiff``'s public signature. _CANONICAL_ORDER = ( diff --git a/xrspatial/geotiff/tests/test_remaining_fail_closed_1987.py b/xrspatial/geotiff/tests/test_remaining_fail_closed_1987.py index ee874883f..3720bf10e 100644 --- a/xrspatial/geotiff/tests/test_remaining_fail_closed_1987.py +++ b/xrspatial/geotiff/tests/test_remaining_fail_closed_1987.py @@ -35,23 +35,15 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - ConflictingNodataError, - GeoTIFFAmbiguousMetadataError, - NonUniformCoordsError, - RotatedTransformError, - UnparseableCRSError, - open_geotiff, - to_geotiff, -) -from xrspatial.geotiff._validation import ( - _check_read_rotated_transform, - _check_read_unparseable_crs, - _check_write_conflicting_nodata, - _check_write_non_uniform_coords, - _registered_read_metadata_checks, - _registered_write_metadata_checks, -) +from xrspatial.geotiff import (ConflictingNodataError, GeoTIFFAmbiguousMetadataError, + NonUniformCoordsError, RotatedTransformError, UnparseableCRSError, + open_geotiff, to_geotiff) +from xrspatial.geotiff._validation import (_check_read_rotated_transform, + _check_read_unparseable_crs, + _check_write_conflicting_nodata, + _check_write_non_uniform_coords, + _registered_read_metadata_checks, + _registered_write_metadata_checks) pyproj = pytest.importorskip("pyproj") diff --git a/xrspatial/geotiff/tests/test_remote_sidecar_chunked_2239.py b/xrspatial/geotiff/tests/test_remote_sidecar_chunked_2239.py index 104e34636..d4c8d0032 100644 --- a/xrspatial/geotiff/tests/test_remote_sidecar_chunked_2239.py +++ b/xrspatial/geotiff/tests/test_remote_sidecar_chunked_2239.py @@ -24,12 +24,10 @@ import io import pathlib -import shutil import numpy as np import pytest - _FIXTURE = ( pathlib.Path(__file__).resolve().parent / "golden_corpus" diff --git a/xrspatial/geotiff/tests/test_rotated_affine_attr_2129.py b/xrspatial/geotiff/tests/test_rotated_affine_attr_2129.py index c6ee4dc20..ef10eef30 100644 --- a/xrspatial/geotiff/tests/test_rotated_affine_attr_2129.py +++ b/xrspatial/geotiff/tests/test_rotated_affine_attr_2129.py @@ -25,15 +25,10 @@ import pytest from xrspatial.geotiff import open_geotiff -from xrspatial.geotiff._attrs import ( - _ATTRS_CONTRACT_VERSION, - _populate_attrs_from_geo_info, - attrs_to_metadata, - geo_info_to_metadata, -) +from xrspatial.geotiff._attrs import (_ATTRS_CONTRACT_VERSION, _populate_attrs_from_geo_info, + attrs_to_metadata, geo_info_to_metadata) from xrspatial.geotiff._geotags import GeoInfo, GeoTransform - _ROTATED_TUPLE = (8.66, -5.0, 100.0, 5.0, 8.66, 200.0) diff --git a/xrspatial/geotiff/tests/test_rotated_typed_error_2267.py b/xrspatial/geotiff/tests/test_rotated_typed_error_2267.py index 2071c02fc..3fd52a2fd 100644 --- a/xrspatial/geotiff/tests/test_rotated_typed_error_2267.py +++ b/xrspatial/geotiff/tests/test_rotated_typed_error_2267.py @@ -26,17 +26,10 @@ import pytest from xrspatial.geotiff import open_geotiff -from xrspatial.geotiff._errors import ( - GeoTIFFAmbiguousMetadataError, - RotatedTransformError, -) -from xrspatial.geotiff._geotags import ( - TAG_MODEL_TRANSFORMATION, - _extract_transform, -) +from xrspatial.geotiff._errors import GeoTIFFAmbiguousMetadataError, RotatedTransformError +from xrspatial.geotiff._geotags import TAG_MODEL_TRANSFORMATION, _extract_transform from xrspatial.geotiff._header import IFD, IFDEntry - _COS30 = 0.8660254037844387 _SIN30 = 0.5 _ROTATED_M = ( diff --git a/xrspatial/geotiff/tests/test_round_trip_invariants.py b/xrspatial/geotiff/tests/test_round_trip_invariants.py index e7417d4bb..243862f87 100644 --- a/xrspatial/geotiff/tests/test_round_trip_invariants.py +++ b/xrspatial/geotiff/tests/test_round_trip_invariants.py @@ -58,11 +58,11 @@ from xrspatial.geotiff._geotags import GeoTransform from xrspatial.geotiff._writer import write - # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- + def _default_gt() -> GeoTransform: return GeoTransform( origin_x=500000.0, origin_y=4000000.0, diff --git a/xrspatial/geotiff/tests/test_round_trip_parity_rasterio_zarr_1961.py b/xrspatial/geotiff/tests/test_round_trip_parity_rasterio_zarr_1961.py index ab4f157ef..22270d6f1 100644 --- a/xrspatial/geotiff/tests/test_round_trip_parity_rasterio_zarr_1961.py +++ b/xrspatial/geotiff/tests/test_round_trip_parity_rasterio_zarr_1961.py @@ -38,7 +38,6 @@ from xrspatial.geotiff import open_geotiff # noqa: E402 from xrspatial.geotiff._crs import _resolve_crs_to_wkt # noqa: E402 - # --------------------------------------------------------------------------- # helpers # --------------------------------------------------------------------------- @@ -162,7 +161,7 @@ def _build_rasterio_coords(transform: Affine, height: int, width: int): ``origin_y + (i + 0.5) * pixel_height``. """ pw, _, ox, _, ph, oy = (transform.a, transform.b, transform.c, - transform.d, transform.e, transform.f) + transform.d, transform.e, transform.f) y = oy + (np.arange(height) + 0.5) * ph x = ox + (np.arange(width) + 0.5) * pw return y, x @@ -221,9 +220,9 @@ def _parity_check_single_band( ref_y, ref_x = _build_rasterio_coords( ras_transform, ras_raw.shape[0], ras_raw.shape[1]) np.testing.assert_allclose(xrs.y.values, ref_y, - rtol=0.0, atol=ATOL_COORD) + rtol=0.0, atol=ATOL_COORD) np.testing.assert_allclose(xrs.x.values, ref_x, - rtol=0.0, atol=ATOL_COORD) + rtol=0.0, atol=ATOL_COORD) _assert_transforms_match(ras_transform, xrs.attrs['transform']) _assert_crs_match(ras_crs, xrs.attrs) else: @@ -244,9 +243,9 @@ def _parity_check_single_band( _assert_pixels_equal(xrs_np, rt_np) assert rt.dtype == xrs.dtype np.testing.assert_allclose(rt.y.values, xrs.y.values, - rtol=0.0, atol=ATOL_COORD) + rtol=0.0, atol=ATOL_COORD) np.testing.assert_allclose(rt.x.values, xrs.x.values, - rtol=0.0, atol=ATOL_COORD) + rtol=0.0, atol=ATOL_COORD) # Critical scalar attrs survive the zarr trip. for key in ('crs', 'crs_wkt', 'nodata', 'transform'): if key in xrs.attrs: diff --git a/xrspatial/geotiff/tests/test_roundtrip_properties.py b/xrspatial/geotiff/tests/test_roundtrip_properties.py index e858bef63..b2d6b360b 100644 --- a/xrspatial/geotiff/tests/test_roundtrip_properties.py +++ b/xrspatial/geotiff/tests/test_roundtrip_properties.py @@ -74,19 +74,12 @@ hypothesis = pytest.importorskip("hypothesis") -from hypothesis import ( # noqa: E402 - HealthCheck, - assume, - event, - given, - settings, -) +from hypothesis import HealthCheck, assume, event, given, settings # noqa: E402 from hypothesis import strategies as st # noqa: E402 from xrspatial.geotiff import open_geotiff, to_geotiff # noqa: E402 from xrspatial.geotiff._geotags import _NO_GEOREF_KEY # noqa: E402 - # --------------------------------------------------------------------------- # Profile registration # --------------------------------------------------------------------------- diff --git a/xrspatial/geotiff/tests/test_security.py b/xrspatial/geotiff/tests/test_security.py index 00ab6a6ed..b00da7e96 100644 --- a/xrspatial/geotiff/tests/test_security.py +++ b/xrspatial/geotiff/tests/test_security.py @@ -9,28 +9,23 @@ import os import struct -import tempfile import threading import numpy as np import pytest -from xrspatial.geotiff._reader import ( - MAX_PIXELS_DEFAULT, - _check_dimensions, - _read_strips, - _read_tiles, - read_to_array, -) -from xrspatial.geotiff._header import parse_header, parse_all_ifds from xrspatial.geotiff._dtypes import tiff_dtype_to_numpy -from .conftest import make_minimal_tiff +from xrspatial.geotiff._header import parse_all_ifds, parse_header +from xrspatial.geotiff._reader import (MAX_PIXELS_DEFAULT, _check_dimensions, _read_strips, + _read_tiles, read_to_array) +from .conftest import make_minimal_tiff # --------------------------------------------------------------------------- # Cat 1: Unbounded allocation guard # --------------------------------------------------------------------------- + class TestDimensionGuard: def test_check_dimensions_rejects_oversized(self): """_check_dimensions raises when total pixels exceed the limit.""" @@ -222,7 +217,7 @@ def test_open_geotiff_forged_tile_dims(self, tmp_path): # Parse to locate the tile-width entry, then rewrite it in place. # The conftest TIFF uses little-endian SHORT for TileWidth (322). import struct - header = parse_header(base) + # IFD starts at offset 8, then 2-byte count, then 12-byte entries num_entries = struct.unpack_from(' bytes: """Build a real tiled COG, then patch every TileByteCounts entry.""" - from xrspatial.geotiff import to_geotiff import xarray as xr + + from xrspatial.geotiff import to_geotiff arr = np.arange(64 * 64, dtype=np.float32).reshape(64, 64) da = xr.DataArray(arr, dims=['y', 'x']) path = str(tmp_path / "forged_1536.tif") @@ -685,9 +680,11 @@ def test_error_message_names_offending_value(self, tmp_path, monkeypatch): def test_normal_cog_still_reads(self, tmp_path, monkeypatch): """Realistic per-tile byte counts pass under the default cap.""" - from xrspatial.geotiff import to_geotiff, _reader as _reader_mod import xarray as xr + from xrspatial.geotiff import _reader as _reader_mod + from xrspatial.geotiff import to_geotiff + arr = np.arange(64 * 64, dtype=np.float32).reshape(64, 64) da = xr.DataArray(arr, dims=['y', 'x']) path = str(tmp_path / "normal_1536.tif") @@ -703,9 +700,11 @@ def test_normal_cog_still_reads(self, tmp_path, monkeypatch): def test_env_override_lifts_cap(self, tmp_path, monkeypatch): """A user with legitimate large tiles can raise the cap via env.""" - from xrspatial.geotiff import to_geotiff, _reader as _reader_mod import xarray as xr + from xrspatial.geotiff import _reader as _reader_mod + from xrspatial.geotiff import to_geotiff + arr = np.arange(64 * 64, dtype=np.float32).reshape(64, 64) da = xr.DataArray(arr, dims=['y', 'x']) path = str(tmp_path / "normal_env_1536.tif") @@ -729,9 +728,10 @@ def test_local_path_respects_default_cap(self, tmp_path): cap is shared, so we just confirm the default (256 MiB) leaves plenty of headroom for a normal small tiled COG. """ - from xrspatial.geotiff import open_geotiff, to_geotiff import xarray as xr + from xrspatial.geotiff import open_geotiff, to_geotiff + arr = np.arange(64 * 64, dtype=np.float32).reshape(64, 64) da = xr.DataArray(arr, dims=['y', 'x']) path = str(tmp_path / "local_normal_1664.tif") diff --git a/xrspatial/geotiff/tests/test_sidecar_max_cloud_bytes_2121.py b/xrspatial/geotiff/tests/test_sidecar_max_cloud_bytes_2121.py index cd5ed91fd..84aa6f55d 100644 --- a/xrspatial/geotiff/tests/test_sidecar_max_cloud_bytes_2121.py +++ b/xrspatial/geotiff/tests/test_sidecar_max_cloud_bytes_2121.py @@ -30,7 +30,6 @@ from xrspatial.geotiff._reader import CloudSizeLimitError from xrspatial.geotiff._sidecar import load_sidecar - _FIXTURE = ( pathlib.Path(__file__).resolve().parent / "golden_corpus" diff --git a/xrspatial/geotiff/tests/test_sidecar_ovr_2112.py b/xrspatial/geotiff/tests/test_sidecar_ovr_2112.py index d6f125ea8..3f4ade5d6 100644 --- a/xrspatial/geotiff/tests/test_sidecar_ovr_2112.py +++ b/xrspatial/geotiff/tests/test_sidecar_ovr_2112.py @@ -31,7 +31,6 @@ from xrspatial.geotiff._reader import read_to_array from xrspatial.geotiff._sidecar import find_sidecar, load_sidecar - _FIXTURE = ( pathlib.Path(__file__).resolve().parent / "golden_corpus" diff --git a/xrspatial/geotiff/tests/test_signature_annotations_1654.py b/xrspatial/geotiff/tests/test_signature_annotations_1654.py index 246a14f7e..d5b3a9ffd 100644 --- a/xrspatial/geotiff/tests/test_signature_annotations_1654.py +++ b/xrspatial/geotiff/tests/test_signature_annotations_1654.py @@ -20,15 +20,8 @@ import inspect -from xrspatial.geotiff import ( - open_geotiff, - read_geotiff_dask, - read_geotiff_gpu, - read_vrt, - to_geotiff, - write_geotiff_gpu, - write_vrt, -) +from xrspatial.geotiff import (open_geotiff, read_geotiff_dask, read_geotiff_gpu, read_vrt, + to_geotiff, write_geotiff_gpu, write_vrt) def _annotation(fn, param_name): @@ -208,6 +201,7 @@ def test_open_geotiff_bytesio_source_runtime(tmp_path): drops the file-like branch fails CI. See issue #1754. """ import io + import numpy as np import xarray as xr diff --git a/xrspatial/geotiff/tests/test_signature_annotations_1705.py b/xrspatial/geotiff/tests/test_signature_annotations_1705.py index 45e015a3f..24166c07b 100644 --- a/xrspatial/geotiff/tests/test_signature_annotations_1705.py +++ b/xrspatial/geotiff/tests/test_signature_annotations_1705.py @@ -23,11 +23,7 @@ import inspect -from xrspatial.geotiff import ( - to_geotiff, - write_geotiff_gpu, - write_vrt, -) +from xrspatial.geotiff import to_geotiff, write_geotiff_gpu, write_vrt def _annotation(fn, param_name): diff --git a/xrspatial/geotiff/tests/test_signature_parity_1631.py b/xrspatial/geotiff/tests/test_signature_parity_1631.py index 1c5e48a99..37e563b04 100644 --- a/xrspatial/geotiff/tests/test_signature_parity_1631.py +++ b/xrspatial/geotiff/tests/test_signature_parity_1631.py @@ -24,12 +24,7 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - open_geotiff, - to_geotiff, - write_geotiff_gpu, - write_vrt, -) +from xrspatial.geotiff import open_geotiff, to_geotiff, write_geotiff_gpu, write_vrt def _gpu_available() -> bool: diff --git a/xrspatial/geotiff/tests/test_signed_miniswhite_rejected_2278.py b/xrspatial/geotiff/tests/test_signed_miniswhite_rejected_2278.py index 0c77ae94f..6dc1cd154 100644 --- a/xrspatial/geotiff/tests/test_signed_miniswhite_rejected_2278.py +++ b/xrspatial/geotiff/tests/test_signed_miniswhite_rejected_2278.py @@ -21,11 +21,7 @@ import xarray as xr from xrspatial.geotiff import open_geotiff, to_geotiff -from xrspatial.geotiff._header import ( - TAG_PHOTOMETRIC, - TAG_SAMPLE_FORMAT, - parse_header, -) +from xrspatial.geotiff._header import TAG_PHOTOMETRIC, TAG_SAMPLE_FORMAT, parse_header def _da(arr: np.ndarray, attrs_extra=None) -> xr.DataArray: diff --git a/xrspatial/geotiff/tests/test_size_param_validation_gpu_vrt_1776.py b/xrspatial/geotiff/tests/test_size_param_validation_gpu_vrt_1776.py index 5b9b13801..36ae5f43c 100644 --- a/xrspatial/geotiff/tests/test_size_param_validation_gpu_vrt_1776.py +++ b/xrspatial/geotiff/tests/test_size_param_validation_gpu_vrt_1776.py @@ -25,14 +25,8 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - read_geotiff_dask, - read_geotiff_gpu, - read_vrt, - to_geotiff, - write_geotiff_gpu, - write_vrt, -) +from xrspatial.geotiff import (read_geotiff_dask, read_geotiff_gpu, read_vrt, to_geotiff, + write_geotiff_gpu, write_vrt) def _gpu_available() -> bool: diff --git a/xrspatial/geotiff/tests/test_sparse_cog.py b/xrspatial/geotiff/tests/test_sparse_cog.py index a136900e3..c77bfd8b7 100644 --- a/xrspatial/geotiff/tests/test_sparse_cog.py +++ b/xrspatial/geotiff/tests/test_sparse_cog.py @@ -13,8 +13,8 @@ rasterio = pytest.importorskip('rasterio') -from xrspatial.geotiff import open_geotiff -from xrspatial.geotiff._reader import read_to_array +from xrspatial.geotiff import open_geotiff # noqa: E402 +from xrspatial.geotiff._reader import read_to_array # noqa: E402 def _write_sparse_tiled(path, *, dtype='uint16', nodata=0, diff --git a/xrspatial/geotiff/tests/test_ssrf_hardening_1664.py b/xrspatial/geotiff/tests/test_ssrf_hardening_1664.py index 9336aa68f..1917d1393 100644 --- a/xrspatial/geotiff/tests/test_ssrf_hardening_1664.py +++ b/xrspatial/geotiff/tests/test_ssrf_hardening_1664.py @@ -18,7 +18,6 @@ from xrspatial.geotiff import UnsafeURLError from xrspatial.geotiff import _reader as _reader_mod - # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- diff --git a/xrspatial/geotiff/tests/test_streaming_codecs_2026_05_11.py b/xrspatial/geotiff/tests/test_streaming_codecs_2026_05_11.py index a73abbbb5..bd1e34795 100644 --- a/xrspatial/geotiff/tests/test_streaming_codecs_2026_05_11.py +++ b/xrspatial/geotiff/tests/test_streaming_codecs_2026_05_11.py @@ -29,16 +29,13 @@ pytest.importorskip("dask") from xrspatial.geotiff import open_geotiff, to_geotiff # noqa: E402 -from xrspatial.geotiff._compression import ( # noqa: E402 - LERC_AVAILABLE, - LZ4_AVAILABLE, -) - +from xrspatial.geotiff._compression import LERC_AVAILABLE, LZ4_AVAILABLE # noqa: E402 # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- + @pytest.fixture def float_raster(): """200x200 float32 raster shaped to exceed a single chunk.""" @@ -77,7 +74,7 @@ def dask_uint8_raster(uint8_raster): @pytest.mark.skipif(not LERC_AVAILABLE, reason="lerc not installed") class TestStreamingLerc: def test_lossless_round_trip(self, float_raster, dask_float_raster, - tmp_path): + tmp_path): """Dask + LERC (max_z_error=0) round-trips exactly.""" path = str(tmp_path / 'stream_lerc_lossless.tif') # Tier 3 codec (issue #2137); opt in to exercise the encode path. @@ -88,7 +85,7 @@ def test_lossless_round_trip(self, float_raster, dask_float_raster, np.testing.assert_array_equal(result.values, float_raster.values) def test_lossy_respects_max_z_error(self, float_raster, dask_float_raster, - tmp_path): + tmp_path): """Dask + LERC with non-zero max_z_error keeps every pixel within bound.""" max_z = 0.1 path = str(tmp_path / 'stream_lerc_lossy.tif') @@ -102,7 +99,7 @@ def test_lossy_respects_max_z_error(self, float_raster, dask_float_raster, f"{max_diff} > {max_z}") def test_streaming_matches_eager(self, float_raster, dask_float_raster, - tmp_path): + tmp_path): """Pixel-identical output between eager and streaming LERC writers. This is the parity guarantee: both paths feed the same @@ -137,7 +134,7 @@ def test_round_trip(self, float_raster, dask_float_raster, tmp_path): np.testing.assert_array_equal(result.values, float_raster.values) def test_streaming_matches_eager(self, float_raster, dask_float_raster, - tmp_path): + tmp_path): eager_path = str(tmp_path / 'eager_lz4.tif') stream_path = str(tmp_path / 'stream_lz4.tif') to_geotiff(float_raster, eager_path, compression='lz4', @@ -161,7 +158,7 @@ def test_round_trip_uint8(self, uint8_raster, dask_uint8_raster, tmp_path): np.testing.assert_array_equal(result.values, uint8_raster.values) def test_streaming_matches_eager(self, uint8_raster, dask_uint8_raster, - tmp_path): + tmp_path): eager_path = str(tmp_path / 'eager_packbits.tif') stream_path = str(tmp_path / 'stream_packbits.tif') to_geotiff(uint8_raster, eager_path, compression='packbits') diff --git a/xrspatial/geotiff/tests/test_streaming_photometric_override_2073.py b/xrspatial/geotiff/tests/test_streaming_photometric_override_2073.py index b21f2fb92..f964feccb 100644 --- a/xrspatial/geotiff/tests/test_streaming_photometric_override_2073.py +++ b/xrspatial/geotiff/tests/test_streaming_photometric_override_2073.py @@ -25,7 +25,6 @@ from xrspatial.geotiff import open_geotiff, to_geotiff - TAG_PHOTOMETRIC = 262 TYPE_SHORT = 3 diff --git a/xrspatial/geotiff/tests/test_streaming_write.py b/xrspatial/geotiff/tests/test_streaming_write.py index 235852941..9edb817b5 100644 --- a/xrspatial/geotiff/tests/test_streaming_write.py +++ b/xrspatial/geotiff/tests/test_streaming_write.py @@ -1,6 +1,5 @@ """Tests for streaming TIFF write from dask-backed DataArrays (#1084).""" import numpy as np -import os import pytest import xarray as xr diff --git a/xrspatial/geotiff/tests/test_streaming_write_parallel.py b/xrspatial/geotiff/tests/test_streaming_write_parallel.py index 978b66512..2f95b8deb 100644 --- a/xrspatial/geotiff/tests/test_streaming_write_parallel.py +++ b/xrspatial/geotiff/tests/test_streaming_write_parallel.py @@ -18,14 +18,14 @@ import pytest import xarray as xr -from xrspatial.geotiff import open_geotiff, to_geotiff from xrspatial.geotiff import _writer as writer_mod - +from xrspatial.geotiff import open_geotiff, to_geotiff # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- + def _make_dataarray(shape, dtype=np.float32, seed=20260508): rng = np.random.default_rng(seed) if np.issubdtype(np.dtype(dtype), np.floating): diff --git a/xrspatial/geotiff/tests/test_streaming_write_pool_leak_2276.py b/xrspatial/geotiff/tests/test_streaming_write_pool_leak_2276.py index 000b62f5b..0cd421ff6 100644 --- a/xrspatial/geotiff/tests/test_streaming_write_pool_leak_2276.py +++ b/xrspatial/geotiff/tests/test_streaming_write_pool_leak_2276.py @@ -37,9 +37,8 @@ import pytest import xarray as xr -from xrspatial.geotiff import to_geotiff from xrspatial.geotiff import _writer as writer_mod - +from xrspatial.geotiff import to_geotiff # Re-use the writer's own constant so the test does not silently drift # if the prefix ever changes on the writer side. ``_writer`` exposes diff --git a/xrspatial/geotiff/tests/test_strict_mode_1662.py b/xrspatial/geotiff/tests/test_strict_mode_1662.py index be48bc6fb..79a7b94e8 100644 --- a/xrspatial/geotiff/tests/test_strict_mode_1662.py +++ b/xrspatial/geotiff/tests/test_strict_mode_1662.py @@ -23,11 +23,7 @@ import pytest -from xrspatial.geotiff import ( - GeoTIFFFallbackWarning, - _geotiff_strict_mode, - _wkt_to_epsg, -) +from xrspatial.geotiff import GeoTIFFFallbackWarning, _geotiff_strict_mode, _wkt_to_epsg from xrspatial.geotiff._geotags import _epsg_to_wkt @@ -319,8 +315,7 @@ def test_read_geotiff_gpu_env_var_promotes_to_strict(monkeypatch, tmp_path): import numpy as np import xarray as xr - from xrspatial.geotiff import read_geotiff_gpu, to_geotiff - from xrspatial.geotiff import _gpu_decode + from xrspatial.geotiff import _gpu_decode, read_geotiff_gpu, to_geotiff # 1. Write a small valid TIF so the metadata parse succeeds and we # reach the GPU decode stage. diff --git a/xrspatial/geotiff/tests/test_strip_zero_dims_2053.py b/xrspatial/geotiff/tests/test_strip_zero_dims_2053.py index 5db5c9673..d7fc96ee9 100644 --- a/xrspatial/geotiff/tests/test_strip_zero_dims_2053.py +++ b/xrspatial/geotiff/tests/test_strip_zero_dims_2053.py @@ -29,19 +29,15 @@ import xarray as xr from xrspatial.geotiff import open_geotiff, to_geotiff -from xrspatial.geotiff._header import ( - TAG_IMAGE_LENGTH, - TAG_IMAGE_WIDTH, - TAG_SAMPLES_PER_PIXEL, - parse_header, -) +from xrspatial.geotiff._header import (TAG_IMAGE_LENGTH, TAG_IMAGE_WIDTH, TAG_SAMPLES_PER_PIXEL, + parse_header) from xrspatial.geotiff._reader import _check_source_dimensions - # --------------------------------------------------------------------------- # Helpers: locate and patch a tag value inside a classic-TIFF IFD entry # --------------------------------------------------------------------------- + def _find_ifd_entry_offset(buf: bytes, tag_id: int) -> int: """Return the byte offset of the IFD entry for ``tag_id``. @@ -228,12 +224,9 @@ def test_windowed_outside_image_returns_empty_not_error(self, tmp_path): # supports one; otherwise call the lower-level reader directly. # We use the lower-level _read_strips because open_geotiff # doesn't expose a window kwarg consistently across versions. + from xrspatial.geotiff._dtypes import resolve_bits_per_sample, tiff_dtype_to_numpy from xrspatial.geotiff._header import parse_all_ifds from xrspatial.geotiff._reader import _read_strips - from xrspatial.geotiff._dtypes import ( - resolve_bits_per_sample, - tiff_dtype_to_numpy, - ) data = bytes(buf) header = parse_header(data) diff --git a/xrspatial/geotiff/tests/test_supported_features_tiers_2137.py b/xrspatial/geotiff/tests/test_supported_features_tiers_2137.py index 09b020598..c66c3e39f 100644 --- a/xrspatial/geotiff/tests/test_supported_features_tiers_2137.py +++ b/xrspatial/geotiff/tests/test_supported_features_tiers_2137.py @@ -36,15 +36,10 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - GeoTIFFFallbackWarning, - SUPPORTED_FEATURES, - to_geotiff, - write_geotiff_gpu, -) +from xrspatial.geotiff import (SUPPORTED_FEATURES, GeoTIFFFallbackWarning, to_geotiff, + write_geotiff_gpu) from xrspatial.geotiff._attrs import _VALID_COMPRESSIONS - _TIER_VALUES = {'stable', 'advanced', 'experimental', 'internal_only'} diff --git a/xrspatial/geotiff/tests/test_to_geotiff_3d_dim_validation_1812.py b/xrspatial/geotiff/tests/test_to_geotiff_3d_dim_validation_1812.py index 510709b8b..1b0941000 100644 --- a/xrspatial/geotiff/tests/test_to_geotiff_3d_dim_validation_1812.py +++ b/xrspatial/geotiff/tests/test_to_geotiff_3d_dim_validation_1812.py @@ -217,7 +217,7 @@ def test_gpu_writer_happy_path_still_works(tmp_path): arr_bf = cupy.arange(3 * 4 * 5, dtype=cupy.uint8).reshape(3, 4, 5) da_bf = xr.DataArray(arr_bf, dims=("band", "y", "x"), - attrs={"crs": "EPSG:4326"}) + attrs={"crs": "EPSG:4326"}) p_bf = tmp_path / "tmp_1812_gpu_bf.tif" write_geotiff_gpu(da_bf, str(p_bf), crs=4326) out_bf = open_geotiff(str(p_bf)) @@ -225,7 +225,7 @@ def test_gpu_writer_happy_path_still_works(tmp_path): arr_bl = cupy.arange(4 * 5 * 3, dtype=cupy.uint8).reshape(4, 5, 3) da_bl = xr.DataArray(arr_bl, dims=("y", "x", "band"), - attrs={"crs": "EPSG:4326"}) + attrs={"crs": "EPSG:4326"}) p_bl = tmp_path / "tmp_1812_gpu_bl.tif" write_geotiff_gpu(da_bl, str(p_bl), crs=4326) out_bl = open_geotiff(str(p_bl)) diff --git a/xrspatial/geotiff/tests/test_to_geotiff_allow_internal_only_jpeg_parity.py b/xrspatial/geotiff/tests/test_to_geotiff_allow_internal_only_jpeg_parity.py index 13afe52e9..923d236fa 100644 --- a/xrspatial/geotiff/tests/test_to_geotiff_allow_internal_only_jpeg_parity.py +++ b/xrspatial/geotiff/tests/test_to_geotiff_allow_internal_only_jpeg_parity.py @@ -29,12 +29,7 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - GeoTIFFFallbackWarning, - open_geotiff, - to_geotiff, - write_geotiff_gpu, -) +from xrspatial.geotiff import GeoTIFFFallbackWarning, open_geotiff, to_geotiff, write_geotiff_gpu def _make_rgb_uint8_da() -> xr.DataArray: diff --git a/xrspatial/geotiff/tests/test_to_geotiff_drop_rotation_2216.py b/xrspatial/geotiff/tests/test_to_geotiff_drop_rotation_2216.py index 2009c38f3..8f56808ae 100644 --- a/xrspatial/geotiff/tests/test_to_geotiff_drop_rotation_2216.py +++ b/xrspatial/geotiff/tests/test_to_geotiff_drop_rotation_2216.py @@ -28,7 +28,6 @@ from xrspatial.geotiff import open_geotiff, to_geotiff from xrspatial.geotiff._writers.eager import _write_vrt_tiled - _ROTATED_TUPLE = (8.66, -5.0, 100.0, 5.0, 8.66, 200.0) diff --git a/xrspatial/geotiff/tests/test_to_geotiff_gpu_fallback_1674.py b/xrspatial/geotiff/tests/test_to_geotiff_gpu_fallback_1674.py index 2451a0db4..b145509fd 100644 --- a/xrspatial/geotiff/tests/test_to_geotiff_gpu_fallback_1674.py +++ b/xrspatial/geotiff/tests/test_to_geotiff_gpu_fallback_1674.py @@ -32,11 +32,11 @@ from xrspatial.geotiff import GeoTIFFFallbackWarning - # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- + @pytest.fixture def clear_strict_env(monkeypatch): """Default mode: ``XRSPATIAL_GEOTIFF_STRICT`` is unset.""" @@ -254,7 +254,6 @@ def test_auto_detected_gpu_fallback_warns( passed. """ from xrspatial.geotiff import to_geotiff - # Synthesise a "CuPy-looking" DataArray via _is_gpu_data's hook. # Easiest: patch _is_gpu_data to True in the writer module that # actually calls it (the to_geotiff body lives in _writers.eager @@ -330,8 +329,8 @@ def test_explicit_gpu_false_then_true_uses_explicit_template( not from the resolved ``use_gpu`` value -- so passing ``gpu=True`` on numpy data still attributes the fallback to the explicit flag. """ - from xrspatial.geotiff import to_geotiff from xrspatial import geotiff as g + from xrspatial.geotiff import to_geotiff # Even if auto-detect would say "not GPU", the explicit request # should drive the wording. diff --git a/xrspatial/geotiff/tests/test_user_defined_crs_wkt_1632.py b/xrspatial/geotiff/tests/test_user_defined_crs_wkt_1632.py index 802fe8523..50241bab2 100644 --- a/xrspatial/geotiff/tests/test_user_defined_crs_wkt_1632.py +++ b/xrspatial/geotiff/tests/test_user_defined_crs_wkt_1632.py @@ -22,9 +22,8 @@ tifffile = pytest.importorskip("tifffile") -from xrspatial.geotiff import open_geotiff, to_geotiff -from xrspatial.geotiff._geotags import _looks_like_wkt - +from xrspatial.geotiff import open_geotiff, to_geotiff # noqa: E402 +from xrspatial.geotiff._geotags import _looks_like_wkt # noqa: E402 # A user-defined Lambert Conformal Conic that pyproj cannot identify # as a registered EPSG. Trimmed to keep test fixtures readable. @@ -260,10 +259,7 @@ def test_synthesize_user_defined_wkt_sphere(): """Sphere ellipsoid (``inv_flattening == 0``) round-trips to a longlat CRS with ``b == a``. This is the ``crs_citation_only`` fixture shape.""" pyproj = pytest.importorskip("pyproj") - from xrspatial.geotiff._geotags import ( - MODEL_TYPE_GEOGRAPHIC, - _synthesize_user_defined_wkt, - ) + from xrspatial.geotiff._geotags import MODEL_TYPE_GEOGRAPHIC, _synthesize_user_defined_wkt wkt = _synthesize_user_defined_wkt( model_type=MODEL_TYPE_GEOGRAPHIC, @@ -282,10 +278,7 @@ def test_synthesize_user_defined_wkt_sphere(): def test_synthesize_user_defined_wkt_oblate_ellipsoid(): """An oblate ellipsoid (inv_flattening != 0) maps to PROJ ``rf=...``.""" pyproj = pytest.importorskip("pyproj") - from xrspatial.geotiff._geotags import ( - MODEL_TYPE_GEOGRAPHIC, - _synthesize_user_defined_wkt, - ) + from xrspatial.geotiff._geotags import MODEL_TYPE_GEOGRAPHIC, _synthesize_user_defined_wkt wkt = _synthesize_user_defined_wkt( model_type=MODEL_TYPE_GEOGRAPHIC, @@ -304,10 +297,7 @@ def test_synthesize_user_defined_wkt_projected_returns_none(): GeoKeys alone (they need the GeogPrime / Projection parameters), so the helper returns ``None`` and the caller falls back to the deprecated-attrs path.""" - from xrspatial.geotiff._geotags import ( - MODEL_TYPE_PROJECTED, - _synthesize_user_defined_wkt, - ) + from xrspatial.geotiff._geotags import MODEL_TYPE_PROJECTED, _synthesize_user_defined_wkt assert _synthesize_user_defined_wkt( model_type=MODEL_TYPE_PROJECTED, @@ -321,10 +311,7 @@ def test_synthesize_user_defined_wkt_geocentric_returns_none(): """Geocentric and unknown model_type values also fall through to ``None``. Pinned so a future change that promotes geocentric to a real proj_dict still has to update this test deliberately.""" - from xrspatial.geotiff._geotags import ( - MODEL_TYPE_GEOCENTRIC, - _synthesize_user_defined_wkt, - ) + from xrspatial.geotiff._geotags import MODEL_TYPE_GEOCENTRIC, _synthesize_user_defined_wkt assert _synthesize_user_defined_wkt( model_type=MODEL_TYPE_GEOCENTRIC, @@ -346,10 +333,7 @@ def test_synthesize_user_defined_wkt_missing_ellipsoid_returns_none(): """Without any ellipsoid info, refuse to fabricate a CRS rather than silently emit a WGS84 fallback that would compare-equal to unrelated files.""" - from xrspatial.geotiff._geotags import ( - MODEL_TYPE_GEOGRAPHIC, - _synthesize_user_defined_wkt, - ) + from xrspatial.geotiff._geotags import MODEL_TYPE_GEOGRAPHIC, _synthesize_user_defined_wkt # No semi_major: cannot build an ellipsoid. assert _synthesize_user_defined_wkt( diff --git a/xrspatial/geotiff/tests/test_validate_3d_non_band_trailing_dim_2240.py b/xrspatial/geotiff/tests/test_validate_3d_non_band_trailing_dim_2240.py index 398e69d3b..ff2f51af3 100644 --- a/xrspatial/geotiff/tests/test_validate_3d_non_band_trailing_dim_2240.py +++ b/xrspatial/geotiff/tests/test_validate_3d_non_band_trailing_dim_2240.py @@ -25,9 +25,9 @@ from xrspatial.geotiff import open_geotiff, to_geotiff from xrspatial.geotiff._validation import _validate_3d_writer_dims - # --- Validator-level coverage ------------------------------------------------ + @pytest.mark.parametrize( "trailing", ['z', 'level', 'scenario', 'depth', 'member', 'realization', diff --git a/xrspatial/geotiff/tests/test_vrt_backend_coverage_2026_05_11.py b/xrspatial/geotiff/tests/test_vrt_backend_coverage_2026_05_11.py index b8a79a02c..00b604b07 100644 --- a/xrspatial/geotiff/tests/test_vrt_backend_coverage_2026_05_11.py +++ b/xrspatial/geotiff/tests/test_vrt_backend_coverage_2026_05_11.py @@ -28,13 +28,13 @@ from xrspatial.geotiff import open_geotiff, read_vrt, to_geotiff from xrspatial.geotiff._vrt import write_vrt as _write_vrt_internal - # --------------------------------------------------------------------------- # GPU gating: matches the ``_gpu_available`` / ``_HAS_GPU`` predicate that # the rest of the geotiff test suite (e.g. test_backend_kwarg_parity_1561, # test_attrs_parity_1548) uses, so future GPU tests stay greppable. # --------------------------------------------------------------------------- + def _gpu_available() -> bool: if importlib.util.find_spec("cupy") is None: return False diff --git a/xrspatial/geotiff/tests/test_vrt_dtype_12bit_1914.py b/xrspatial/geotiff/tests/test_vrt_dtype_12bit_1914.py index f796e0407..982c79c74 100644 --- a/xrspatial/geotiff/tests/test_vrt_dtype_12bit_1914.py +++ b/xrspatial/geotiff/tests/test_vrt_dtype_12bit_1914.py @@ -29,12 +29,7 @@ import xarray as xr from xrspatial.geotiff import to_geotiff -from xrspatial.geotiff._vrt import ( - _NP_TO_VRT_DTYPE, - _vrt_dtype_name_for, - write_vrt, -) - +from xrspatial.geotiff._vrt import _NP_TO_VRT_DTYPE, _vrt_dtype_name_for, write_vrt # --------------------------------------------------------------------------- # Direct helper tests: every supported (bps, sample_format) pair @@ -125,7 +120,7 @@ def _unique_dir(tmp_path, label: str) -> str: def _write_uint16_tif(path: str, *, h: int = 4, w: int = 4, - origin_x: float = 0.0) -> None: + origin_x: float = 0.0) -> None: arr = np.arange(h * w, dtype=np.uint16).reshape(h, w) y = 100.0 + (np.arange(h) + 0.5) * -1.0 x = origin_x + (np.arange(w) + 0.5) * 1.0 diff --git a/xrspatial/geotiff/tests/test_vrt_finalization_parity_2162.py b/xrspatial/geotiff/tests/test_vrt_finalization_parity_2162.py index efbc6817c..294d836c5 100644 --- a/xrspatial/geotiff/tests/test_vrt_finalization_parity_2162.py +++ b/xrspatial/geotiff/tests/test_vrt_finalization_parity_2162.py @@ -38,19 +38,10 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - open_geotiff, - read_geotiff_dask, - read_vrt, - to_geotiff, -) -from xrspatial.geotiff._attrs import ( - GEOREF_STATUS_CRS_ONLY, - GEOREF_STATUS_FULL, - GEOREF_STATUS_NONE, - GEOREF_STATUS_ROTATED_DROPPED, - GEOREF_STATUS_TRANSFORM_ONLY, -) +from xrspatial.geotiff import open_geotiff, read_geotiff_dask, read_vrt, to_geotiff +from xrspatial.geotiff._attrs import (GEOREF_STATUS_CRS_ONLY, GEOREF_STATUS_FULL, + GEOREF_STATUS_NONE, GEOREF_STATUS_ROTATED_DROPPED, + GEOREF_STATUS_TRANSFORM_ONLY) from xrspatial.geotiff._coords import _NO_GEOREF_KEY from xrspatial.geotiff._writer import write diff --git a/xrspatial/geotiff/tests/test_vrt_holes_attr_1734.py b/xrspatial/geotiff/tests/test_vrt_holes_attr_1734.py index 2d8f86776..84d1315cc 100644 --- a/xrspatial/geotiff/tests/test_vrt_holes_attr_1734.py +++ b/xrspatial/geotiff/tests/test_vrt_holes_attr_1734.py @@ -105,6 +105,7 @@ def test_no_holes_attr_when_all_sources_read(clear_strict_env, tmp_path): cheap completeness check.""" import numpy as np import xarray as xr + from xrspatial.geotiff import to_geotiff # Write a real source the VRT can reference. diff --git a/xrspatial/geotiff/tests/test_vrt_int_nodata_1564.py b/xrspatial/geotiff/tests/test_vrt_int_nodata_1564.py index 7edccd478..a2a25b19a 100644 --- a/xrspatial/geotiff/tests/test_vrt_int_nodata_1564.py +++ b/xrspatial/geotiff/tests/test_vrt_int_nodata_1564.py @@ -13,12 +13,7 @@ import numpy as np import xarray as xr -from xrspatial.geotiff import ( - open_geotiff, - read_vrt, - to_geotiff, - write_vrt, -) +from xrspatial.geotiff import open_geotiff, read_vrt, to_geotiff, write_vrt def _write_uint16_with_nodata_tif(path, sentinel): diff --git a/xrspatial/geotiff/tests/test_vrt_int_source_float_dtype_1616.py b/xrspatial/geotiff/tests/test_vrt_int_source_float_dtype_1616.py index a4cdc0bbe..026fb00bd 100644 --- a/xrspatial/geotiff/tests/test_vrt_int_source_float_dtype_1616.py +++ b/xrspatial/geotiff/tests/test_vrt_int_source_float_dtype_1616.py @@ -153,9 +153,9 @@ def test_float_vrt_int_source_with_band_select(tmp_path): must reach the source-side masking step, not just ``attrs['nodata']``. """ src_a = _write_uint16_with_sentinel(tmp_path, sentinel=65535, - filename='ba.tif') + filename='ba.tif') src_b = _write_uint16_with_sentinel(tmp_path, sentinel=65000, - filename='bb.tif') + filename='bb.tif') vrt_xml = f""" 0.0, 1.0, 0.0, 0.0, 0.0, -1.0 diff --git a/xrspatial/geotiff/tests/test_vrt_lazy_chunks_1814.py b/xrspatial/geotiff/tests/test_vrt_lazy_chunks_1814.py index 832586ab1..abf41d09a 100644 --- a/xrspatial/geotiff/tests/test_vrt_lazy_chunks_1814.py +++ b/xrspatial/geotiff/tests/test_vrt_lazy_chunks_1814.py @@ -30,11 +30,10 @@ def _gpu_available() -> bool: try: - import cupy # noqa: F401 + import cupy except ImportError: return False try: - import cupy return bool(cupy.cuda.is_available()) except Exception: return False @@ -120,7 +119,7 @@ def test_chunks_builds_dask_array_with_multiple_blocks(two_by_two_vrt): def test_chunks_is_lazy_does_not_call_internal_reader(monkeypatch, - two_by_two_vrt): + two_by_two_vrt): """Construction-time call count of the internal VRT reader is zero; after ``.compute()`` it equals the chunk count. """ @@ -288,6 +287,7 @@ def test_chunked_propagates_vrt_holes_when_source_missing(two_by_two_vrt): under #2265, so this test exercises the explicit ``'warn'`` opt-in. """ import warnings + from xrspatial.geotiff import GeoTIFFFallbackWarning from xrspatial.geotiff._reader import _mmap_cache diff --git a/xrspatial/geotiff/tests/test_vrt_mask_nodata_float_source_2158.py b/xrspatial/geotiff/tests/test_vrt_mask_nodata_float_source_2158.py index dc8ddf28b..d85431f1c 100644 --- a/xrspatial/geotiff/tests/test_vrt_mask_nodata_float_source_2158.py +++ b/xrspatial/geotiff/tests/test_vrt_mask_nodata_float_source_2158.py @@ -24,7 +24,7 @@ def _write_float32_with_sentinel(tmp_path, sentinel=-9999.0, - filename='float_2158.tif'): + filename='float_2158.tif'): """float32 GeoTIFF with a non-NaN sentinel and matching pixels. The middle row has a literal ``-9999.0`` so the inline masking @@ -39,7 +39,7 @@ def _write_float32_with_sentinel(tmp_path, sentinel=-9999.0, def _write_float64_with_fractional_sentinel(tmp_path, sentinel=-9999.25, - filename='float64_2158.tif'): + filename='float64_2158.tif'): """float64 GeoTIFF with a fractional sentinel. Float32's exact-cast rounding would clobber a fractional value @@ -232,7 +232,7 @@ def test_masked_vs_unmasked_differ_only_at_sentinels(tmp_path): def _write_uint16_with_sentinel(tmp_path, sentinel=65535, - filename='uint16_2158.tif'): + filename='uint16_2158.tif'): """uint16 GeoTIFF with a matching sentinel. Used to exercise the integer-source-feeding-float-VRT promotion at diff --git a/xrspatial/geotiff/tests/test_vrt_missing_sources_default_raise_1843.py b/xrspatial/geotiff/tests/test_vrt_missing_sources_default_raise_1843.py index 2f7ea9c19..a43f94a45 100644 --- a/xrspatial/geotiff/tests/test_vrt_missing_sources_default_raise_1843.py +++ b/xrspatial/geotiff/tests/test_vrt_missing_sources_default_raise_1843.py @@ -11,8 +11,8 @@ import pytest -from xrspatial.geotiff._vrt import read_vrt as _read_vrt_internal from xrspatial.geotiff import GeoTIFFFallbackWarning +from xrspatial.geotiff._vrt import read_vrt as _read_vrt_internal def _write_missing_source_vrt(path): diff --git a/xrspatial/geotiff/tests/test_vrt_missing_sources_policy_1799.py b/xrspatial/geotiff/tests/test_vrt_missing_sources_policy_1799.py index 217e62a20..29de1ba5e 100644 --- a/xrspatial/geotiff/tests/test_vrt_missing_sources_policy_1799.py +++ b/xrspatial/geotiff/tests/test_vrt_missing_sources_policy_1799.py @@ -3,8 +3,7 @@ import pytest -from xrspatial.geotiff import read_vrt -from xrspatial.geotiff import GeoTIFFFallbackWarning +from xrspatial.geotiff import GeoTIFFFallbackWarning, read_vrt def _write_missing_source_vrt(path): @@ -48,4 +47,3 @@ def test_read_vrt_missing_sources_validates_policy(tmp_path): with pytest.raises(ValueError, match="missing_sources"): read_vrt(str(vrt), missing_sources='ignore') - diff --git a/xrspatial/geotiff/tests/test_vrt_multiband_int_nodata_1611.py b/xrspatial/geotiff/tests/test_vrt_multiband_int_nodata_1611.py index 827aefff0..a9557524a 100644 --- a/xrspatial/geotiff/tests/test_vrt_multiband_int_nodata_1611.py +++ b/xrspatial/geotiff/tests/test_vrt_multiband_int_nodata_1611.py @@ -31,11 +31,11 @@ def _write_two_band_per_band_nodata_vrt(tmp_path, *, dtype_str="UInt16", - np_dtype=np.uint16, - band0_sentinel=65535, - band1_sentinel=65000, - band0_other=(1, 2, 3), - band1_other=(7, 8, 9)): + np_dtype=np.uint16, + band0_sentinel=65535, + band1_sentinel=65000, + band0_other=(1, 2, 3), + band1_other=(7, 8, 9)): """Two single-band integer sources, each with a distinct nodata sentinel, exposed as bands 1 and 2 of a hand-rolled VRT. diff --git a/xrspatial/geotiff/tests/test_vrt_narrow_except_1670.py b/xrspatial/geotiff/tests/test_vrt_narrow_except_1670.py index 62eaa701f..95cb81f4f 100644 --- a/xrspatial/geotiff/tests/test_vrt_narrow_except_1670.py +++ b/xrspatial/geotiff/tests/test_vrt_narrow_except_1670.py @@ -355,6 +355,7 @@ def test_zstd_error_warns_and_continues_if_available( """ pytest.importorskip('zstandard') from zstandard import ZstdError + from xrspatial.geotiff import read_vrt src_path = tmp_path / 'src_1670_zstd.tif' diff --git a/xrspatial/geotiff/tests/test_vrt_path_containment_1671.py b/xrspatial/geotiff/tests/test_vrt_path_containment_1671.py index 74f472c31..692125c4f 100644 --- a/xrspatial/geotiff/tests/test_vrt_path_containment_1671.py +++ b/xrspatial/geotiff/tests/test_vrt_path_containment_1671.py @@ -27,7 +27,8 @@ import xarray as xr from xrspatial.geotiff import to_geotiff -from xrspatial.geotiff._vrt import parse_vrt, read_vrt as _read_vrt_internal +from xrspatial.geotiff._vrt import parse_vrt +from xrspatial.geotiff._vrt import read_vrt as _read_vrt_internal def _unique_dir(tmp_path, label: str) -> str: diff --git a/xrspatial/geotiff/tests/test_vrt_resample_window_inverse_1704.py b/xrspatial/geotiff/tests/test_vrt_resample_window_inverse_1704.py index 51a009a43..4f8cac4bb 100644 --- a/xrspatial/geotiff/tests/test_vrt_resample_window_inverse_1704.py +++ b/xrspatial/geotiff/tests/test_vrt_resample_window_inverse_1704.py @@ -15,7 +15,6 @@ """ from __future__ import annotations -import os from unittest import mock import numpy as np diff --git a/xrspatial/geotiff/tests/test_vrt_single_parse_1825.py b/xrspatial/geotiff/tests/test_vrt_single_parse_1825.py index 4c5e089c6..d98cedee9 100644 --- a/xrspatial/geotiff/tests/test_vrt_single_parse_1825.py +++ b/xrspatial/geotiff/tests/test_vrt_single_parse_1825.py @@ -145,7 +145,7 @@ def test_parsed_vrt_is_picklable(single_tile_vrt_1825): (cloudpickle is a strict superset). """ vrt_path, _ = single_tile_vrt_1825 - from xrspatial.geotiff._vrt import parse_vrt, _read_vrt_xml + from xrspatial.geotiff._vrt import _read_vrt_xml, parse_vrt xml_str = _read_vrt_xml(vrt_path) vrt_dir = os.path.dirname(os.path.abspath(vrt_path)) @@ -180,7 +180,7 @@ def test_chunked_matches_eager_after_refactor(two_by_two_vrt_1825): def test_no_path_containment_revalidation_per_chunk(monkeypatch, - two_by_two_vrt_1825): + two_by_two_vrt_1825): """Per-chunk tasks skip the source-path containment check. ``parse_vrt`` is the only place that resolves and validates source @@ -229,11 +229,8 @@ def test_parsed_kwarg_does_not_mutate_caller_holes(single_tile_vrt_1825): object). Pin that ``parsed.holes`` stays untouched. """ vrt_path, _ = single_tile_vrt_1825 - from xrspatial.geotiff._vrt import ( - _read_vrt_xml, - parse_vrt, - read_vrt as _read_vrt_internal, - ) + from xrspatial.geotiff._vrt import _read_vrt_xml, parse_vrt + from xrspatial.geotiff._vrt import read_vrt as _read_vrt_internal xml_str = _read_vrt_xml(vrt_path) vrt_dir = os.path.dirname(os.path.abspath(vrt_path)) diff --git a/xrspatial/geotiff/tests/test_vrt_source_max_pixels_1796.py b/xrspatial/geotiff/tests/test_vrt_source_max_pixels_1796.py index f93e336b3..13a1eb5be 100644 --- a/xrspatial/geotiff/tests/test_vrt_source_max_pixels_1796.py +++ b/xrspatial/geotiff/tests/test_vrt_source_max_pixels_1796.py @@ -19,7 +19,7 @@ import numpy as np import pytest -from xrspatial.geotiff import to_geotiff, read_vrt +from xrspatial.geotiff import read_vrt, to_geotiff def test_tiny_vrt_with_huge_srcrect_now_reads_minimally(tmp_path): diff --git a/xrspatial/geotiff/tests/test_vrt_write.py b/xrspatial/geotiff/tests/test_vrt_write.py index f4b94bcf9..9525336a4 100644 --- a/xrspatial/geotiff/tests/test_vrt_write.py +++ b/xrspatial/geotiff/tests/test_vrt_write.py @@ -1,6 +1,7 @@ """Tests for VRT tiled output from to_geotiff.""" -import numpy as np import os + +import numpy as np import pytest import xarray as xr @@ -88,12 +89,12 @@ def test_dask_one_tile_per_chunk(self, sample_raster, tmp_path): class TestVrtEdgeCases: def test_cog_with_vrt_raises(self, sample_raster, tmp_path): vrt_path = str(tmp_path / 'cog_1083.vrt') - with pytest.raises(ValueError, match='cog.*vrt|vrt.*cog|COG.*VRT|VRT.*COG|cog.*VRT|vrt.*COG'): + with pytest.raises(ValueError, match='cog.*vrt|vrt.*cog|COG.*VRT|VRT.*COG|cog.*VRT|vrt.*COG'): # noqa: E501 to_geotiff(sample_raster, vrt_path, cog=True) def test_overview_levels_with_vrt_raises(self, sample_raster, tmp_path): vrt_path = str(tmp_path / 'ovr_1083.vrt') - with pytest.raises(ValueError, match='overview.*vrt|vrt.*overview|overview.*VRT|VRT.*overview'): + with pytest.raises(ValueError, match='overview.*vrt|vrt.*overview|overview.*VRT|VRT.*overview'): # noqa: E501 to_geotiff(sample_raster, vrt_path, overview_levels=[2, 4]) def test_nonempty_tiles_dir_raises(self, sample_raster, tmp_path): diff --git a/xrspatial/geotiff/tests/test_vrt_xml_escape_1607.py b/xrspatial/geotiff/tests/test_vrt_xml_escape_1607.py index 4243d469f..b3f4739a9 100644 --- a/xrspatial/geotiff/tests/test_vrt_xml_escape_1607.py +++ b/xrspatial/geotiff/tests/test_vrt_xml_escape_1607.py @@ -6,12 +6,13 @@ from __future__ import annotations import os + import numpy as np import pytest import xarray as xr from xrspatial.geotiff import to_geotiff -from xrspatial.geotiff._vrt import write_vrt, parse_vrt +from xrspatial.geotiff._vrt import parse_vrt, write_vrt @pytest.fixture diff --git a/xrspatial/geotiff/tests/test_wkt_only_crs_warning_1768.py b/xrspatial/geotiff/tests/test_wkt_only_crs_warning_1768.py index 17b95e619..123905840 100644 --- a/xrspatial/geotiff/tests/test_wkt_only_crs_warning_1768.py +++ b/xrspatial/geotiff/tests/test_wkt_only_crs_warning_1768.py @@ -28,7 +28,6 @@ from xrspatial.geotiff import open_geotiff, to_geotiff from xrspatial.geotiff._geotags import GeoTransform, build_geo_tags - _USER_DEFINED_WKT = ( 'PROJCS["User defined LCC 1768",' 'GEOGCS["NAD83",' diff --git a/xrspatial/geotiff/tests/test_write_layout_monkeypatch_contract_2248.py b/xrspatial/geotiff/tests/test_write_layout_monkeypatch_contract_2248.py index 7a654b5a1..7f053b730 100644 --- a/xrspatial/geotiff/tests/test_write_layout_monkeypatch_contract_2248.py +++ b/xrspatial/geotiff/tests/test_write_layout_monkeypatch_contract_2248.py @@ -17,8 +17,8 @@ import pytest import xarray as xr -from xrspatial.geotiff import to_geotiff from xrspatial.geotiff import _writer as writer_mod +from xrspatial.geotiff import to_geotiff def _make_float32(h: int = 8, w: int = 8) -> xr.DataArray: diff --git a/xrspatial/geotiff/tests/test_write_vrt_crs_1715.py b/xrspatial/geotiff/tests/test_write_vrt_crs_1715.py index a33ebad10..9bff86699 100644 --- a/xrspatial/geotiff/tests/test_write_vrt_crs_1715.py +++ b/xrspatial/geotiff/tests/test_write_vrt_crs_1715.py @@ -28,11 +28,7 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - read_vrt, - to_geotiff, - write_vrt, -) +from xrspatial.geotiff import read_vrt, to_geotiff, write_vrt def _build_source_tif(tmp_path, name='src.tif'): diff --git a/xrspatial/geotiff/tests/test_write_vrt_int_nodata_1684.py b/xrspatial/geotiff/tests/test_write_vrt_int_nodata_1684.py index f9bf51326..a4e3dd077 100644 --- a/xrspatial/geotiff/tests/test_write_vrt_int_nodata_1684.py +++ b/xrspatial/geotiff/tests/test_write_vrt_int_nodata_1684.py @@ -19,8 +19,8 @@ import numpy as np import xarray as xr -from xrspatial.geotiff import to_geotiff, write_vrt from xrspatial.geotiff import _vrt as _vrt_module +from xrspatial.geotiff import to_geotiff, write_vrt def _nodata_annotation(fn): diff --git a/xrspatial/geotiff/tests/test_write_vrt_path_kwarg_1946.py b/xrspatial/geotiff/tests/test_write_vrt_path_kwarg_1946.py index b1add2ce2..23be09a7f 100644 --- a/xrspatial/geotiff/tests/test_write_vrt_path_kwarg_1946.py +++ b/xrspatial/geotiff/tests/test_write_vrt_path_kwarg_1946.py @@ -32,12 +32,7 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - read_vrt, - to_geotiff, - write_geotiff_gpu, - write_vrt, -) +from xrspatial.geotiff import read_vrt, to_geotiff, write_geotiff_gpu, write_vrt def _build_source_tif(tmp_path, name='src.tif'): diff --git a/xrspatial/geotiff/tests/test_writer.py b/xrspatial/geotiff/tests/test_writer.py index b5033e618..920c3b7d3 100644 --- a/xrspatial/geotiff/tests/test_writer.py +++ b/xrspatial/geotiff/tests/test_writer.py @@ -5,8 +5,8 @@ import pytest from xrspatial.geotiff._geotags import GeoTransform -from xrspatial.geotiff._writer import write, _make_overview from xrspatial.geotiff._reader import read_to_array +from xrspatial.geotiff._writer import _make_overview, write class TestMakeOverview: diff --git a/xrspatial/geotiff/tests/test_writer_matrix.py b/xrspatial/geotiff/tests/test_writer_matrix.py index 874523992..5bb1c8e0f 100644 --- a/xrspatial/geotiff/tests/test_writer_matrix.py +++ b/xrspatial/geotiff/tests/test_writer_matrix.py @@ -13,8 +13,6 @@ import os import platform -import stat -import sys import numpy as np import pytest @@ -24,7 +22,6 @@ from xrspatial.geotiff._reader import read_to_array from xrspatial.geotiff._writer import write - # --------------------------------------------------------------------------- # T-5: dtype x compression matrix # --------------------------------------------------------------------------- diff --git a/xrspatial/geotiff/tests/test_writer_return_path_1938.py b/xrspatial/geotiff/tests/test_writer_return_path_1938.py index 4dc9b328d..b1f5fe0e2 100644 --- a/xrspatial/geotiff/tests/test_writer_return_path_1938.py +++ b/xrspatial/geotiff/tests/test_writer_return_path_1938.py @@ -27,11 +27,7 @@ import pytest import xarray as xr -from xrspatial.geotiff import ( - to_geotiff, - write_geotiff_gpu, - write_vrt, -) +from xrspatial.geotiff import to_geotiff, write_geotiff_gpu, write_vrt def _gpu_available() -> bool: diff --git a/xrspatial/geotiff/tests/test_writer_uncompressed_tiled_no_dead_alloc_1736.py b/xrspatial/geotiff/tests/test_writer_uncompressed_tiled_no_dead_alloc_1736.py index eb8c2384d..0313bfef1 100644 --- a/xrspatial/geotiff/tests/test_writer_uncompressed_tiled_no_dead_alloc_1736.py +++ b/xrspatial/geotiff/tests/test_writer_uncompressed_tiled_no_dead_alloc_1736.py @@ -28,11 +28,10 @@ import numpy as np import xarray as xr -from xrspatial.geotiff import to_geotiff, open_geotiff +from xrspatial.geotiff import open_geotiff, to_geotiff from xrspatial.geotiff._compression import COMPRESSION_NONE from xrspatial.geotiff._writer import _write_tiled - # Peak ``tracemalloc`` size, in multiples of the input raster size, that # the uncompressed branch of ``_write_tiled`` must stay under. The dead # bytearray drove peak to ~2.07x; the current implementation sits at