xarray-spatial 0.10.2__tar.gz → 0.10.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xarray_spatial-0.10.3/.claude/sweep-accuracy-state.csv +38 -0
- xarray_spatial-0.10.3/.claude/sweep-test-coverage-state.csv +44 -0
- xarray_spatial-0.10.3/.codex/commands/backend-parity.md +159 -0
- xarray_spatial-0.10.3/.codex/commands/bench.md +127 -0
- xarray_spatial-0.10.3/.codex/commands/dask-notebook.md +148 -0
- xarray_spatial-0.10.3/.codex/commands/deep-sweep.md +438 -0
- xarray_spatial-0.10.3/.codex/commands/efficiency-audit.md +274 -0
- xarray_spatial-0.10.3/.codex/commands/new-issues.md +113 -0
- xarray_spatial-0.10.3/.codex/commands/ready-to-merge.md +153 -0
- xarray_spatial-0.10.3/.codex/commands/release-major.md +109 -0
- xarray_spatial-0.10.3/.codex/commands/release-minor.md +109 -0
- xarray_spatial-0.10.3/.codex/commands/release-patch.md +140 -0
- xarray_spatial-0.10.3/.codex/commands/review-contributor-pr.md +332 -0
- xarray_spatial-0.10.3/.codex/commands/review-pr.md +249 -0
- xarray_spatial-0.10.3/.codex/commands/rockout.md +380 -0
- xarray_spatial-0.10.3/.codex/commands/sweep-accuracy.md +335 -0
- xarray_spatial-0.10.3/.codex/commands/sweep-api-consistency.md +291 -0
- xarray_spatial-0.10.3/.codex/commands/sweep-metadata.md +334 -0
- xarray_spatial-0.10.3/.codex/commands/sweep-performance.md +366 -0
- xarray_spatial-0.10.3/.codex/commands/sweep-security.md +334 -0
- xarray_spatial-0.10.3/.codex/commands/sweep-style.md +316 -0
- xarray_spatial-0.10.3/.codex/commands/sweep-test-coverage.md +293 -0
- xarray_spatial-0.10.3/.codex/commands/user-guide-notebook.md +203 -0
- xarray_spatial-0.10.3/.codex/commands/validate.md +216 -0
- xarray_spatial-0.10.3/.codex/sweep-api-consistency-state.csv +10 -0
- xarray_spatial-0.10.3/.codex/sweep-metadata-state.csv +12 -0
- xarray_spatial-0.10.3/.codex/sweep-performance-state.csv +47 -0
- xarray_spatial-0.10.3/.codex/sweep-security-state.csv +48 -0
- xarray_spatial-0.10.3/.codex/sweep-style-state.csv +13 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.gitattributes +1 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.gitignore +2 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/CHANGELOG.md +74 -0
- {xarray_spatial-0.10.2/xarray_spatial.egg-info → xarray_spatial-0.10.3}/PKG-INFO +1 -1
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/setup.cfg +1 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3/xarray_spatial.egg-info}/PKG-INFO +1 -1
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xarray_spatial.egg-info/SOURCES.txt +34 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/_version.py +3 -3
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/aspect.py +97 -47
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/contour.py +97 -19
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/convolution.py +6 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/focal.py +370 -154
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geodesic.py +29 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/__init__.py +51 -10
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_backends/dask.py +22 -6
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_backends/gpu.py +22 -4
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_backends/vrt.py +31 -10
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_errors.py +22 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_validation.py +103 -2
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_writers/eager.py +11 -3
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/release_gates/test_features.py +5 -0
- xarray_spatial-0.10.3/xrspatial/geotiff/tests/test_stable_only_bbox_ordering_2869.py +196 -0
- xarray_spatial-0.10.3/xrspatial/geotiff/tests/test_stable_only_remote_2821.py +183 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/unit/test_input_validation.py +80 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/vrt/test_parity.py +55 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/gpu_rtx/__init__.py +1 -1
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/gpu_rtx/hillshade.py +20 -11
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/gpu_rtx/mesh_utils.py +68 -17
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/gpu_rtx/viewshed.py +22 -15
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/flow_accumulation_mfd.py +59 -26
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/flow_length_mfd.py +83 -31
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/flow_path_mfd.py +33 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/hand_mfd.py +12 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/stream_link_mfd.py +8 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/stream_order_mfd.py +8 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_flow_accumulation_mfd.py +93 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_flow_length_mfd.py +82 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_flow_path_mfd.py +38 -0
- xarray_spatial-0.10.3/xrspatial/hydro/tests/test_validate_mfd_companion_shape.py +131 -0
- xarray_spatial-0.10.3/xrspatial/hydro/tests/test_validate_mfd_fractions.py +188 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_watershed_mfd.py +70 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/watershed_mfd.py +41 -26
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/polygonize.py +100 -11
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/proximity.py +369 -49
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/slope.py +64 -17
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/general_checks.py +8 -6
- xarray_spatial-0.10.3/xrspatial/tests/test_aspect.py +594 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_contour.py +365 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_convolution.py +13 -1
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_dask_laziness.py +34 -2
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_focal.py +604 -22
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_geodesic_aspect.py +62 -1
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_geodesic_slope.py +202 -2
- xarray_spatial-0.10.3/xrspatial/tests/test_gpu_rtx_has_rtx.py +50 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_gpu_rtx_mesh.py +96 -4
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_northness_eastness.py +129 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_polygonize.py +277 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_proximity.py +625 -0
- xarray_spatial-0.10.3/xrspatial/tests/test_slope.py +627 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_utils.py +93 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_viewshed.py +266 -16
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/utils.py +199 -6
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/viewshed.py +85 -23
- xarray_spatial-0.10.2/xrspatial/tests/test_aspect.py +0 -251
- xarray_spatial-0.10.2/xrspatial/tests/test_slope.py +0 -276
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/backend-parity.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/bench.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/dask-notebook.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/deep-sweep.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/efficiency-audit.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/new-issues.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/ready-to-merge.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/release-major.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/release-minor.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/release-patch.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/review-contributor-pr.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/review-pr.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/rockout.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/sweep-accuracy.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/sweep-api-consistency.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/sweep-metadata.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/sweep-performance.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/sweep-security.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/sweep-style.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/sweep-test-coverage.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/user-guide-notebook.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/commands/validate.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/sweep-api-consistency-state.csv +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/sweep-metadata-state.csv +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/sweep-performance-state.csv +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/sweep-security-state.csv +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.claude/sweep-style-state.csv +0 -0
- {xarray_spatial-0.10.2/.claude → xarray_spatial-0.10.3/.codex}/sweep-accuracy-state.csv +0 -0
- {xarray_spatial-0.10.2/.claude → xarray_spatial-0.10.3/.codex}/sweep-test-coverage-state.csv +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.efficiency-audit-baseline.json +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.efficiency-audit-baseline.prev.json +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.github/ISSUE_TEMPLATE/feature-proposal.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.github/ISSUE_TEMPLATE/new-contributor.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.github/labeler.yml +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.github/pull_request_template.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.github/workflows/benchmarks.yml +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.github/workflows/copilot-review.yml +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.github/workflows/labeler.yml +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.github/workflows/pypi-publish.yml +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.github/workflows/test-cog-validator.yml +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.github/workflows/test-geotiff-corpus.yml +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.github/workflows/test.yml +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.github/workflows/welcome-contributor.yml +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/.readthedocs.yml +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/AI_POLICY.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/CLAUDE.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/CODE_OF_CONDUCT.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/CONTRIBUTING.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/Citation-styles.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/LICENSE.txt +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/MANIFEST.in +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/README.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/RELEASE.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/codecov.yml +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/pyproject.toml +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/setup.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xarray_spatial.egg-info/dependency_links.txt +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xarray_spatial.egg-info/entry_points.txt +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xarray_spatial.egg-info/not-zip-safe +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xarray_spatial.egg-info/requires.txt +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xarray_spatial.egg-info/top_level.txt +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/__main__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/accessor.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/analytics.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/balanced_allocation.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/bilateral.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/bump.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/classify.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/corridor.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/cost_distance.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/curvature.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/dasymetric.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/dataset_support.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/datasets/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/datasets/sentinel-2/blue_band.nc +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/datasets/sentinel-2/green_band.nc +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/datasets/sentinel-2/nir_band.nc +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/datasets/sentinel-2/red_band.nc +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/datasets/sentinel-2/swir1_band.nc +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/datasets/sentinel-2/swir2_band.nc +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/diagnostics.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/diffusion.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/edge_detection.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/emerging_hotspots.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/erosion.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/experimental/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/experimental/min_observable_height.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/fire.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/flood.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_attrs.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_backends/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_backends/_gpu_helpers.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_cog_http.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_compression.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_coords.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_crs.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_decode.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_dtypes.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_encode.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_geotags.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_gpu_decode.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_header.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_layout.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_nodata.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_overview.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_overview_kernels.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_reader.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_runtime.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_safe_xml.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_sidecar.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_sources.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_vrt.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_vrt_validation.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_write_layout.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_writer.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_writers/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_writers/gpu.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/_writers/vrt.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/_helpers/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/_helpers/markers.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/_helpers/tiff_builders.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/_helpers/tiff_surgery.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/attrs/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/attrs/test_contract.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/bench_vs_rioxarray.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/conftest.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/README.md +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/_marks.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/_oracle.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/cog_internal_overview_uint16.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/compression_deflate_predictor2_uint16.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/compression_deflate_predictor3_float32.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/compression_jpeg_uint8_ycbcr.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/compression_lerc_float32.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/compression_lzw_predictor2_int16.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/compression_none_uint8.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/crs_citation_only.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/crs_epsg_3857.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/crs_wkt_utm10n.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_float32.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_float64.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_int16.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_int32.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_int8.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_uint16.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_uint32.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_uint8.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/extra_tags_uint16.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/gdal_metadata_namespaced_uint16.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/nodata_int_sentinel_uint16.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/nodata_miniswhite_uint8.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/nodata_nan_float32.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/overview_external_ovr_uint16.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/overview_external_ovr_uint16.tif.ovr +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/overview_internal_uint16.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/planar_separate_uint8_rgb.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/sparse_tiled_uint16.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/stripped_be_uint16.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/stripped_le_uint16.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/tiled_be_uint16.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/fixtures/tiled_le_uint16.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/generate.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/manifest.yaml +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_compression.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_corpus_determinism.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_dask_gpu.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_dask_numpy.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_dtype_variants.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_eager_numpy.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_fsspec.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_gpu.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_http.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_layout_endian.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_manifest.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_metadata_tags.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_nodata_sentinels.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_oracle.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_overview_cog.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/golden_corpus/test_vrt.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/gpu/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/gpu/test_codec.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/gpu/test_kernels_and_kwargs.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/gpu/test_reader.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/gpu/test_writer.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/integration/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/integration/test_dask_pipeline.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/integration/test_gpu_pipeline.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/integration/test_http_sources.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/integration/test_sidecar.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/parity/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/parity/test_backend_matrix.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/parity/test_finalization.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/parity/test_pixel_equality.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/parity/test_reference.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/parity/test_signature_contract.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/read/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/read/test_basic.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/read/test_bbox_2555.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/read/test_bbox_vrt_2668.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/read/test_compression.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/read/test_coords.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/read/test_crs.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/read/test_dtypes.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/read/test_endianness.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/read/test_georef.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/read/test_nodata.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/read/test_overview.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/read/test_streaming.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/read/test_tiling.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/release_gates/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/release_gates/test_stable_features.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/test_edge_cases.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/test_fuzz_hypothesis.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/test_polish.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/test_round_trip.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/test_security.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/test_shutdown_cleanup_2486.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/unit/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/unit/test_codec_roundtrip.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/unit/test_compression.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/unit/test_geotags.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/unit/test_header.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/unit/test_ifd.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/unit/test_metadata.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/unit/test_photometric.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/unit/test_predictor.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/unit/test_safe_xml.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/unit/test_signatures.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/vrt/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/vrt/test_dtype_conversion.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/vrt/test_metadata.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/vrt/test_missing_sources.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/vrt/test_source_opt_ins_2672.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/vrt/test_validation.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/vrt/test_window.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/write/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/write/test_basic.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/write/test_bigtiff.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/write/test_cog.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/write/test_crs.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/write/test_nodata.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/write/test_overview.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/write/test_streaming.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/geotiff/tests/write/test_vrt_atomic.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/glcm.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/gpu_rtx/_memory.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/gpu_rtx/cuda_utils.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hillshade.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/_boundary_store.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/basin_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/fill_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/flow_accumulation_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/flow_accumulation_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/flow_direction_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/flow_direction_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/flow_direction_mfd.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/flow_length_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/flow_length_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/flow_path_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/flow_path_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/hand_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/hand_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/sink_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/snap_pour_point_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/stream_link_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/stream_link_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/stream_order_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/stream_order_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/conftest.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_basin_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_fill_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_flow_accumulation_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_flow_accumulation_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_flow_direction_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_flow_direction_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_flow_direction_mfd.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_flow_length_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_flow_length_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_flow_path_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_flow_path_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_hand_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_hand_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_hand_mfd.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_sink_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_snap_pour_point_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_stream_link_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_stream_link_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_stream_link_mfd.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_stream_order_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_stream_order_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_stream_order_mfd.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_twi_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_validate_cellsize.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_validate_scalar_params.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_validate_secondary_args.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_watershed_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/tests/test_watershed_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/twi_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/watershed_d8.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/hydro/watershed_dinf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/interpolate/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/interpolate/_idw.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/interpolate/_kriging.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/interpolate/_spline.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/interpolate/_validation.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/kde.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/mahalanobis.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/mcda/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/mcda/combine.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/mcda/constrain.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/mcda/sensitivity.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/mcda/standardize.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/mcda/weights.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/morphology.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/multispectral.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/normalize.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/pathfinding.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/perlin.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/polygon_clip.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/preview.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/rasterize.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/_crs_utils.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/_datum_grids.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/_grid.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/_interpolate.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/_itrf.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/_lite_crs.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/_merge.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/_projections.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/_projections_cuda.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/_transform.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/_vertical.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/grids/at_bev_AT_GIS_GRID.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/grids/au_icsm_A66_National_13_09_01.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/grids/be_ign_bd72lb72_etrs89lb08.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/grids/ch_swisstopo_CHENyx06_ETRS.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/grids/de_adv_BETA2007.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/grids/es_ign_SPED2ETV2.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/grids/nl_nsgi_rdcorr2018.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/grids/pt_dgt_D73_ETRS89_geo.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/grids/uk_os_OSTN15_NTv2_OSGBtoETRS.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/grids/us_nga_egm96_15.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/grids/us_noaa_alaska.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/grids/us_noaa_conus.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/grids/us_noaa_hawaii.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/grids/us_noaa_nadcon5_nad27_nad83_1986_conus.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/reproject/grids/us_noaa_prvi.tif +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/resample.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/sieve.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/sky_view_factor.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/surface_distance.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/terrain.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/terrain_metrics.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/__init__.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/bench_reproject_vs_rioxarray.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/conftest.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_accessor.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_analytics.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_balanced_allocation.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_bilateral.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_bump.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_classify.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_corridor.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_cost_distance.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_curvature.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_dask_cupy_gaps.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_dasymetric.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_dataset_support.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_datasets.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_diagnostics.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_diffusion.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_edge_detection.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_emerging_hotspots.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_erosion.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_fire.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_flood.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_fused_overlap.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_glcm.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_glcm_metric_order.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_gpu_rtx_memory.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_hillshade.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_hypsometric_integral.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_interpolation.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_kde.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_lite_crs.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_mahalanobis.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_mcda.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_min_observable_height.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_morphology.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_morphology_derived.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_multi_overlap.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_multispectral.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_normalize.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_optional_shapely.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_pathfinding.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_perlin.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_polygon_clip.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_polygonize_atol_rtol_backend_coverage_2026_05_27.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_polygonize_coverage_2026_05_19.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_polygonize_dask_row_batch_2608.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_polygonize_issue_2172.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_polygonize_issue_2583.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_polygonize_issue_2606.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_polygonize_issue_2666.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_polygonize_mask_dtype_coverage_2026_05_29.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_preview.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_accuracy.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_all_touched_supercover_2169.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_coverage_2026_05_17.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_coverage_2026_05_21.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_coverage_2026_05_27.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_coverage_2026_05_29.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_descending_x_2568.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_gpu_race_2167.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_nan_int_fill_2504.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_nan_propagation_2255.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_partial_dims_2569.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_props_hoist_2506.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_resolution_exact_2573.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_resolution_validation_2576.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_signature_annot_2250.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_signed_step_2566.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rasterize_tile_props_slice_2020.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_rechunk_no_shuffle.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_reproject.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_reproject_coverage_2026_05_27.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_reproject_cupy_gate_2564.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_resample.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_resample_coverage_2026_05_27.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_resample_cupy_agg_fallback_2615.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_resample_input_validation_2574.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_resample_irregular_coords_2663.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_resample_signature_annot_2544.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_sieve.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_sieve_gdal_parity.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_sky_view_factor.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_surface_distance.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_terrain.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_terrain_metrics.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_validation.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_visibility.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_zonal.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/tests/test_zonal_backend_coverage_2026_05_27.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/visibility.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/worley.py +0 -0
- {xarray_spatial-0.10.2 → xarray_spatial-0.10.3}/xrspatial/zonal.py +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module,last_inspected,issue,severity_max,categories_found,notes
|
|
2
|
+
aspect,2026-06-02,2827,MEDIUM,5,"Cat5 backend divergence: planar cupy _gpu snapped aspect>359.999 to 0 (no such clamp in numpy _cpu, whose range is [0,360) and never reaches 360), so cupy/dask+cupy disagreed with numpy by ~360 on near-degenerate gradients (gx~0+, gy>0). Removing the clamp exposed a 2nd divergence: GPU used coarse 57.29578 vs numpy 180/pi, flipping the >90 compass branch and yielding exact 360 vs 0 on uint32/uint64 random data. Fix #2827/PR #2833: GPU reuses RADIAN and wraps >=360 back to [0,360). Cats 1-4 clean; geodesic path canonicalizes consistently on CPU+GPU and was left untouched. CUDA available; cupy+dask+cupy verified (235 tests pass, numpy-vs-cupy max abs diff 0 over 360 rasters). Dedup: prior aspect fixes #2780 (cellsize)/#2774 (dask mem guard)/#2781 (oracle) all merged and unrelated. Note: PR review COMMENT could not be posted to GitHub (auto-mode permission denial); findings recorded in PR run instead."
|
|
3
|
+
balanced_allocation,2026-04-14T12:00:00Z,1203,,,float32 allocation array caused source ID mismatch for non-integer IDs. Fix in PR #1205.
|
|
4
|
+
bilateral,2026-05-01,,,,"No CRIT/HIGH/MEDIUM. Sigma underflow validated via sqrt(tiny) bound; oversize sigma clamped. float64 throughout numpy/cupy. NaN center returns NaN; NaN neighbors skipped (denom not incremented). w_sum>0 guard avoids div-by-zero. map_overlap depth==kernel radius. CUDA bounds correct. Inf input could yield 0*inf=NaN in v_sum but unvalidated input is general xrspatial pattern, not bilateral-specific."
|
|
5
|
+
contour,2026-05-01,,,,"Marching squares correct: NaN check uses self-inequality, loop bounds (ny-1,nx-1) cover all quads, dask overlap depth=1 matches 2x2 stencil, float64 cast consistent across backends, saddle disambiguation via center value. No CRIT/HIGH issues; minor LOW (Inf inputs not specifically rejected) not flagged."
|
|
6
|
+
corridor,2026-05-01,,LOW,1,"LOW: corridor inherits float32 from cost_distance; for very large accumulated costs, normalized = corridor - corridor_min loses precision near min (intrinsic to upstream dtype, not corridor itself). NaN handling correct (skipna min, np.isfinite check before normalize). All 4 backends route through pure xarray arithmetic; threshold uses dask/cupy/numpy where with try/except dispatch. No CRIT/HIGH issues."
|
|
7
|
+
cost_distance,2026-04-13T12:00:00Z,1191,,,CuPy Bellman-Ford max_iterations = h+w instead of h*w. Fix in PR #1192.
|
|
8
|
+
curvature,2026-03-30T15:00:00Z,,,,Formula matches ArcGIS reference. Backends consistent. No issues found.
|
|
9
|
+
dasymetric,2026-04-14T12:00:00Z,,,,Mass conservation correct. Weighted/binary/limiting_variable all verified. Pycnophylactic Tobler algorithm correct.
|
|
10
|
+
diffusion,2026-05-01,,LOW,1;2;5,"LOW: no Kahan summation across long iterations (drift over 100k steps, standard for explicit Euler); lap=n+s+w+e-4*val has catastrophic cancellation for nearly-uniform large values; res=0 in attrs causes div-by-zero (no guard); dask+cupy boundary='nan' relies on dask accepting cp.nan as fill. CPU/GPU NaN handling consistent (np.isnan vs val!=val). depth=1 matches stencil radius. Memory guards, CFL check, step cap all in place. No CRIT/HIGH."
|
|
11
|
+
edge_detection,2026-05-01,,,,Thin wrappers around convolve_2d with fixed Sobel/Prewitt/Laplacian kernels; no issues found
|
|
12
|
+
emerging_hotspots,2026-04-30,,MEDIUM,2;3,MEDIUM: threshold_90 uses int() (truncation) instead of ceil() so n_times=11 requires only 9/11 (81.8%) instead of 90%. MEDIUM: NaN time steps produce gi_bin=0 which classifier counts as 'non-significant' rather than missing; threshold_90 uses full n_times not valid count. LOW: 'global_std == 0' check does not catch NaN std for fully/mostly NaN inputs.
|
|
13
|
+
fire,2026-04-30,,,,All ops per-pixel (no accumulation/stencil/projected distance). NaN handled via x!=x; CUDA bounds use strict <; rdnbr and ros divisions guarded; CPU/GPU/dask paths algorithmically identical. No accuracy issues found.
|
|
14
|
+
flood,2026-04-30,,MEDIUM,2;5,"MEDIUM (not fixed): dask backend preserves float32 input dtype while numpy promotes to float64 in flood_depth and curve_number_runoff; DataArray inputs for curve_number, mannings_n bypass scalar > 0 (and CN <= 100) range validation, silently producing NaN/garbage."
|
|
15
|
+
focal,2026-06-02,2831,MEDIUM,1;5,"GPU focal_stats std/var used one-pass E[x^2]-E[x]^2 variance in float32; catastrophic cancellation collapsed std/var toward 0 on large-offset rasters (~1e6-1e7), diverging from float64 two-pass numpy/dask. Fixed cupy + dask+cupy via two-pass kernel (issue #2831, PR pending). hotspots() Gi* rewrite (#2803), dask laziness (#2802), float-dtype (#2805), GPU variety (#2800), kernel/stats_funcs validation (#2799/#2798), cupy boundary (#2736) all verified consistent across 4 backends. Cat1+Cat5."
|
|
16
|
+
geotiff,2026-05-15,1975,HIGH,1;2;5,"Pass 25 (2026-05-15): HIGH fixed -- issue #1975. _block_reduce_2d's cubic branch in xrspatial/geotiff/_writer.py gated the sentinel-to-NaN mask on arr2d.dtype.kind=='f', so to_geotiff(cog=True, overview_resampling='cubic', nodata=<finite>) on an integer raster fell through to an unmasked zoom(arr2d, 0.5, order=3). The bicubic spline blended the sentinel (e.g. -9999) into neighbouring valid cells; cast back to the source integer dtype, the boundary pixels surfaced as silent garbage. Reproduction (1024x1024 int16 + 256x256 nodata corner + nodata=-9999): lvl1 boundary [128, 124:132] showed [1082, 1082, 1085, 1134, 5, 93, 100, 100] instead of [-9999/NaN, ..., 100, 100, 100, 100]; max poisoned value 1134 (11x the actual data value of 100) and min -11104 (below the sentinel -9999). Same root cause as #1623 (float cubic + nodata) but for the integer dtype branch. Both CPU and GPU writers affected because _block_reduce_2d_gpu's cubic path falls back to _block_reduce_2d on CPU. Fix mirrors the float branch: promote the cropped block to float64, mask sentinel to NaN via the integer-range guard (mirrors _int_nodata_in_range), run scipy.ndimage.zoom(prefilter=False), rewrite NaN back to the sentinel, then np.round(...).astype(source_int_dtype) so the integer cast is well-defined. 12 regression tests in test_cog_cubic_int_overview_nodata_1975.py: helper-level cubic per int dtype (int16, uint16, int32), no-nodata regression, out-of-range sentinel no-op, fractional sentinel no-op, all-sentinel block fallback, float cubic regression guard, end-to-end 1024x1024 round-trip, non-constant int regression, cubic-vs-mean sentinel-mask parity, and GPU/CPU byte parity. All 3186 non-stale geotiff tests still pass (2 pre-existing failures unrelated: test_predictor2_big_endian_gpu references the hidden read_to_array symbol, and test_size_param_validation_gpu_vrt_1776 asserts pre-#1767 tile_size=4 behaviour). Categories: Cat 1 (precision loss from cubic spline blending sentinel into valid cells) + Cat 2 (NaN-equivalent corruption: the read-side int-to-NaN mask only catches exact sentinel hits, so the poisoned values survive as legitimate measurements) + Cat 5 (backend parity: CPU and GPU writers shared the same wrong cubic path). | Pass 23 (2026-05-14): HIGH fixed -- issue #1847. extract_geo_info parsed GDAL_NODATA via float() unconditionally, which loses 1 ULP on uint64 max (2**64-1) and int64 max (2**63-1). The downstream integer-mask gate info.min <= int(nodata) <= info.max then rejects the cast because float-rounded sentinel is one above the dtype max; the sentinel pixel survives as a literal valid integer instead of NaN. Same float-only parse in _reader._resolve_masked_fill (LERC fill) and _reader._sparse_fill_value (SPARSE_OK fill). VRT _vrt._parse_band_nodata had already fixed this for the XML parse path (PR #1833) but TIFF source-of-truth was never updated, so write_vrt([uint64.tif]) stringified the float-parsed nodata as '1.8446744073709552e+19' into XML where the VRT reader then rejected it for being out of range. Fix: lift the int-first parse into shared helper _parse_nodata_str in _geotags.py and reuse across the three TIFF-side sites. The helper tries int(text) first to preserve full precision, falls back to float(text) for NaN/Inf/scientific/fractional. Downstream gates already handle int values transparently because np.isfinite(int) works and int(int) is a no-op. 25 regression tests in test_nodata_int64_precision_1847.py: unit-level _parse_nodata_str matrix (int vs float branches, edge cases), eager open_geotiff (uint64 max / int64 max / int64 min / uint16 / int32 / float regression guards), read_geotiff_dask (uint64 max, int64 max), write_vrt + read_vrt round-trip with XML literal assertion, and a GPU parity test. All 2434 non-stale geotiff tests still pass (1 pre-existing test_size_param_validation_gpu_vrt_1776 failure unrelated -- test asserts pre-#1767 tile_size=4 behaviour). Categories: Cat 2 (NaN propagation: sentinel pixel survived as literal valid number on all 4 backends) + Cat 5 (backend inconsistency: VRT XML parse path handled 64-bit sentinels via _parse_band_nodata but TIFF parse path did not, even though write_vrt fed the latter into the former). Audited but did not file: LOW silent kwarg drop -- to_geotiff(da, 'out.vrt', photometric='miniswhite') drops the photometric arg at _write_vrt_tiled call (per-tile files written as MinIsBlack). Data round-trips correctly because no inversion happens on either side; only the tile photometric tag disagrees with the user's request. Niche path + no data corruption + metadata-only drift = LOW, not filed. | Pass 22 (2026-05-13): HIGH fixed -- issue #1809. MinIsWhite (photometric=0) inversion ran before the sentinel-to-NaN nodata mask on all four backends (eager numpy in open_geotiff, dask chunk reader, eager GPU in read_geotiff_gpu, GPU stripped fallback). Because the inversion rewrites the original sentinel value (e.g. uint8 nodata=0 becomes 255, float32 nodata=-9999 becomes 9999), the post-inversion mask matched the wrong pixels: cells whose stored value happened to equal iinfo.max - sentinel were flagged NaN while real sentinel cells survived as inverted values. PR #1804 (a5d78e4) had refactored the helper but kept the original ordering. Fix: introduce _miniswhite_inverted_nodata in _reader.py and stash the inverted sentinel on geo_info._mask_nodata; route every backend mask through that field, keeping geo_info.nodata + attrs[nodata] at the original value for write-side round-trip. Dask path also re-inverts the closure nodata at graph-build time, picking up _ifd_photometric / _ifd_samples_per_pixel stashed in _read_geo_info. 9 regression tests in test_miniswhite_nodata_1809.py cover uint8 nodata=0, uint16 nodata=65535, float32 nodata=-9999 across numpy, dask, and GPU backends plus no-collision and no-nodata controls. All 2424 non-stale geotiff tests pass (4 pre-existing failures unrelated to this fix). Categories: Cat 2 (NaN propagation: real data became NaN while sentinel survived as inverted value) + Cat 5 (backend inconsistency: all four backends share the identical wrong result, so they agreed on the wrong answer rather than diverged). | Pass 21 (2026-05-13): MEDIUM fixed -- issue #1774. open_geotiff / read_geotiff_dask / _apply_nodata_mask_gpu crashed with ValueError: cannot convert float NaN to integer when reading an integer TIFF whose GDAL_NODATA tag was the string ""nan"" / ""inf"" / ""-inf"". Three sites in xrspatial/geotiff/__init__.py called int(nodata) on the integer-dtype branch without first checking np.isfinite. _geotags.py:extract_geo_info parses the GDAL_NODATA tag through float(nodata_str) so a ""nan"" tag surfaces as Python NaN; the integer mask code then explodes. Sibling helpers _resolve_masked_fill and _sparse_fill_value in _reader.py already gate on not math.isnan(v) and not math.isinf(v) (the unfinished pass of #1581). Fix: gate each int(nodata) cast on np.isfinite(nodata). A non-finite sentinel on an integer file cannot match any pixel, so the mask is a no-op and the file dtype is preserved; attrs['nodata'] still carries the raw NaN/Inf sentinel so a write round-trip keeps the original GDAL_NODATA tag. The read_geotiff_dask effective_dtype branch already used try/except and was safe in practice, but tightened with the same isfinite gate for readability. 15 regression tests in test_nodata_nan_int_1774.py covering eager numpy (3 NaN variants + 6 Inf variants), in-range finite still masks regression guard, dask (NaN + Inf), and GPU (NaN + Inf + finite). All pass; 2023 existing geotiff tests still pass (7 pre-existing test_predictor2_big_endian_gpu failures unrelated: they reference xrspatial.geotiff.read_to_array which was hidden from the public namespace in #1708, 3 pre-existing matplotlib palette failures in test_features.py unrelated). Categories: Cat 2 (NaN propagation: NaN nodata produced a crash instead of being treated as missing) + Cat 5 (backend inconsistency: _resolve_masked_fill / _sparse_fill_value already guarded; the three __init__.py sites did not). | Pass 20 (2026-05-12): HIGH fixed -- PR #1691 (no issue created; agent harness blocked gh issue create). Integer COG overview pyramid mixed sentinel into reduced pixels. _block_reduce_2d (_writer.py:258-264) and _block_reduce_2d_gpu (_gpu_decode.py:3027-3028) promoted integer blocks to float64 but never masked the sentinel to NaN before nanmean / nanmin / nanmax / nanmedian. The reduction averaged the sentinel into surrounding valid cells (e.g. (-9999 + 100 + 100 + 100)/4 = -2425 cast back to int16), producing overview pixels that the read-side int-to-NaN mask in open_geotiff couldn't recover because they didn't equal the sentinel. Silent garbage at every zoom above level 0 for to_geotiff(int_data, cog=True, nodata=N). Methods affected: mean, min, max, median; nearest/mode safe (no averaging). Fix: gate the sentinel-to-NaN mask on representability in the source integer dtype (mirrors _int_nodata_in_range in _reader.py) so uint16+GDAL_NODATA=""-9999"" stays a no-op; rewrite all-sentinel-block NaN back to sentinel before the integer dtype cast so the cast is well-defined (the caller's post-overview loop in write() only runs for floats). GPU mirror gets the same path with cupy.where + cupy.isnan for byte parity with CPU. 38 regression tests in test_cog_int_overview_nodata_2026_05_12.py: _block_reduce_2d per-dtype/per-method matrix (uint8/uint16/int16/int32 x mean/min/max/median), all-sentinel-block, no-nodata regression, out-of-range sentinel no-op, end-to-end uint16 + int16 round-trip, 3-band integer COG, GPU per-dtype/per-method matrix, CPU/GPU byte-match parity. All 1606 existing geotiff tests still pass. Categories: Cat 1 (precision/representation loss in nan-aware reduction) + Cat 2 (silent NaN-equivalent corruption from sentinel poisoning) + Cat 5 (backend parity between float and integer code paths within the same writer). Deferred LOW: HTTP COG path (_read_cog_http at _reader.py:1638) skips the band-range validation that local/dask/GPU added in #1673; band=-1 silently selects the last channel on HTTP while local raises IndexError. Cat 5, MEDIUM-leaning but separate concern from the overview fix; one-finding-per-PR per project policy. | Pass 19 (2026-05-12): MEDIUM fixed -- issue #1655. read_vrt silently dropped <NODATA>0</NODATA> on a SimpleSource because of src.nodata or nodata at _vrt.py:370. Python treats 0.0 as falsy, so the per-source sentinel fell through to the band-level <NoDataValue> (or None when missing) and pixels equal to 0.0 in the source file survived as valid data. The in-code comment acknowledged the quirk as backward compat, but the resulting behaviour silently biased every NaN-aware aggregation on VRT mosaics whose sources used 0 as a sentinel (a common convention for unsigned remote-sensing imagery). Fix: src_nodata = src.nodata if src.nodata is not None else nodata. Five regression tests in test_vrt_source_nodata_zero_1655.py covering source NODATA=0, integer XML literal, non-zero unchanged, band-level NoDataValue=0 still honoured, and source-overrides-band precedence. All 100 vrt-related geotiff tests still pass; 3 pre-existing test_features.py matplotlib palette failures unrelated. Categories: Cat 2 (NaN propagation) + Cat 5 (backend inconsistency: read_geotiff masks 0 correctly when GDAL_NODATA tag is set; only VRT path was broken). | Pass 18 (2026-05-11): MEDIUM fixed -- issue #1642. PR #1641 (issue #1640) inherited level-0 georef on overview reads but kept the level-0 origin_x/origin_y unchanged. That is correct for PixelIsArea (origin = upper-left corner of pixel (0,0)) but wrong for PixelIsPoint (origin = center of pixel (0,0), GeoKey 1025 = 2). For a 1024x1024 PixelIsPoint COG with 10 m pixels and origin (0, 0), open_geotiff(overview_level=1) returned x[:3]=[0,20,40] instead of [5,25,45] (level-1 pixel 0 covers level-0 pixels 0-1 whose centers are 0 and 10, centroid 5); same for y. Downstream sel/interp/reproject silently snaps to the wrong pixel for any DEM-style PixelIsPoint COG (USGS, OpenTopography, Copernicus DEM). Categories: Cat 3 (off-by-one / boundary handling) + Cat 5 (raster_type-dependent backend convention). Fix: in extract_geo_info_with_overview_inheritance (_geotags.py), pick the effective raster_type first (overview-declared if non-default, otherwise inherited from parent), then when it is PixelIsPoint apply origin_shift = (scale - 1) * 0.5 * pixel_size_lvl0 along each axis before building the new GeoTransform. PixelIsArea path is byte-equivalent. 13 regression tests in test_overview_pixel_is_point_1642.py: centroid identity across all 4 backends, transform tuple across all 4 backends, uniform grid step, unit-level helper tests for both raster_types via stubbed extract_geo_info, own-geokeys-not-clobbered path on PixelIsPoint, and a PixelIsArea regression check. All 1397 existing non-network geotiff tests still pass (3 pre-existing matplotlib palette failures unrelated). Deferred LOW: non-power-of-two overview dimensions cause scale = base_w/ov_w to diverge from the true 2^level reduction (writer drops the right/bottom strip via h2=(h//2)*2; for h=1023 a level-1 overview has 511 rows so scale=2.0019 not 2.0). Fix would need to either (a) emit explicit geo tags on overview IFDs from the writer or (b) pass the level number into the inheritance helper; neither is a one-line change and the resulting coord error is sub-pixel of level 0. | Pass 17 (2026-05-11): MEDIUM fixed -- issue #1634. open_geotiff eager path windowed read produced confusing CoordinateValidationError when window extended past source extent. read_to_array clamped the window internally and returned a smaller array, but the eager code path used unclamped window indices for y/x coord generation (xrspatial/geotiff/__init__.py lines 562-572), so the coord array length differed from the data and xarray refused to construct the DataArray. Same bug affected the windowed transform shift in _populate_attrs_from_geo_info. The dask path (read_geotiff_dask) already validated up front since #1561, raising a clear ValueError with the format 'window=... is outside the source extent (HxW) or has non-positive size.' so the two backends diverged on the contract. Fix: validate the window up front in open_geotiff's eager branch via _read_geo_info (metadata-only read, no extra pixel cost) using the exact same condition the dask path uses, raising the same ValueError message format. Reproduction: 10x10 raster + window=(5,5,15,15) on eager raised CoordinateValidationError('conflicting sizes ... length 5 ... length 10'); now raises ValueError('window=(5, 5, 15, 15) is outside the source extent (10x10) or has non-positive size.'). Categories: Cat 3 (off-by-one / boundary handling) + Cat 5 (backend inconsistency). 12 regression tests in test_window_out_of_bounds_1634.py: negative start, past-right-edge, past-bottom-edge, past-both-edges, zero-size, inverted window, full-extent ok, interior subset, edge-aligned, eager-vs-dask parity, message-format parity, issue reproducer. All 1286 existing non-network geotiff tests still pass. | Pass 16 (2026-05-11): HIGH fixed -- issue #1623. to_geotiff(cog=True, overview_resampling='cubic', nodata=<finite>) on a float raster with NaN regions produced overview pixels with severe ringing artefacts near nodata borders. Same class of bug as #1613 but for the cubic branch: writer rewrites NaN to the sentinel upstream, then _block_reduce_2d(method=cubic) handed the sentinel-poisoned array straight to scipy.ndimage.zoom(order=3). The cubic spline blended the sentinel (e.g. -9999) into neighbouring cells, producing values like 1133.44, -10290.08 where the data was a constant 100. Repro on 16x16 float32 with a 4x4 NaN corner showed 18 polluted pixels in the 8x8 overview. Fix: when nodata is supplied on a float dtype and the sentinel is found, mask sentinel to NaN, run cubic with prefilter=False so a single NaN cannot poison the entire row/column (default B-spline prefilter is global), then rewrite any NaN in the result back to the sentinel. prefilter=False only fires when a sentinel is present so the non-nodata cubic semantics are unchanged. GPU side: _block_reduce_2d_gpu previously raised on method='cubic'; added a CPU fallback (same pattern as 'mode') so GPU writer produces byte-equivalent overviews. GPU_OVERVIEW_METHODS now includes 'cubic'. 12 regression tests in test_cog_cubic_overview_nodata_1623.py (helper no-ringing, poisoning repro, no-nodata unchanged, end-to-end round-trip, GPU fallback, CPU/GPU byte-match, +/-inf nodata mask, NaN-sentinel no-op, GPU_OVERVIEW_METHODS contract). All 1256 existing geotiff tests still pass (3 pre-existing matplotlib failures unrelated). | Pass 15 (2026-05-11): HIGH fixed -- issue #1613. to_geotiff(cog=True, nodata=<finite>) on a float raster with NaN produced a corrupted overview pyramid. The NaN-to-sentinel rewrite in __init__.py:1202 (CPU) and :2852 (GPU write_geotiff_gpu) ran BEFORE _make_overview / make_overview_gpu, so the nan-aware aggregations (np.nanmean/min/max/median, cupy.nanmean/min/max/median) saw the sentinel as a real number and biased every overview pixel. Reproduction with -9999 sentinel produced [[-4998.75,-4997.75],..] where np.nanmean gives [[1.5,3.5],..]. Both CPU and GPU paths affected; backend results matched each other but were both wrong (CAT 2 NaN propagation + CAT 5 documents the parity). Fix: _block_reduce_2d / _block_reduce_2d_gpu accept a nodata kwarg that masks the sentinel back to NaN for float dtypes before the reduction; the writer's overview loop passes nodata in, then rewrites all-sentinel reductions (which surface as NaN from the reducer) back to the sentinel for the on-disk pyramid. 11 regression tests in test_cog_overview_nodata_1613.py (CPU mean / partial-block / min/max/median / no-nodata passthrough / helper kwarg / all-sentinel block / GPU mean / GPU helper / CPU-GPU agreement). All 235 nodata/overview/cog tests still pass. | Pass 14 (2026-05-11): HIGH fixed -- issue #1611. read_vrt(band=None) on a multi-band integer VRT with per-band <NoDataValue> tags only masks band 0's sentinel. __init__.py lines 2795-2809 in read_vrt apply vrt.bands[0].nodata to the full ndim==3 array; bands 1+ keep their integer sentinels as literal finite values (e.g. 65000 surfaces as 65000.0 after the dtype=float64 cast, not NaN). Float-VRT path masks per-band correctly in _vrt._read_data lines 296-297 + 347-351. PR #1602 fixed the single-band band=N case for issue #1598; the band=None multi-band case is the same class of bug. Repro: 2-band uint16 VRT with NoDataValue 65535 / 65000 returns r.values[1,1,1] == 65000.0 instead of NaN; r.values[1,1,0] is NaN (band 0 sentinel masked). Fix scope: in read_vrt, when band is None, iterate over vrt.bands and mask each arr[..., i] slice against its own <NoDataValue> (gated by the same _int_nodata_in_range guard PR #1583 introduced). Severity HIGH (Cat 2 NaN propagation + Cat 5 backend inconsistency: identical input semantics produce different masking outcomes based on dtype, with finite garbage values where NaN expected). Fix in PR #1612: walks vrt.bands when band is None and ndim==3, masks each arr[..., i] slice against its own <NoDataValue> via the refactored _sentinel_for_dtype helper (reuses PR #1583's range guard so out-of-range/non-finite/fractional sentinels are a no-op). attrs['nodata'] still carries band 0's sentinel for band=None reads (documented contract). 7 regression tests in test_vrt_multiband_int_nodata_1611.py: uint16 per-band, int32 negative, mixed presence, dtype preservation when no sentinel hit, out-of-range gating, band=N non-regression, attrs contract. 135 existing vrt/nodata geotiff tests still pass. | Pass 13 (2026-05-11): HIGH fixed -- issue #1599. write_geotiff_gpu (and to_geotiff gpu=True) emitted raw NaN bytes for missing pixels even when nodata=<finite> was supplied, while the CPU writer substituted NaN with the sentinel before encoding. xrspatial-only round-trips were unaffected (the reader masks both NaN and the sentinel), but external readers (rasterio/GDAL/QGIS) that mask only on the GDAL_NODATA tag saw NaN pixels as valid data -- rasterio reported 100% valid pixels on a 25-NaN file vs CPU's 25-invalid report. Root cause: __init__.py lines 2579-2587 jumped from shape/dtype resolution straight to compression, missing the equivalent of the CPU writer's NaN-to-sentinel rewrite at to_geotiff line ~1156. Fix: cupy.isnan + masked write on a defensive copy of arr, gated on np_dtype.kind=='f' and not np.isnan(float(nodata)). Caller's CuPy buffer preserved (copy before mutate). 7 regression tests in test_gpu_writer_nan_sentinel_1599.py: substitution lands as sentinel, CPU/GPU byte-equivalent, caller buffer not mutated, no-NaN no-op, NaN sentinel skips substitution, rasterio sees identical invalid count on CPU/GPU, multiband 3D path. All other GPU writer tests still pass (50 passed across band-first, attrs, nodata, dask+cupy, writer, nodata aliases). | Pass 12 (2026-05-11): HIGH fixed -- issue #1581. Reading a uint TIFF with a negative GDAL_NODATA sentinel (e.g. uint16 + -9999) raised OverflowError on every backend because the nodata-mask code did arr.dtype.type(int(nodata)) with no range check. Three identical cast sites in __init__.py (numpy eager, _apply_nodata_mask_gpu, _delayed_read_window) plus _resolve_masked_fill and _sparse_fill_value in _reader.py. Fix: _int_nodata_in_range helper gates the cast; out-of-range sentinels are a no-op for value matching (the file can never contain that value), file dtype is preserved, attrs['nodata'] still surfaces the original sentinel so write round-trips keep the GDAL_NODATA tag intact. Matches rasterio behavior. 8 regression tests in test_nodata_out_of_range_1581.py cover the helper, both eager and dask read paths, in-range sentinel non-regression, and GPU helper (cupy-gated). | Pass 11 (2026-05-10): CLEAN. Audited the one additional commit since pass 10 -- #1559 (PR 1548, Centralise GeoTIFF attrs population across all read backends). Refactor extracts _populate_attrs_from_geo_info helper and routes eager numpy, dask, GPU stripped, GPU tiled read paths through it; before the fix dask only emitted crs/transform/raster_type/nodata while numpy emitted the full attrs set including x/y_resolution, resolution_unit, image_description, extra_samples, GDAL metadata, and the CRS-description fields. No data-path arithmetic touched; only attrs dict population. Windowed origin math (origin_x + c0*pixel_width, origin_y + r0*pixel_height) verified to produce -98.0 / 48.75 origin for window=(10,20,50,70) on a (0.1,-0.125) pixel-size raster, with PixelIsArea half-pixel offset preserved on coord lookups (-97.95, 48.6875). Cross-backend attrs parity re-verified: numpy/dask/cupy all emit identical key set on deflate+predictor3+nodata round-trip (crs, crs_wkt, nodata, transform, x_resolution, y_resolution). Data bit-parity re-verified across numpy/dask/cupy on same payload (np.array_equal with equal_nan=True). test_attrs_parity_1548.py (5 tests), test_reader.py/test_writer.py/test_dask_cupy_combined.py (25 tests), GPU orientation/predictor2-BE/LERC-mask/nodata/byteswap suites (65 tests) all green. No accuracy or backend-divergence findings. | Pass 10 (2026-05-10): CLEAN. Audited 5 recent commits: #1558 drop-defensive-copies (frombuffer path still .copy()s before in-place predictor decode at _reader.py:778), #1556 fp-predictor ngjit (writer pre-ravels so 1-D slice arg is correct, float32/64 LE+BE bit-exact), #1552 batched D2H (OOM guard fires before cupy.concatenate, host_buf offsets correct), #1551 parallel-decode gate (>= vs > sends 256x256 default to parallel path, no value diff confirmed via partial-tile parity), #1549 nvjpeg constants (gray + RGB GPU JPEG decode pixel-identical to Pillow CPU, max diff = 0). Cross-backend parity re-verified clean: numpy/dask+numpy/cupy/dask+cupy equal .data/.dtype/.coords/nodata/NaN-mask on deflate+predictor3+nodata; orientations 1-8 numpy==GPU; partial edge tiles 100x150, 257x383, 512x257 numpy==GPU==dask; predictor2 LE/BE round-trip uint8/int16/uint16/int32/uint32 pass; predictor3 LE/BE float32/64 pass. Deferred LOW (pre-existing, not opened): float16 (bps=16, SampleFormat=3) absent from tiff_dtype_to_numpy map - writer never emits, asymmetric but unreachable. | Pass 9 (2026-05-09): TWO HIGH fixed -- (a) PR #1539 closes #1537: TIFF Orientation tag 2/3/4 (mirror flips) on georeferenced files left y/x coords computed from the un-flipped transform, so xarray label lookups returned the wrong pixel even though _apply_orientation flipped the buffer. PR #1521 only updated the transform for the 5-8 axis-swap branch. Fix updates origin and pixel-scale signs along whichever axes were flipped, for both PixelIsArea (origin shifts by N*step) and PixelIsPoint (shifts by (N-1)*step). 10 new tests in test_orientation.py. (b) PR #1546 closes #1540: read_geotiff_gpu ignored Orientation tag completely; CPU correctly applied 2-8 (PR #1521) but GPU returned the raw stored buffer. Cross-backend disagreement on every non-default orientation. Fix adds _apply_orientation_gpu (cupy slicing mirror of the CPU helper) and _apply_orientation_geo_info, threads them into the tiled GPU pipeline, reuses CPU-fallback geo_info for the stripped path to avoid double-applying. 28 new tests in test_orientation_gpu.py (every orientation, single-band tiled, single-band stripped, 3-band tiled, mirror-flip sel-fidelity, default no-tag passthrough). Re-confirmed clean: HTTP coalesce_ranges with overlapping ranges and zero-length ranges, parallel streaming write thread-safety (each tile gets independent buffer via copy or padded zeros), planar=2 + chunky GPU LERC mask propagation matches CPU, IFD chain cap MAX_IFDS=256, max_z_error round-trip on tiled write, _resolve_masked_fill float vs integer dtype semantics. Deferred LOW: per-sample LERC mask (3D mask (h,w,samples)) collapsed to per-pixel ""any sample invalid"" on GPU while CPU honours per-sample; LERC implementations rarely emit 3D masks (verified: lerc.encode with 2D mask on 3-band returns 2D mask). Documented planar=2 + LERC + GPU silently drops mask (rare in practice, source comment acknowledges). | Pass 8 (2026-05-07): HIGH fixed in fix-jpeg-tiff-disable -- to_geotiff(compression='jpeg') wrote files that no external reader can decode. The writer tags compression=7 (new-style JPEG) but emits a self-contained JFIF stream per tile/strip and never writes the JPEGTables tag (347) that the TIFF spec requires for that codec. libtiff/GDAL/rasterio all reject the file with TIFFReadEncodedStrip() failed; our reader round-trips because Pillow decodes the standalone JFIF, hiding the break. Pass-4 notes flagged the read side of the same JPEGTables gap and deferred it; pass-8 covers the write side. Fix: reject compression='jpeg' at the to_geotiff entry with a clear ValueError pointing at deflate/zstd/lzw. The internal _writer.write is untouched so the existing self-decoding tests still cover the codec; re-enabling the public path needs a JPEGTables-aware encoder. PR diffs reviewed but not merged: #1512 (BytesIO source) and #1513 (LERC max_z_error) -- both look correct; #1512 file-like read path goes through read_all() once so the per-call BytesIOSource lock is theoretical, and #1513 forwards max_z_error through every overview/tile/strip/streaming path including _write_vrt_tiled and _compress_block. No regressions found in either open PR. Other surfaces audited clean: predictor=3 with float16 (writer auto-promotes to float32 on both eager and streaming paths, value-exact round-trip); planar=2 multi-tile read uses band_idx*tiles_per_band offset so no cross-contamination between planes; _header.py multi-byte tag parsing uses bo (byte_order) consistently; Pillow YCbCr-vs-tagged-RGB photometric mismatch becomes moot once JPEG is disabled. Deferred (LOW/MEDIUM, not filed): JPEG2000 writer accepts arbitrary dtype with no validation (rare codec, narrow risk); float16 dtype not in tiff_dtype_to_numpy decode map (writer never emits it - asymmetric but unreachable); Orientation tag (274) still ignored on read (pass-4 deferral). | Pass 7 (2026-05-07): HIGH fixed in fix-mmap-cache-refcount-after-replace -- _MmapCache.release() looked up the cache entry by realpath, so a holder that acquired the OLD mmap before an os.replace and released it AFTER another caller had acquired the post-replace entry would decrement the new holder's refcount. Subsequent eviction (cache full, or another acquire) closed the still-in-use mmap, breaking reads with 'mmap closed or invalid'. Real exposure: any concurrent reader/writer pattern where to_geotiff replaces a file that another reader had just opened via open_geotiff with chunks= or via _FileSource. PR #1506 added stale-replacement detection but did not fix the refcount confusion across the pop. Fix: acquire returns an opaque entry token; release takes the token and decrements that exact entry, regardless of cache state. Orphaned (popped) entries close their fh+mmap when their own refcount hits zero. _FileSource updated to pass the token. Regression test test_release_after_path_replacement_does_not_clobber_new_holder added. All 665 geotiff tests pass; GPU path verified. | Pass 6 (2026-05-07) PR #1507: BE pred2 numba TypingError. | Pass 5 (2026-05-06) PR #1506: mmap cache stale after file replace. | Pass 4 (2026-05-06) PR #1501: sparse COG tiles. | Pass 3 (2026-05-06) PR #1500: predictor=3 byte order. | Pass 2 (2026-05-05) PR #1498: predictor=2 sample-wise. | Pass 1 (2026-04-23) PR #1247. Re-confirmed clean over passes 2-7: items 2 (writer always emits LE TIFFs - hardcoded b'II'), 3 (RowsPerStrip default = height when missing), 4 (StripByteCounts missing raises clear ValueError), 5 (TileWidth without TileLength caught by 'tw <= 0 or th <= 0' check at _reader.py:688), 9 (read determinism on compressed+tiled+multiband), 11 (predictor=2 with awkward sample stride round-trips), 18 (compression_level=99 raises ValueError 'out of range for deflate (valid: 1-9)'), 21 (concurrent writes serialize correctly via mkstemp+os.replace), 24 (uint16 dtype preserved on numpy backend, dask honors chunks param), 26 (chunks rounds correctly with remainder chunk for non-tile-aligned). Deferred: item 8 (BytesIO/file-like sources are not supported, source.lower() error) - documented as 'str' parameter, not a bug; item 19 (LERC max_z_error not user-exposed by to_geotiff) - missing feature, not a bug."
|
|
17
|
+
glcm,2026-05-01,1408,HIGH,2,"angle=None averaged NaN as 0, masking no-valid-pairs as zero texture; fixed via nanmean-style averaging"
|
|
18
|
+
hillshade,2026-04-10T12:00:00Z,,,,"Horn's method correct. All backends consistent. NaN propagation correct. float32 adequate for [0,1] output."
|
|
19
|
+
hydro,2026-04-30,,LOW,1,Only LOW: twi log(0)=-inf if fa=0 (out-of-contract); MFD weighted sum no Kahan (negligible). No CRIT/HIGH issues.
|
|
20
|
+
kde,2026-04-13T12:00:00Z,1198,,,kde/line_density return zeros for descending-y templates. Fix in PR #1199.
|
|
21
|
+
mahalanobis,2026-05-01,,LOW,1,"LOW: np.linalg.inv (no pinv fallback) returns garbage for near-singular cov without raising. LOW: two-pass mean/cov instead of Welford could lose precision for inputs with very large mean/small variance. No CRIT/HIGH; all four backends use float64 throughout, NaN handled via isfinite, dist_sq clamped non-negative, singular case raises ValueError."
|
|
22
|
+
morphology,2026-04-30,"1397,1399",HIGH,2;5,HIGH fixed in #1397/PR #1398: morph_erode/dilate seeded centre cell into running min/max even when kernel[centre]==0 (all 4 backends). HIGH fixed in #1399/PR #1400: dask backends raised on 1xN/Nx1 kernels because empty-slice writeback (0:-0).
|
|
23
|
+
multispectral,2026-03-30T14:00:00Z,1094,,,
|
|
24
|
+
normalize,2026-05-01,,,,rescale and standardize across all 4 backends. NaN/inf filtered via isfinite mask before min/max/mean/std. Constant input handled (range=0 -> new_min; std=0 -> 0.0). Output dtype float64 consistently. Backend parity covered by test_matches_numpy. No accuracy issues found.
|
|
25
|
+
perlin,2026-04-10T12:00:00Z,,,,Improved Perlin noise implementation correct. Fade/gradient functions verified. Backend-consistent. Continuous at cell boundaries.
|
|
26
|
+
polygon_clip,2026-04-13T12:00:00Z,1197,,,crop=True + all_touched=True drops boundary pixels. Fix in PR #1200.
|
|
27
|
+
polygonize,2026-05-29,2606,HIGH,5,"Cat 5 HIGH: dask connectivity=8 cross-chunk merge filled diagonal notch where same-value regions meet only at a corner across a chunk boundary; total area exceeded raster. Hole ring was dropped because containment tested hole[0] (on exterior at pinch). Fixed via _ring_interior_point in PR for #2606. numpy, dask+numpy, dask+cupy area parity now holds; 4-conn was already correct. cupy + dask+cupy paths validated on GPU host. Other cats clean: NaN masked on numpy/cupy float paths (tested), _is_close handles +/-inf via exact-equality short-circuit, atol/rtol/simplify_tolerance reject NaN/inf, integer GPU CCL matches numpy."
|
|
28
|
+
proximity,2026-05-29,2721,MEDIUM,4;5,Bounded GREAT_CIRCLE on dask (both numpy+cupy) raised ValueError: map_overlap pad depth = max_distance/cellsize mixed metre distance with degree cellsize. numpy/cupy backends fine. Fixed by measuring per-pixel pitch with active metric (PR #2722). Cat1 float32 output is documented design choice; NaN/Inf masking via np.isfinite consistent; numpy GDAL-sweep matches exact nearest and cupy brute-force on tested grids.
|
|
29
|
+
reproject,2026-05-29,2620,HIGH,5,"Cat5 backend inconsistency: cupy _resample_cupy (cupyx map_coordinates) diverged from numpy/native on pyproj-fallback CRS pairs (projected->projected, e.g. EPSG:32633->3857). Edge-band cval=0.0 bleed (all modes, ~534/pixel) + cubic B-spline vs Catmull-Rom (~0.45 interior). Fixed PR for #2620: route eager+dask cupy through _resample_cupy_native. Other files clean: _merge numpy/cupy structurally identical; _datum_grids/_vertical/_itrf use -0.5 pixel-center interp and self-inequality NaN checks; WGS84/GRS80 constants correct; curvature correction n/a (no geodesic gradient here). LOW (not fixed): _transform._bilinear_interp_2ch docstring claims parallel but isn't."
|
|
30
|
+
resample,2026-05-29,2610,HIGH,3;5,"dask interp (nearest/bilinear) overlap depth=1 too small on downsample; block-centered source coord landed past chunk, map_coordinates clamped to edge -> wrong seam rows. Fixed PR #2627 via per-axis _downsample_radius. cupy+dask+cupy verified."
|
|
31
|
+
sieve,2026-04-13T12:00:00Z,,,,Union-find CCL correct. NaN excluded from labeling. All backends funnel through _sieve_numpy.
|
|
32
|
+
sky_view_factor,2026-05-01,1407,HIGH,4,Horizon angle ignored cell size; fixed by passing cellsize_x/cellsize_y into CPU+GPU kernels and using ground distance
|
|
33
|
+
terrain,2026-04-10T12:00:00Z,,,,Perlin/Worley/ridged noise correct. Dask chunk boundaries produce bit-identical results. No precision issues.
|
|
34
|
+
terrain_metrics,2026-04-30,,LOW,2;5,"LOW: Inf input not rejected, propagates as Inf (consistent across backends but undocumented). LOW: dask+cupy non-nan boundary path double-pads (wasted compute, central output values still correct). No CRIT/HIGH; tests cover NaN propagation, all 4 backends, all 4 boundary modes, dtype acceptance."
|
|
35
|
+
viewshed,2026-05-29,2691,HIGH,3;5,max_distance window sized from coarser axis clipped cells on anisotropic rasters (PR #2702). LOW unfixed: distance_sweep ring radius same max(res) pattern but max_distance arg always None; _calculate_event_row_col line 880 abs(x>1) precedence bug is a broken guard only. cuda+rtx paths validated.
|
|
36
|
+
visibility,2026-04-13T12:00:00Z,,,,"Bresenham line, LOS kernel, Fresnel zone all correct. All backends converge to numpy."
|
|
37
|
+
worley,2026-05-01,,MEDIUM,2;5,"MEDIUM: numpy backend uses np.empty_like(data) so integer input dtype produces integer output (distances truncated to 0); cupy/dask paths always produce float32. LOW: freq=inf produces 100000 sentinel (sqrt of initial min_dist=1e10), no validation of freq/seed for non-finite values."
|
|
38
|
+
zonal,2026-05-27,2528,MEDIUM,5,"Pass 2 (2026-05-27): MEDIUM fixed -- issue #2528. zonal_stats() on dask-backed inputs silently dropped 'majority' from the requested stats list. The mutable default stats_funcs included 'majority' (added in commit 7c8d5759), but the dask path filtered it out at xrspatial/zonal.py:459 (computed_stats = [s for s in stats_funcs.keys() if s in stats_dict]) because 'majority' is not in _DASK_BLOCK_STATS. Symptom: stats(zones=dask, values=dask) returned 7 columns instead of the 8 the docstring promises; stats(..., stats_funcs=['mean','majority']) returned only ['zone','mean'] with no error or warning. Both dask+numpy and dask+cupy were affected (dask+cupy delegates to dask+numpy). Fix: replaced the mutable list literal default with stats_funcs=None and resolved the default per backend inside the function -- numpy/cupy get the full 8-stat list, dask gets the 7-stat subset (no majority). Explicit majority on dask now raises ValueError with a clear supported-stats message instead of silently filtering. 4 regression tests in test_zonal.py: explicit majority raises on dask, bare default omits majority on dask, bare default keeps majority on numpy, default list is not mutated across calls (covers the historical mutable-default pitfall). All 129 test_zonal.py tests pass (125 pre-existing + 4 new); test_dasymetric.py 61 tests still pass (dasymetric uses zonal.stats internally). Categories: Cat 5 (backend inconsistency: numpy/cupy honoured majority; dask paths silently dropped it). | Pass 1 (2026-03-30T12:00:00Z): historical entry #1090."
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module,last_inspected,issue,severity_max,categories_found,notes
|
|
2
|
+
aspect,2026-06-02,2742;2829,HIGH,3;4,"#2742: degenerate shapes (1x1/Nx1/1xN) + geodesic boundary modes; tests added all 4 backends, GPU-validated. #2829: northness/eastness method='geodesic' branch was untested (planar only); added correctness (diagonal surface where planar!=geodesic) + 4-backend parity, GPU-validated. all-NaN planar/geodesic returns all-NaN (correct). Inf input -> silent -1/flat on spike cell: possible source bug, out of scope for test-only sweep, not filed. Dedup: rectangular-cell oracle #2781 + cell-size #2780 already merged, not duplicated."
|
|
3
|
+
contour,2026-05-29,2704;2710,HIGH,2;5,"Pass 1 (2026-05-29): added TestInfHandling, TestCRSPropagation, TestNonDefaultDims to test_contour.py (5 passed + 2 strict-xfail on a CUDA host; full file 29 passed, 2 xfailed). All four backends (numpy / cupy / dask+numpy / dask+cupy) were already exercised with cross-backend segment-equality assertions (TestBackendEquivalence), and ran green locally on the CUDA host -- Cat 1 well covered, no new backend tests needed. Cat 2 HIGH (Inf): the marching-squares NaN-skip guard at contour.py:67 uses x!=x which does not catch infinity, so a finite level near a +/-inf corner leaks NaN coordinates into the output. Filed source bug #2704 and added two xfail(strict=True) tests pinning it (+inf and -inf) plus test_inf_far_level_no_crossing covering the safe path where the inf quad classifies as all-above (idx 15) and is skipped before any interpolation. Cat 5 MEDIUM: no test asserted gdf.crs propagation from agg.attrs['crs'] (contour.py:660) -- added test_geopandas_crs_from_attrs (to_epsg()==5070) + test_geopandas_no_crs_attr. Cat 5 MEDIUM: the index-to-coordinate transform (contour.py:644-654) reads agg.dims[0]/[1] coords but no test used non-y/x dims -- added test_lat_lon_dims_coordinate_transform + test_lat_lon_matches_yx_equivalent. PR #2710 (test-only, source untouched). LOW (documented, not fixed): non-square cellsize (cellsize_x != cellsize_y) never exercised -- all tests use res (0.5,0.5); levels=None early-return on all-NaN/all-equal works (probed) but only the explicit-levels all-NaN path is asserted. Cat 3 1x1/Nx1/1xN are rejected by the >=2x2 validation guard and that rejection is already tested (test_too_small, test_minimum_raster)."
|
|
4
|
+
geotiff,2026-05-29,,MEDIUM,1,"Pass 19 (2026-05-29): added TestBboxBackendParity to read/test_bbox_2555.py closing a Cat 1 MEDIUM cross-backend gap on the open_geotiff(bbox=) feature (#2556). bbox= resolves to a pixel window at the dispatcher (__init__.py _bbox_to_window, ~L799) then forwards uniformly into every backend; the eager-numpy path had full validation+overview coverage but bbox= was never exercised with gpu=True, chunks=, or gpu=True+chunks=. window= itself is tested on GPU (parity/test_finalization.py) and dask (parity/test_pixel_equality.py) but the composed bbox->window->backend path was untested, so a regression dropping the resolved window before GPU/dask dispatch would ship undetected. 3 new tests, all RUN (not skipped) and passing on a CUDA host: gpu / dask+numpy / dask+gpu each assert pixel+shape parity vs the eager bbox read. Mutation check confirmed teeth: a dask read with the window dropped returns full 100x100 vs the expected 60x60, failing the shape assert. New features since last pass already covered at merge: #2603 projected+base-geographic CRS (unit/test_geotags.py), #2598 .xrs.open_geotiff accessor (integration/test_dask_pipeline.py). | Pass 18 (2026-05-18): added test_parallel_strip_decode_sparse_2100.py closing Cat 3 HIGH geometric-edge / Cat 4 HIGH parameter-coverage gap on the parallel-decode strip paths (#2100/#2104). The strip-decode parallelisation in _read_strips (lines 1942-2014) and _fetch_decode_cog_http_strips (lines 2685-2740) added a collect-decode-place pipeline whose job-collection loop filters sparse strips (byte_counts[idx] == 0) before they reach the ThreadPoolExecutor. The existing test_parallel_strip_decode_2100.py covers parallel/serial parity, the pool-engaged branch, single-strip serial short-circuit, windowed strip reads, and planar=2 multi-band, but every fixture is fully populated. The 128x128 sparse fixture in test_sparse_cog.py is below the 64K-pixel parallel gate, so the sparse-strip filter inside the parallel branch is wholly untested. A regression that lost the byte_counts==0 guard would silently ship: the decoder would receive an empty data[offsets[idx]:offsets[idx]+0] slice and either raise 'Decompressed tile/strip size mismatch' or return corrupt pixels. 7 new tests, all passing: local-strip full-image parallel/serial parity with sparse strips, parallel-pool-engaged on multi-strip sparse images, windowed reads across the sparse boundary, all-sparse degenerate (zero filled rows -> empty job list -> short-circuit gate), planar=2 sparse parity (dedicated 'planar == 2 and samples > 1' branch with its own byte_counts==0 guard at lines 1949-1962), HTTP windowed read on a non-sparse strict subset (parallel decode of fetched strips), and HTTP windowed read across the sparse boundary (parallel decode of the fetched strips with placement matching the local read). Mutation against the strip-job collection sparse guard (delete the byte_counts == 0 continue) flips 5 of 5 local tests red with 'Decompressed tile/strip size mismatch: expected ... got 0'; mutation against the HTTP path sparse guard at line 2646 flips the boundary HTTP test red. Confirmed clean restore via md5sum. Source untouched. Cat 3 HIGH + Cat 4 HIGH (geometric edge case + parameter coverage on the sparse-strip code path under parallel decode). Pass 17 (2026-05-18): added test_mask_nodata_gpu_vrt_2052.py closing Cat 1 HIGH backend-coverage gap on the mask_nodata= opt-out kwarg (#2052). The kwarg was added in #2052 and wired through the four public readers (open_geotiff, read_geotiff_gpu, read_geotiff_dask, read_vrt), but test_mask_nodata_kwarg_2052.py only exercised the eager-numpy and dask+numpy branches. The pure-GPU mask gating at _backends/gpu.py:709, the dask+GPU dispatcher forwarding at _backends/gpu.py:991, the eager VRT mask gating at _backends/vrt.py:320, and the chunked VRT graph builder at _backends/vrt.py:408/588 had zero direct coverage. 19 new tests, all passing on GPU host: GPU eager + dask+GPU mask_nodata=False preserves uint16, GPU defaults still promote to float64, dispatcher thread-through for open_geotiff(gpu=True, mask_nodata=False) and open_geotiff(gpu=True, chunks=N, mask_nodata=False), VRT eager and chunked branches mirror, cross-backend parity (eager vs dask, eager vs GPU, eager vs dask+GPU, eager vs VRT) bit-exact under mask_nodata=False, direct read_geotiff_dask entry-point coverage. Fixture uses tiled+deflate compression so the pure nvCOMP decode path is exercised, not the CPU-fallback piggyback path. Mutation against gpu.py:709 (force mask_nodata=True) flipped 4 GPU tests red; mutation against vrt.py eager mask gate flipped 4 VRT tests red. Cat 1 HIGH (backend coverage on mask_nodata=False for GPU, dask+GPU, VRT eager, VRT chunked). Pass 16 (2026-05-15): added test_max_cloud_bytes_dispatcher_silent_drop_2026_05_15.py closing Cat 4 HIGH parameter-coverage gap on the open_geotiff dispatcher's max_cloud_bytes kwarg. The kwarg was added in #1928 (eager fsspec budget) and re-ordered into the canonical reader signature by #1957, but open_geotiff only forwards it to _read_to_array on the eager non-VRT branch (__init__.py:431). The GPU branch at line 410, the dask branch at line 422, and the VRT branch at line 362 never reference the kwarg, so open_geotiff(p, max_cloud_bytes=8, gpu=True) / open_geotiff(p, max_cloud_bytes=8, chunks=N) / open_geotiff(vrt, max_cloud_bytes=8) all silently drop the budget. Same class of dispatcher-silently-drops-backend-kwarg bug fixed by #1561 / #1605 / #1685 / #1810 for other kwargs; the two sibling kwargs on_gpu_failure (line 339) and missing_sources (line 355) already raise ValueError when used on a path where they do not apply. 11 tests: 4 xfail(strict=True) pinning the fix surface (gpu, dask, vrt, dask+gpu), 3 passing pins on the current silent-drop behaviour so the fix is visible as a diff, 4 positive pins that the eager local + file-like paths accept the kwarg (docstring no-op contract). Filed issue #1974 for the dispatcher fix (sweep is test-only). Cat 4 HIGH (silent backend-kwarg drop). Pass 15 (2026-05-15): added test_write_vrt_bool_nodata_1921.py closing Cat 1 HIGH backend-parity gap on bool nodata rejection. Issue #1911 added the isinstance(nodata, (bool, np.bool_)) -> TypeError guard at to_geotiff and build_geo_tags, but the sibling writers were left unchecked: write_vrt(nodata=True) silently emits <NoDataValue>True</NoDataValue> into the VRT XML (str(True) drops the sentinel because no reader parses 'True' as numeric); write_geotiff_gpu direct call relies on the build_geo_tags defense-in-depth rather than an entry-point check, so a future refactor moving that guard would regress the GPU writer with no test coverage. 17 new tests: 4 xfail (strict=True) pinning the write_vrt fix surface (issue #1921), 1 passing pin on the current buggy str(True) emission so the fix is visible as a diff, 6 numeric/None happy-path tests on write_vrt, 4 GPU writer direct-call bool-reject tests (4 dtypes x 1 call), 1 to_geotiff(gpu=True) dispatcher thread-through. Filed issue #1921 for the write_vrt fix (sweep is test-only). Cat 1 HIGH (write_vrt backend parity bug) + Cat 1 MEDIUM (write_geotiff_gpu defense-in-depth pin). Pass 14 (2026-05-15): added test_dask_streaming_write_degenerate_2026_05_15.py closing Cat 3 HIGH and Cat 2 HIGH/MEDIUM gaps on the dask streaming write path (to_geotiff with dask-backed DataArray, #1084). test_streaming_write.py covered 100x100 with a NaN block plus a 2x2 small raster but had nothing 1-pixel-row, 1-pixel-column, all-NaN, all-Inf, or +/-Inf-mixed. The streaming tile-row segmenter (#1485) on a 1-pixel-tall raster and the streaming nodata-mask coercion on an all-NaN chunk were reachable only with a dask input and had no direct coverage; a regression on either would not surface from the eager numpy path or the write_geotiff_gpu path (pass 5 covered the GPU writer's degenerate shapes). 16 new tests, all passing: 1x1 chunk-matches-shape + nodata-attr round-trip + uint16, 1xN single chunk + chunks-split-columns + wide-segmented-by-buffer (#1485 streaming_buffer_bytes=1 forces the segmenter), Nx1 single chunk + chunks-split-rows, all-NaN with finite sentinel + all-NaN without sentinel, mixed NaN/+Inf/-Inf preserving Inf bit-exact + sentinel masking NaN only, all-+Inf and all--Inf, predictor=3 (float predictor) round-trip on float32 + float64 plus int-dtype ValueError. predictor=3 streaming coverage extends the small-chunk and int-rejection geometry around test_predictor_fp_write_1313.test_predictor3_streaming_dask (which already covers a 128x192 predictor=3 dask streaming write with a Predictor-tag assertion). Cat 3 HIGH (1x1/1xN/Nx1) + Cat 2 HIGH (all-NaN with sentinel) + Cat 2 MEDIUM (mixed-Inf, all-Inf) + Cat 4 MEDIUM (predictor=3 streaming). Pass 13 (2026-05-13): added test_size_param_validation_gpu_vrt_1776.py closing Cat 4 HIGH parameter-coverage gap on size-arg validation. Issue #1752 added tile_size validation to to_geotiff and chunks validation to read_geotiff_dask, but the matching kwargs on three sibling entry points were left unchecked: write_geotiff_gpu(tile_size=) raised ZeroDivisionError for 0, struct.error for -1, TypeError for 256.0; read_geotiff_gpu(chunks=) and read_vrt(chunks=) raised ZeroDivisionError for 0 and silently accepted negative values. Factored two shared validators (_validate_tile_size_arg, _validate_chunks_arg) and called them up front from each entry point. 34 new tests, all passing on GPU host: tile_size matrix on write_geotiff_gpu (0/-1/256.0/True/False/positive/np.int64), chunks matrix on read_geotiff_gpu and read_vrt (0/-1/(0,N)/(N,-1)/wrong-length/bool/non-int/(N,float)/positive/np.int64), dispatcher thread-through tests (open_geotiff(gpu=True, chunks=0), to_geotiff(gpu=True, tile_size=0)). Pre-existing 13 #1752 tests still pass after refactor. Filed issue #1776. Pass 12 (2026-05-12): added test_gpu_writer_overview_mode_and_compression_level_1740.py closing Cat 4 HIGH and Cat 4 MEDIUM parameter-coverage gaps. (1) write_geotiff_gpu(overview_resampling='mode') and the dedicated _block_reduce_2d_gpu mode-fallback branch (_gpu_decode.py:3051-3056) had zero direct tests; six of the seven overview_resampling modes were covered (mean/nearest by test_features, min/max/median by pass 6, cubic by test_signature_parity_1631) but mode was the odd one out -- a regression dropping the mode dispatch from _block_reduce_2d_gpu would fall through to the mean reshape branch and emit wrong overview pixels for integer rasters. (2) write_geotiff_gpu(compression_level=) documented as accepted-but-ignored had no test; the CPU writer rejects out-of-range levels with ValueError, the GPU writer is documented not to -- a regression wiring the GPU writer up to the CPU range validator would silently break every to_geotiff(gpu=True, compression_level=X) caller for in-range levels and noisily for out-of-range. 19 tests, all passing on GPU host: _block_reduce_2d_gpu(method='mode') CPU-parity on 4x4 deterministic + random 8x8 + dtype-preserved across u8/u16/i16/i32, write_geotiff_gpu(cog=True, overview_resampling='mode') end-to-end round trip, to_geotiff(gpu=True, ..., overview_resampling='mode') dispatcher thread-through, GPU-vs-CPU pixel parity on 8x8 input, write_geotiff_gpu(compression_level=) in-range matrix on zstd/deflate, out-of-range matrix (zstd=999/-5, deflate=50/0) accepted without raising + round-trip preserved, to_geotiff(gpu=True, compression_level=999) dispatcher thread-through, companion CPU rejects-OOR pin to lock the asymmetry. Mutation against the mode branch (drop the 'if method == mode' block in _block_reduce_2d_gpu) flipped 9 mode tests red. Filed issue #1740. Pass 11 (2026-05-12): added test_gpu_writer_cpu_fallback_codecs_2026_05_12.py closing a Cat 4 HIGH parameter-coverage gap on write_geotiff_gpu compression= modes for the CPU-fallback codecs (lzw, packbits, lz4, lerc, jpeg2000/j2k). Pass 7 (test_gpu_writer_compression_modes_2026_05_11) covered only none/deflate/zstd/jpeg; the remaining five codecs route through dedicated branches in gpu_compress_tiles (_gpu_decode.py:2974-3019) with CPU fallbacks (lerc_compress, jpeg2000_compress, cpu_compress) that had zero direct tests via write_geotiff_gpu. A regression in routing/tag-wiring/fallback dispatch would ship silently because the internal reader uses the same compression-tag table. 17 tests, all passing on GPU host: lzw/packbits/lz4 round-trip + compression-tag pin on uint16, lerc lossless float32 + uint16 round-trip + tag pin, jpeg2000 uint8 single-band + RGB multi-band lossless round-trip + j2k-alias parity + tag pin, GPU-vs-CPU writer pixel parity for lzw/packbits, to_geotiff(gpu=True, compression=lzw/packbits) dispatcher thread-through. Mutation against compression dispatch (swap lzw bytes to zstd; swap lerc bytes to deflate) flipped round-trip tests red. Filed issue #1706. Pass 10 (2026-05-12): added test_kwarg_behaviour_2026_05_12_v2.py closing two Cat 4 HIGH parameter-coverage gaps. (1) write_geotiff_gpu(predictor=True/2/3) had zero direct tests; the GPU writer threads predictor= through normalize_predictor and gpu_compress_tiles into five CUDA encode kernels (_predictor_encode_kernel_u8/u16/u32/u64 for predictor=2, _fp_predictor_encode_kernel for predictor=3) and a regression dropping the encode-kernel calls would ship corrupt files. (2) read_vrt(window=) had no behaviour tests (only a signature pin in test_signature_annotations_1654); the kwarg is documented and _vrt.read_vrt implements full windowed-read semantics (clip, multi-source overlap, src/dst scaling, GeoTransform origin shift on coords + attrs['transform']). 23 tests, all passing on GPU host: predictor=True/2 round-trips on u8/u16/i32 + 3-band RGB samples_per_pixel stride; predictor=3 lossless round-trip on f32 and f64; predictor=3 int-dtype ValueError (CPU/GPU parity); CPU/GPU pixel-exact parity for pred=2 u16 and pred=3 f32; read_vrt(window=) subregion + full + clamp-overflow + clamp-negative + 2x1 mosaic seam straddle + offset past seam + transform-attr origin shift + y/x coords half-pixel shift + window+band + window+chunks (dask) + window+gpu (cupy) + window+gpu+chunks (dask+cupy). Mutation against the encode dispatch flipped 7 predictor tests red. Filed issue #1690. Pass 9 (2026-05-12): added test_kwarg_behaviour_2026_05_12.py closing three Cat 4 MEDIUM parameter-coverage gaps plus one Cat 4 LOW error path. write_vrt documented kwargs (relative/crs_wkt/nodata) had a smoke-test pinning that the kwargs are accepted but no test verified the override *effect* -- a regression dropping the override branch and silently using the default-from-first-source would ship undetected. read_geotiff_gpu(dtype=) cast had zero direct tests; the eager path has TestDtypeEager and dask has TestDtypeDask but the GPU branch had no equivalent. write_geotiff_gpu(bigtiff=) threads through to _assemble_tiff(force_bigtiff=) but no test asserted the on-disk header byte switches; the CPU writer had it via test_features::test_force_bigtiff_via_public_api. write_vrt(source_files=[]) ValueError was uncovered. 26 tests, all passing on GPU host: write_vrt relative=True/False XML attribute + path inspection + parse-back round-trip, write_vrt crs_wkt= override distinct-from-default XML check, write_vrt nodata= override + default-from-source coverage, write_vrt([]) ValueError + no-file side effect, read_geotiff_gpu dtype= matrix (float64->float32, float64->float16, uint16->int32, uint16->uint8, float-to-int raise, dtype=None preserves native), open_geotiff(gpu=True, dtype=) dispatcher, read_geotiff_gpu(chunks=, dtype=) dask+GPU branch, write_geotiff_gpu bigtiff=True/False/None header verification, to_geotiff(gpu=True, bigtiff=True) dispatcher thread-through. Pass 8 (2026-05-11): added test_lz4_compression_level_2026_05_11.py closing Cat 4 MEDIUM parameter-coverage gap on compression='lz4' + compression_level=. _LEVEL_RANGES advertises lz4: (0, 16) but only deflate (1, 9) and zstd (1, 22) had direct level boundary + round-trip + reject tests. The range check is the gatekeeper -- lz4_compress silently accepts any int level -- so a regression dropping 'lz4' from _LEVEL_RANGES would ship undetected. 18 tests, all passing: round-trip at levels 0/1/9/16 (lossless), default-level no-arg path, higher-level-not-larger smoke check on compressible input, out-of-range reject at -1/-10/17/100 on eager path, valid-range message format pin (lz4 valid: 0-16), dask streaming round-trip at 0/1/8/16, dask streaming out-of-range reject at -1/17/50 (separate _LEVEL_RANGES call site). Pass 7 (2026-05-11): added test_gpu_writer_compression_modes_2026_05_11.py closing Cat 4 HIGH gap on write_geotiff_gpu compression= modes. The writer documents zstd (default, fastest GPU), deflate, jpeg, and none, but only deflate + none had round-trip tests; the default zstd and the jpeg (nvJPEG/Pillow) paths shipped without targeted coverage. 11 new tests, all passing on GPU host: zstd round-trip + default-codec pinning, jpeg round-trip on 3-band RGB uint8 + 1-band greyscale, TIFF compression-tag header check across none/deflate/zstd/jpeg, plain deflate + none round-trips outside the COG/sentinel paths, and a cross-codec lossless parity check (zstd/deflate/none agree pixel-exact). nvJPEG path was exercised live, not just the Pillow fallback. Pass 6 (2026-05-11): added test_overview_resampling_min_max_median_2026_05_11.py covering Cat 4 HIGH parameter-coverage gap on overview_resampling=min/max/median. CPU end-to-end paths were already covered by test_cog_overview_nodata_1613::test_cpu_cog_overview_aggregations_ignore_sentinel; the GPU end-to-end paths and the direct CPU+GPU block-reducer branches had no targeted tests, so a regression on those code paths would ship undetected. 26 tests, all passing on GPU host: block-reducer unit tests (finite + partial-NaN), end-to-end COG writes for both to_geotiff and write_geotiff_gpu, CPU/GPU parity for to_geotiff(gpu=True), CPU nodata-sentinel regression check, and ValueError error-path tests for unknown method names on both backends. Pass 5 (2026-05-11): added test_degenerate_shapes_backends_2026_05_11.py covering Cat 3 HIGH geometric gaps (1x1 / 1xN / Nx1 reads on dask+numpy, GPU, dask+cupy backends; 1x1 / 1xN / Nx1 writes through write_geotiff_gpu) and Cat 2 MEDIUM NaN/Inf gaps (all-NaN read on GPU + dask+cupy, Inf / -Inf reads on all non-eager backends, NaN sentinel mask on dask read path including sentinel block split across chunk boundary). 23 tests, all passing on GPU host. Prior passes still hold: pass 4 (r4) closed read_geotiff_gpu/dask name= + max_pixels= kwargs (Cat 4), pass 3 (r3) closed read_vrt GPU/dask+GPU backend dispatch (Cat 1) and dtype/name kwargs (Cat 4)."
|
|
5
|
+
polygonize,2026-05-27,2537,MEDIUM,4,"Pass 2 (2026-05-27): added test_polygonize_atol_rtol_backend_coverage_2026_05_27.py with 15 tests, all passing on a CUDA host. Closes Cat 4 MEDIUM parameter-coverage gap on atol/rtol forwarding through the cupy and dask+cupy backends. atol/rtol were exposed by #2173 / #2194 and thread through _polygonize_cupy (polygonize.py:808) and _polygonize_dask (polygonize.py:1719); the dask path further plumbs them into dask.delayed(_polygonize_chunk)(...) at lines 1748-1754 and into _bucket_key_for_value for cross-chunk merge bucketing at lines 1757-1758. Pre-existing tests covered non-default atol/rtol only on numpy and dask+numpy. The cupy and dask+cupy dispatchers were untested -- a regression dropping the kwargs there would silently change the float polygon count and would not be caught. Same dispatcher-silently-drops-kwarg pattern fixed by #1561 / #1605 / #1685 / #1810 / #1974 on adjacent GeoTIFF surfaces. 15 tests: cupy strict-equality + default-tolerance pin on _REPRO_2173, dask+cupy strict-equality single-chunk + multi-chunk (engages cross-chunk merge bucket) + default-tolerance multi-chunk pin, cupy intermediate-atol small/large pair, dask+cupy intermediate-atol single/multi-chunk small + single-chunk large, cupy integer atol-ignored matrix, dask+cupy integer atol-ignored single-chunk + multi-chunk, cupy rtol-only large/small matrix. Mutation against _polygonize_cupy float branch (drop atol/rtol kwargs in the _polygonize_numpy forward call at polygonize.py:823-825) flips 3 of 5 cupy tests red; mutation against dask.delayed(_polygonize_chunk)(...) at polygonize.py:1748-1754 (drop atol, rtol args) flips 2 of 6 dask+cupy tests red. Confirmed clean restore via md5sum. Source untouched. Filed issue #2537 (test-only). Cat 4 MEDIUM (parameter coverage on cupy + dask+cupy atol/rtol forwarding). Pass 1 (2026-05-19): added test_polygonize_coverage_2026_05_19.py with 58 tests, all passing on a CUDA host. Closes Cat 3 HIGH 1x1 / Nx1 single-column geometric gaps (Nx1 exercises the nx==1 padding path at polygonize.py:565 and the cupy nx==1 numpy-fallback at polygonize.py:671), Cat 3 MEDIUM 1xN single-row and all-equal-value rasters on all four backends. Closes Cat 2 HIGH NaN parity for cupy + dask+cupy (numpy/dask were already covered by test_polygonize_nan_pixels_excluded*), Cat 2 MEDIUM all-NaN raster on all four backends, Cat 2 HIGH +/-Inf pins on all four backends. Filed source-bug issue #2155: numpy/dask/dask+cupy backends silently absorb Inf cells into adjacent finite polygons because _is_close reduces abs(inf-inf) to nan; cupy backend handles Inf correctly. Pins lock the asymmetric behaviour so the fix is visible. Closes Cat 1 MEDIUM simplify_tolerance + mask= parity gaps on dask+cupy backend (numpy/cupy/dask were already covered). Closes Cat 4 MEDIUM column_name non-default value across geopandas/spatialpandas/geojson return types and Cat 4 MEDIUM validation error paths (bad connectivity, bad transform length, mask shape mismatch, mask underlying-type mismatch). Cat 5 N/A: polygonize returns lists/dataframes, not a DataArray with attrs to propagate."
|
|
6
|
+
rasterize,2026-05-27,,HIGH,1;2;4,"Pass 3 (2026-05-27): added test_rasterize_coverage_2026_05_27.py with 23 tests, all passing on a CUDA host. Closes Cat 1 HIGH eager-cupy merge-mode parity gap: pass-1 only pinned merge='last' on a single non-overlapping polygon via TestCuPy.test_cupy_matches_numpy, and the Inf-burn tests in pass-2 only partially exercised sum/min/max on eager cupy; the parametrised six-mode parity test (last/first/max/min/sum/count) that TestDaskNumpy and TestDaskCupy carry had no eager-cupy twin, so a routing regression that swapped any of the six GPU atomic kernels in _ensure_gpu_kernels (rasterize.py:1308-1556) would slip past the dask+cupy tiled-finalize tests. Pin a three-way overlapping polygon scene plus a three-way overlapping point scene across all six modes on the eager cupy backend, with sanity checks (first!=last, min<max) so the fixture is non-degenerate. Closes Cat 1 MEDIUM eager-cupy empty-geometry-list gap: numpy + dask+numpy + dask+cupy had empty-list coverage but the eager _run_cupy zero-geometry path (zero-sized cupy bbox/edge/segment buffers) did not -- pin that use_cuda=True with [] still returns a cupy.ndarray (not a numpy short-circuit) under both an explicit fill and the default NaN fill. Closes Cat 2 MEDIUM all-equal-value count gap: four overlapping rectangles all burning 1.0 under merge='count' must still count overlaps as >1; a future GPU atomic optimisation that deduped identical-value writes would silently break density rasters. Closes Cat 4 MEDIUM name= kwarg thread-through on dask+numpy / eager cupy / dask+cupy (the eager numpy path was the only one with name= coverage at TestBasic.test_output_name). Source untouched. Pass 2 (2026-05-21): added test_rasterize_coverage_2026_05_21.py with 58 tests, all passing on a CUDA host. Closes Cat 2 HIGH +/-Inf and NaN burn-value gaps that pass-1 left untouched: pin +Inf / -Inf / Inf+(-Inf)/NaN polygon, point, and line burn behaviour across numpy / cupy / dask+numpy / dask+cupy, plus Inf+finite under sum stays Inf, Inf+(-Inf) under sum collapses to NaN, min(Inf, 1.0) and max(-Inf, 1.0) pick the finite value, and Inf-as-bound is rejected with the same ValueError as NaN-as-bound (pass-1 only tested the NaN-bound rejection). Closes Cat 1 MEDIUM nested GeometryCollection on all four backends: a GC inside a GC has no direct test today even though rasterize.py:1995 documents recursive unpacking, and the deeply-nested-3-levels eager test pins the recursion depth limit isn't 1 or 2. Closes Cat 1 MEDIUM columns= (multi-column) parity on cupy and dask+cupy (TestMultiColumn covered numpy/dask+numpy only); pin three columns of props on GPU so the (N, P) loop survives the kernel boundary. Closes Cat 3 LOW rectangular-pixel parity with resolution=(rx, ry) across backends. Filed source-bug issue #2255: GPU max/min merge silently suppresses NaN burn values -- CPU returns NaN (1.0 > NaN is False, keeps NaN); GPU returns 1.0 because the kernel inits the output buffer to -inf for max (or +inf for min) and atomicMax/Min is NaN-suppressing under IEEE device semantics. Pinned both the CPU NaN-propagating behaviour and the GPU NaN-suppressing behaviour as paired tests (test_nan_burn_overlaps_max_cpu_propagates vs test_nan_burn_overlaps_max_gpu_suppresses_nan, plus test_nan_burn_single_geom_max_gpu_returns_neg_inf for the single-write-on-GPU-returns-buffer-init case) so the divergence is visible in CI until the GPU kernels are aligned. Source untouched. Pass 1 (2026-05-17): added test_rasterize_coverage_2026_05_17.py with 34 tests, all passing on a CUDA host. Closes four documented public-API gaps left after the pass-0 audit. (1) Cat 3 HIGH 1x1 single-pixel raster -- test_rasterize.py covers 1xN strips and Nx1 strips but never width=1 AND height=1, so the polygon scanline / line Bresenham / point burn kernels all ship without the single-cell degenerate case; the new TestSinglePixelRaster class pins polygon/point/line on eager numpy plus polygon parity across cupy / dask+numpy / dask+cupy. (2) Cat 4 HIGH like= template-raster parameter is documented at rasterize.py:2038 and implemented by _extract_grid_from_like (line 1930) but no test exercises it; TestLikeParameter pins dtype/bounds/coords inheritance, the three override branches (dtype, bounds, width/height), the three validation branches (not-DataArray, 3D, wrong dim names) and like= on all four backends. Mutation against the like-dtype branch (rasterize.py:2183-2184) flipped the inheritance test red. (3) Cat 4 HIGH resolution= happy path -- only the oversize-rejection error path was tested (line 304); TestResolutionParameter pins the scalar branch, the tuple branch, the ceil-and-clamp-to-1 semantics, and resolution= on all four backends. (4) Cat 4 HIGH non-empty GeometryCollection unpacking is documented at rasterize.py:1995 and implemented by _classify_geometries_loop (line 228) but only the empty-GC case was tested (line 269); TestGeometryCollection pins polygon+point and polygon+line+point collections on eager numpy plus parity across cupy / dask+numpy / dask+cupy so the loop classifier's polygon/line/point sub-bucketing has direct coverage. Cat 1 MEDIUM gap closed: eager cupy all_touched=True parity vs eager numpy (TestEagerCupyAllTouched) -- the existing test only covered dask+cupy all_touched, leaving the direct GPU all_touched kernel untested. Cat 2 MEDIUM gap closed: int32 dtype with default NaN fill silently casts to the int32-min sentinel (TestIntegerDtypeNanFill) -- pin the cast so any future ValueError-raises switch is visible as a code-review diff. Pre-existing 143 passing + 2 skipped tests in test_rasterize.py untouched."
|
|
7
|
+
reproject,2026-05-27,,MEDIUM,1,"Pass 2 (2026-05-27): added test_reproject_coverage_2026_05_27.py with 10 tests, all passing on a CUDA host. Closes Cat 1 MEDIUM backend-coverage gaps left after pass 1: (a) bounds_policy=#2187 had numpy + dask+numpy coverage but no cupy / dask+cupy tests -- a regression dropping the kwarg from the GPU dispatchers would ship undetected; TestBoundsPolicyCupy and TestBoundsPolicyDaskCupy pin raw/clamp/bogus on both GPU backends and assert clamp-grid parity with numpy. (b) test_reproject_handles_inf_input only covered eager numpy; the dask, cupy, and dask+cupy chunk workers each ship their own bilinear/cubic resampler so a regression raising on +/-Inf in any one backend would not surface from the existing test. Four new tests close the matrix (dask+numpy, cupy, dask+cupy with scattered +/-Inf cells; cupy with all-Inf raster checking no spurious finite cells appear). Note carried forward from pass 1: _merge_arrays_cupy is imported but unused -- no cupy merge dispatch in merge(); feature gap not test gap. Added 39 tests: LiteCRS direct coverage, itrf_transform behaviour/roundtrip/array, itrf_frames, geoid_height numerical correctness + raster happy-path, vertical helpers (ellipsoidal<->orthometric/depth), reproject() lat/lon and latitude/longitude dim propagation. Note: _merge_arrays_cupy is imported but unused (no cupy merge dispatch in merge()); flagged as feature gap not test gap."
|
|
8
|
+
resample,2026-05-27,2547,HIGH,2;3;5,"Pass 1 (2026-05-27): added test_resample_coverage_2026_05_27.py with 70 tests (68 passing, 2 skipped). Closes Cat 3 HIGH Nx1 single-column gap across numpy/cupy/dask+numpy/dask+cupy x 8 methods (nearest/bilinear/cubic/average/min/max/median/mode) plus Nx1 upsample-nearest parity and Nx1 cross-backend aggregate parity. Closes Cat 2 MEDIUM NaN-parity gap on cupy and dask+cupy (existing TestCuPyParity/TestDaskCuPyParity used random data without NaN; the weight-mask gate and spline-prepad had no GPU NaN coverage). Closes Cat 3 MEDIUM all-equal-value raster across 8 methods (downsample) and 3 interp methods (upsample) plus a constant-with-NaN aggregate variant. Closes Cat 5 MEDIUM non-default dim-name propagation: lat/lon, latitude/longitude, and (channel, lat, lon) 3D round-trip without being renamed to y/x; per-dim attrs (units) preserved. Closes Cat 3 MEDIUM empty-raster behaviour pin: 0-row and 0-col rasters raise (currently IndexError) -- contract covered. Filed source-bug issue #2547: cubic on dask backends fails for Nx1 / arrays smaller than depth=16; the 2 skipped tests in this file gate on that fix landing. Source untouched."
|
|
9
|
+
zonal,2026-05-27,,HIGH,1;3;4;5,"Pass 1 (2026-05-27): added test_zonal_backend_coverage_2026_05_27.py with 32 tests, all passing on a CUDA host. Closes Cat 1 HIGH backend-coverage gaps: crosstab cupy + dask+cupy (_crosstab_cupy / _crosstab_dask_cupy were dispatched but never invoked by tests), regions cupy + dask+cupy (_regions_cupy via cupyx.scipy.ndimage + _regions_dask_cupy), trim dask+numpy + cupy + dask+cupy (_trim_bounds_dask isnan path and cupy data.get() path), crop dask+numpy + cupy + dask+cupy (_crop_bounds_dask + cupy data.get() path), apply 3D cupy + dask+cupy (per-layer kernel launch over the third axis in _apply_cupy and _apply_dask_cupy). Existing test_zonal.py covered only numpy + dask+numpy for crosstab/regions/trim/crop and 2D-only for cupy apply. Closes Cat 3 MEDIUM 1x1 / 1xN / Nx1 strip edge cases for trim, crop, and regions. Closes Cat 4 LOW pins: regions(neighborhood=6) ValueError, suggest_zonal_canvas(crs='Geographic') aspect-ratio pin and invalid-crs KeyError, crosstab cupy zone_ids/cat_ids filter, crosstab cupy agg='percentage'. Closes Cat 5 MEDIUM: regions coords/attrs propagation across numpy + dask+numpy, trim/crop name='trim'/'crop' default + attrs preservation. Also pins the documented numpy-vs-dask trim asymmetry on NaN sentinel (numpy _trim does equality which never matches NaN; dask _trim_bounds_dask has dedicated isnan branch). Mutation against the cupy.asnumpy() conversion in _crosstab_cupy flipped test_crosstab_cupy_matches_numpy red. Source untouched."
|
|
10
|
+
module,last_inspected,issue,severity_max,categories_found,notes
|
|
11
|
+
geotiff,2026-05-18,,HIGH,3;4,"Pass 18 (2026-05-18): added test_parallel_strip_decode_sparse_2100.py closing Cat 3 HIGH geometric-edge / Cat 4 HIGH parameter-coverage gap on the parallel-decode strip paths (#2100/#2104). The strip-decode parallelisation in _read_strips (lines 1942-2014) and _fetch_decode_cog_http_strips (lines 2685-2740) added a collect-decode-place pipeline whose job-collection loop filters sparse strips (byte_counts[idx] == 0) before they reach the ThreadPoolExecutor. The existing test_parallel_strip_decode_2100.py covers parallel/serial parity, the pool-engaged branch, single-strip serial short-circuit, windowed strip reads, and planar=2 multi-band, but every fixture is fully populated. The 128x128 sparse fixture in test_sparse_cog.py is below the 64K-pixel parallel gate, so the sparse-strip filter inside the parallel branch is wholly untested. A regression that lost the byte_counts==0 guard would silently ship: the decoder would receive an empty data[offsets[idx]:offsets[idx]+0] slice and either raise 'Decompressed tile/strip size mismatch' or return corrupt pixels. 7 new tests, all passing: local-strip full-image parallel/serial parity with sparse strips, parallel-pool-engaged on multi-strip sparse images, windowed reads across the sparse boundary, all-sparse degenerate (zero filled rows -> empty job list -> short-circuit gate), planar=2 sparse parity (dedicated 'planar == 2 and samples > 1' branch with its own byte_counts==0 guard at lines 1949-1962), HTTP windowed read on a non-sparse strict subset (parallel decode of fetched strips), and HTTP windowed read across the sparse boundary (parallel decode of the fetched strips with placement matching the local read). Mutation against the strip-job collection sparse guard (delete the byte_counts == 0 continue) flips 5 of 5 local tests red with 'Decompressed tile/strip size mismatch: expected ... got 0'; mutation against the HTTP path sparse guard at line 2646 flips the boundary HTTP test red. Confirmed clean restore via md5sum. Source untouched. Cat 3 HIGH + Cat 4 HIGH (geometric edge case + parameter coverage on the sparse-strip code path under parallel decode).
|
|
12
|
+
|
|
13
|
+
Pass 17 (2026-05-18): added test_mask_nodata_gpu_vrt_2052.py closing Cat 1 HIGH backend-coverage gap on the mask_nodata= opt-out kwarg (#2052). The kwarg was added in #2052 and wired through the four public readers (open_geotiff, read_geotiff_gpu, read_geotiff_dask, read_vrt), but test_mask_nodata_kwarg_2052.py only exercised the eager-numpy and dask+numpy branches. The pure-GPU mask gating at _backends/gpu.py:709, the dask+GPU dispatcher forwarding at _backends/gpu.py:991, the eager VRT mask gating at _backends/vrt.py:320, and the chunked VRT graph builder at _backends/vrt.py:408/588 had zero direct coverage. 19 new tests, all passing on GPU host: GPU eager + dask+GPU mask_nodata=False preserves uint16, GPU defaults still promote to float64, dispatcher thread-through for open_geotiff(gpu=True, mask_nodata=False) and open_geotiff(gpu=True, chunks=N, mask_nodata=False), VRT eager and chunked branches mirror, cross-backend parity (eager vs dask, eager vs GPU, eager vs dask+GPU, eager vs VRT) bit-exact under mask_nodata=False, direct read_geotiff_dask entry-point coverage. Fixture uses tiled+deflate compression so the pure nvCOMP decode path is exercised, not the CPU-fallback piggyback path. Mutation against gpu.py:709 (force mask_nodata=True) flipped 4 GPU tests red; mutation against vrt.py eager mask gate flipped 4 VRT tests red. Cat 1 HIGH (backend coverage on mask_nodata=False for GPU, dask+GPU, VRT eager, VRT chunked). Pass 16 (2026-05-15): added test_max_cloud_bytes_dispatcher_silent_drop_2026_05_15.py closing Cat 4 HIGH parameter-coverage gap on the open_geotiff dispatcher's max_cloud_bytes kwarg. The kwarg was added in #1928 (eager fsspec budget) and re-ordered into the canonical reader signature by #1957, but open_geotiff only forwards it to _read_to_array on the eager non-VRT branch (__init__.py:431). The GPU branch at line 410, the dask branch at line 422, and the VRT branch at line 362 never reference the kwarg, so open_geotiff(p, max_cloud_bytes=8, gpu=True) / open_geotiff(p, max_cloud_bytes=8, chunks=N) / open_geotiff(vrt, max_cloud_bytes=8) all silently drop the budget. Same class of dispatcher-silently-drops-backend-kwarg bug fixed by #1561 / #1605 / #1685 / #1810 for other kwargs; the two sibling kwargs on_gpu_failure (line 339) and missing_sources (line 355) already raise ValueError when used on a path where they do not apply. 11 tests: 4 xfail(strict=True) pinning the fix surface (gpu, dask, vrt, dask+gpu), 3 passing pins on the current silent-drop behaviour so the fix is visible as a diff, 4 positive pins that the eager local + file-like paths accept the kwarg (docstring no-op contract). Filed issue #1974 for the dispatcher fix (sweep is test-only). Cat 4 HIGH (silent backend-kwarg drop). Pass 15 (2026-05-15): added test_write_vrt_bool_nodata_1921.py closing Cat 1 HIGH backend-parity gap on bool nodata rejection. Issue #1911 added the isinstance(nodata, (bool, np.bool_)) -> TypeError guard at to_geotiff and build_geo_tags, but the sibling writers were left unchecked: write_vrt(nodata=True) silently emits <NoDataValue>True</NoDataValue> into the VRT XML (str(True) drops the sentinel because no reader parses 'True' as numeric); write_geotiff_gpu direct call relies on the build_geo_tags defense-in-depth rather than an entry-point check, so a future refactor moving that guard would regress the GPU writer with no test coverage. 17 new tests: 4 xfail (strict=True) pinning the write_vrt fix surface (issue #1921), 1 passing pin on the current buggy str(True) emission so the fix is visible as a diff, 6 numeric/None happy-path tests on write_vrt, 4 GPU writer direct-call bool-reject tests (4 dtypes x 1 call), 1 to_geotiff(gpu=True) dispatcher thread-through. Filed issue #1921 for the write_vrt fix (sweep is test-only). Cat 1 HIGH (write_vrt backend parity bug) + Cat 1 MEDIUM (write_geotiff_gpu defense-in-depth pin). Pass 14 (2026-05-15): added test_dask_streaming_write_degenerate_2026_05_15.py closing Cat 3 HIGH and Cat 2 HIGH/MEDIUM gaps on the dask streaming write path (to_geotiff with dask-backed DataArray, #1084). test_streaming_write.py covered 100x100 with a NaN block plus a 2x2 small raster but had nothing 1-pixel-row, 1-pixel-column, all-NaN, all-Inf, or +/-Inf-mixed. The streaming tile-row segmenter (#1485) on a 1-pixel-tall raster and the streaming nodata-mask coercion on an all-NaN chunk were reachable only with a dask input and had no direct coverage; a regression on either would not surface from the eager numpy path or the write_geotiff_gpu path (pass 5 covered the GPU writer's degenerate shapes). 16 new tests, all passing: 1x1 chunk-matches-shape + nodata-attr round-trip + uint16, 1xN single chunk + chunks-split-columns + wide-segmented-by-buffer (#1485 streaming_buffer_bytes=1 forces the segmenter), Nx1 single chunk + chunks-split-rows, all-NaN with finite sentinel + all-NaN without sentinel, mixed NaN/+Inf/-Inf preserving Inf bit-exact + sentinel masking NaN only, all-+Inf and all--Inf, predictor=3 (float predictor) round-trip on float32 + float64 plus int-dtype ValueError. predictor=3 streaming coverage extends the small-chunk and int-rejection geometry around test_predictor_fp_write_1313.test_predictor3_streaming_dask (which already covers a 128x192 predictor=3 dask streaming write with a Predictor-tag assertion). Cat 3 HIGH (1x1/1xN/Nx1) + Cat 2 HIGH (all-NaN with sentinel) + Cat 2 MEDIUM (mixed-Inf, all-Inf) + Cat 4 MEDIUM (predictor=3 streaming). Pass 13 (2026-05-13): added test_size_param_validation_gpu_vrt_1776.py closing Cat 4 HIGH parameter-coverage gap on size-arg validation. Issue #1752 added tile_size validation to to_geotiff and chunks validation to read_geotiff_dask, but the matching kwargs on three sibling entry points were left unchecked: write_geotiff_gpu(tile_size=) raised ZeroDivisionError for 0, struct.error for -1, TypeError for 256.0; read_geotiff_gpu(chunks=) and read_vrt(chunks=) raised ZeroDivisionError for 0 and silently accepted negative values. Factored two shared validators (_validate_tile_size_arg, _validate_chunks_arg) and called them up front from each entry point. 34 new tests, all passing on GPU host: tile_size matrix on write_geotiff_gpu (0/-1/256.0/True/False/positive/np.int64), chunks matrix on read_geotiff_gpu and read_vrt (0/-1/(0,N)/(N,-1)/wrong-length/bool/non-int/(N,float)/positive/np.int64), dispatcher thread-through tests (open_geotiff(gpu=True, chunks=0), to_geotiff(gpu=True, tile_size=0)). Pre-existing 13 #1752 tests still pass after refactor. Filed issue #1776. Pass 12 (2026-05-12): added test_gpu_writer_overview_mode_and_compression_level_1740.py closing Cat 4 HIGH and Cat 4 MEDIUM parameter-coverage gaps. (1) write_geotiff_gpu(overview_resampling='mode') and the dedicated _block_reduce_2d_gpu mode-fallback branch (_gpu_decode.py:3051-3056) had zero direct tests; six of the seven overview_resampling modes were covered (mean/nearest by test_features, min/max/median by pass 6, cubic by test_signature_parity_1631) but mode was the odd one out -- a regression dropping the mode dispatch from _block_reduce_2d_gpu would fall through to the mean reshape branch and emit wrong overview pixels for integer rasters. (2) write_geotiff_gpu(compression_level=) documented as accepted-but-ignored had no test; the CPU writer rejects out-of-range levels with ValueError, the GPU writer is documented not to -- a regression wiring the GPU writer up to the CPU range validator would silently break every to_geotiff(gpu=True, compression_level=X) caller for in-range levels and noisily for out-of-range. 19 tests, all passing on GPU host: _block_reduce_2d_gpu(method='mode') CPU-parity on 4x4 deterministic + random 8x8 + dtype-preserved across u8/u16/i16/i32, write_geotiff_gpu(cog=True, overview_resampling='mode') end-to-end round trip, to_geotiff(gpu=True, ..., overview_resampling='mode') dispatcher thread-through, GPU-vs-CPU pixel parity on 8x8 input, write_geotiff_gpu(compression_level=) in-range matrix on zstd/deflate, out-of-range matrix (zstd=999/-5, deflate=50/0) accepted without raising + round-trip preserved, to_geotiff(gpu=True, compression_level=999) dispatcher thread-through, companion CPU rejects-OOR pin to lock the asymmetry. Mutation against the mode branch (drop the 'if method == mode' block in _block_reduce_2d_gpu) flipped 9 mode tests red. Filed issue #1740. Pass 11 (2026-05-12): added test_gpu_writer_cpu_fallback_codecs_2026_05_12.py closing a Cat 4 HIGH parameter-coverage gap on write_geotiff_gpu compression= modes for the CPU-fallback codecs (lzw, packbits, lz4, lerc, jpeg2000/j2k). Pass 7 (test_gpu_writer_compression_modes_2026_05_11) covered only none/deflate/zstd/jpeg; the remaining five codecs route through dedicated branches in gpu_compress_tiles (_gpu_decode.py:2974-3019) with CPU fallbacks (lerc_compress, jpeg2000_compress, cpu_compress) that had zero direct tests via write_geotiff_gpu. A regression in routing/tag-wiring/fallback dispatch would ship silently because the internal reader uses the same compression-tag table. 17 tests, all passing on GPU host: lzw/packbits/lz4 round-trip + compression-tag pin on uint16, lerc lossless float32 + uint16 round-trip + tag pin, jpeg2000 uint8 single-band + RGB multi-band lossless round-trip + j2k-alias parity + tag pin, GPU-vs-CPU writer pixel parity for lzw/packbits, to_geotiff(gpu=True, compression=lzw/packbits) dispatcher thread-through. Mutation against compression dispatch (swap lzw bytes to zstd; swap lerc bytes to deflate) flipped round-trip tests red. Filed issue #1706. Pass 10 (2026-05-12): added test_kwarg_behaviour_2026_05_12_v2.py closing two Cat 4 HIGH parameter-coverage gaps. (1) write_geotiff_gpu(predictor=True/2/3) had zero direct tests; the GPU writer threads predictor= through normalize_predictor and gpu_compress_tiles into five CUDA encode kernels (_predictor_encode_kernel_u8/u16/u32/u64 for predictor=2, _fp_predictor_encode_kernel for predictor=3) and a regression dropping the encode-kernel calls would ship corrupt files. (2) read_vrt(window=) had no behaviour tests (only a signature pin in test_signature_annotations_1654); the kwarg is documented and _vrt.read_vrt implements full windowed-read semantics (clip, multi-source overlap, src/dst scaling, GeoTransform origin shift on coords + attrs['transform']). 23 tests, all passing on GPU host: predictor=True/2 round-trips on u8/u16/i32 + 3-band RGB samples_per_pixel stride; predictor=3 lossless round-trip on f32 and f64; predictor=3 int-dtype ValueError (CPU/GPU parity); CPU/GPU pixel-exact parity for pred=2 u16 and pred=3 f32; read_vrt(window=) subregion + full + clamp-overflow + clamp-negative + 2x1 mosaic seam straddle + offset past seam + transform-attr origin shift + y/x coords half-pixel shift + window+band + window+chunks (dask) + window+gpu (cupy) + window+gpu+chunks (dask+cupy). Mutation against the encode dispatch flipped 7 predictor tests red. Filed issue #1690. Pass 9 (2026-05-12): added test_kwarg_behaviour_2026_05_12.py closing three Cat 4 MEDIUM parameter-coverage gaps plus one Cat 4 LOW error path. write_vrt documented kwargs (relative/crs_wkt/nodata) had a smoke-test pinning that the kwargs are accepted but no test verified the override *effect* -- a regression dropping the override branch and silently using the default-from-first-source would ship undetected. read_geotiff_gpu(dtype=) cast had zero direct tests; the eager path has TestDtypeEager and dask has TestDtypeDask but the GPU branch had no equivalent. write_geotiff_gpu(bigtiff=) threads through to _assemble_tiff(force_bigtiff=) but no test asserted the on-disk header byte switches; the CPU writer had it via test_features::test_force_bigtiff_via_public_api. write_vrt(source_files=[]) ValueError was uncovered. 26 tests, all passing on GPU host: write_vrt relative=True/False XML attribute + path inspection + parse-back round-trip, write_vrt crs_wkt= override distinct-from-default XML check, write_vrt nodata= override + default-from-source coverage, write_vrt([]) ValueError + no-file side effect, read_geotiff_gpu dtype= matrix (float64->float32, float64->float16, uint16->int32, uint16->uint8, float-to-int raise, dtype=None preserves native), open_geotiff(gpu=True, dtype=) dispatcher, read_geotiff_gpu(chunks=, dtype=) dask+GPU branch, write_geotiff_gpu bigtiff=True/False/None header verification, to_geotiff(gpu=True, bigtiff=True) dispatcher thread-through. Pass 8 (2026-05-11): added test_lz4_compression_level_2026_05_11.py closing Cat 4 MEDIUM parameter-coverage gap on compression='lz4' + compression_level=. _LEVEL_RANGES advertises lz4: (0, 16) but only deflate (1, 9) and zstd (1, 22) had direct level boundary + round-trip + reject tests. The range check is the gatekeeper -- lz4_compress silently accepts any int level -- so a regression dropping 'lz4' from _LEVEL_RANGES would ship undetected. 18 tests, all passing: round-trip at levels 0/1/9/16 (lossless), default-level no-arg path, higher-level-not-larger smoke check on compressible input, out-of-range reject at -1/-10/17/100 on eager path, valid-range message format pin (lz4 valid: 0-16), dask streaming round-trip at 0/1/8/16, dask streaming out-of-range reject at -1/17/50 (separate _LEVEL_RANGES call site). Pass 7 (2026-05-11): added test_gpu_writer_compression_modes_2026_05_11.py closing Cat 4 HIGH gap on write_geotiff_gpu compression= modes. The writer documents zstd (default, fastest GPU), deflate, jpeg, and none, but only deflate + none had round-trip tests; the default zstd and the jpeg (nvJPEG/Pillow) paths shipped without targeted coverage. 11 new tests, all passing on GPU host: zstd round-trip + default-codec pinning, jpeg round-trip on 3-band RGB uint8 + 1-band greyscale, TIFF compression-tag header check across none/deflate/zstd/jpeg, plain deflate + none round-trips outside the COG/sentinel paths, and a cross-codec lossless parity check (zstd/deflate/none agree pixel-exact). nvJPEG path was exercised live, not just the Pillow fallback. Pass 6 (2026-05-11): added test_overview_resampling_min_max_median_2026_05_11.py covering Cat 4 HIGH parameter-coverage gap on overview_resampling=min/max/median. CPU end-to-end paths were already covered by test_cog_overview_nodata_1613::test_cpu_cog_overview_aggregations_ignore_sentinel; the GPU end-to-end paths and the direct CPU+GPU block-reducer branches had no targeted tests, so a regression on those code paths would ship undetected. 26 tests, all passing on GPU host: block-reducer unit tests (finite + partial-NaN), end-to-end COG writes for both to_geotiff and write_geotiff_gpu, CPU/GPU parity for to_geotiff(gpu=True), CPU nodata-sentinel regression check, and ValueError error-path tests for unknown method names on both backends. Pass 5 (2026-05-11): added test_degenerate_shapes_backends_2026_05_11.py covering Cat 3 HIGH geometric gaps (1x1 / 1xN / Nx1 reads on dask+numpy, GPU, dask+cupy backends; 1x1 / 1xN / Nx1 writes through write_geotiff_gpu) and Cat 2 MEDIUM NaN/Inf gaps (all-NaN read on GPU + dask+cupy, Inf / -Inf reads on all non-eager backends, NaN sentinel mask on dask read path including sentinel block split across chunk boundary). 23 tests, all passing on GPU host. Prior passes still hold: pass 4 (r4) closed read_geotiff_gpu/dask name= + max_pixels= kwargs (Cat 4), pass 3 (r3) closed read_vrt GPU/dask+GPU backend dispatch (Cat 1) and dtype/name kwargs (Cat 4)."
|
|
14
|
+
polygonize,2026-05-29,2623,MEDIUM,4,"Pass 3 (2026-05-29): added test_polygonize_mask_dtype_coverage_2026_05_29.py (41 passed, 8 xfailed on a CUDA host). Closes Cat 4 MEDIUM parameter-coverage gap: mask= is documented to accept bool/integer/float values but every prior test passed only a bool mask. Integer masks (int32/int64) now pinned against the same-backend bool-mask output on all four backends x both raster dtypes x connectivity 4/8; float-mask-on-integer-raster also pinned. Each backend is compared to its OWN bool reference to isolate mask-dtype from the unrelated numpy-vs-dask hole-vs-single-ring representation difference. Mutation (drop the not-mask[ij] exclusion in _calculate_regions) flips 11 tests red incl. the pixel-exclusion sanity anchor; clean md5 restore. Surfaced source bug #2623: a float-dtype mask on a float-dtype raster raises TypeError at polygonize.py:918 (mask & nan_mask; bitwise_and undefined for float&bool; cupy/dask route floats through _polygonize_numpy so they crash too; int masks coerce fine). 8 float-mask cases marked xfail(strict, raises=TypeError) referencing #2623. Test-only; source untouched. | Pass 2 (2026-05-27): added test_polygonize_atol_rtol_backend_coverage_2026_05_27.py with 15 tests, all passing on a CUDA host. Closes Cat 4 MEDIUM parameter-coverage gap on atol/rtol forwarding through the cupy and dask+cupy backends. atol/rtol were exposed by #2173 / #2194 and thread through _polygonize_cupy (polygonize.py:808) and _polygonize_dask (polygonize.py:1719); the dask path further plumbs them into dask.delayed(_polygonize_chunk)(...) at lines 1748-1754 and into _bucket_key_for_value for cross-chunk merge bucketing at lines 1757-1758. Pre-existing tests covered non-default atol/rtol only on numpy and dask+numpy. The cupy and dask+cupy dispatchers were untested -- a regression dropping the kwargs there would silently change the float polygon count and would not be caught. Same dispatcher-silently-drops-kwarg pattern fixed by #1561 / #1605 / #1685 / #1810 / #1974 on adjacent GeoTIFF surfaces. 15 tests: cupy strict-equality + default-tolerance pin on _REPRO_2173, dask+cupy strict-equality single-chunk + multi-chunk (engages cross-chunk merge bucket) + default-tolerance multi-chunk pin, cupy intermediate-atol small/large pair, dask+cupy intermediate-atol single/multi-chunk small + single-chunk large, cupy integer atol-ignored matrix, dask+cupy integer atol-ignored single-chunk + multi-chunk, cupy rtol-only large/small matrix. Mutation against _polygonize_cupy float branch (drop atol/rtol kwargs in the _polygonize_numpy forward call at polygonize.py:823-825) flips 3 of 5 cupy tests red; mutation against dask.delayed(_polygonize_chunk)(...) at polygonize.py:1748-1754 (drop atol, rtol args) flips 2 of 6 dask+cupy tests red. Confirmed clean restore via md5sum. Source untouched. Filed issue #2537 (test-only). Cat 4 MEDIUM (parameter coverage on cupy + dask+cupy atol/rtol forwarding). Pass 1 (2026-05-19): added test_polygonize_coverage_2026_05_19.py with 58 tests, all passing on a CUDA host. Closes Cat 3 HIGH 1x1 / Nx1 single-column geometric gaps (Nx1 exercises the nx==1 padding path at polygonize.py:565 and the cupy nx==1 numpy-fallback at polygonize.py:671), Cat 3 MEDIUM 1xN single-row and all-equal-value rasters on all four backends. Closes Cat 2 HIGH NaN parity for cupy + dask+cupy (numpy/dask were already covered by test_polygonize_nan_pixels_excluded*), Cat 2 MEDIUM all-NaN raster on all four backends, Cat 2 HIGH +/-Inf pins on all four backends. Filed source-bug issue #2155: numpy/dask/dask+cupy backends silently absorb Inf cells into adjacent finite polygons because _is_close reduces abs(inf-inf) to nan; cupy backend handles Inf correctly. Pins lock the asymmetric behaviour so the fix is visible. Closes Cat 1 MEDIUM simplify_tolerance + mask= parity gaps on dask+cupy backend (numpy/cupy/dask were already covered). Closes Cat 4 MEDIUM column_name non-default value across geopandas/spatialpandas/geojson return types and Cat 4 MEDIUM validation error paths (bad connectivity, bad transform length, mask shape mismatch, mask underlying-type mismatch). Cat 5 N/A: polygonize returns lists/dataframes, not a DataArray with attrs to propagate."
|
|
15
|
+
rasterize,2026-05-27,,HIGH,1;2;4,"Pass 3 (2026-05-27): added test_rasterize_coverage_2026_05_27.py with 23 tests, all passing on a CUDA host. Closes Cat 1 HIGH eager-cupy merge-mode parity gap: pass-1 only pinned merge='last' on a single non-overlapping polygon via TestCuPy.test_cupy_matches_numpy, and the Inf-burn tests in pass-2 only partially exercised sum/min/max on eager cupy; the parametrised six-mode parity test (last/first/max/min/sum/count) that TestDaskNumpy and TestDaskCupy carry had no eager-cupy twin, so a routing regression that swapped any of the six GPU atomic kernels in _ensure_gpu_kernels (rasterize.py:1308-1556) would slip past the dask+cupy tiled-finalize tests. Pin a three-way overlapping polygon scene plus a three-way overlapping point scene across all six modes on the eager cupy backend, with sanity checks (first!=last, min<max) so the fixture is non-degenerate. Closes Cat 1 MEDIUM eager-cupy empty-geometry-list gap: numpy + dask+numpy + dask+cupy had empty-list coverage but the eager _run_cupy zero-geometry path (zero-sized cupy bbox/edge/segment buffers) did not -- pin that use_cuda=True with [] still returns a cupy.ndarray (not a numpy short-circuit) under both an explicit fill and the default NaN fill. Closes Cat 2 MEDIUM all-equal-value count gap: four overlapping rectangles all burning 1.0 under merge='count' must still count overlaps as >1; a future GPU atomic optimisation that deduped identical-value writes would silently break density rasters. Closes Cat 4 MEDIUM name= kwarg thread-through on dask+numpy / eager cupy / dask+cupy (the eager numpy path was the only one with name= coverage at TestBasic.test_output_name). Source untouched. Pass 2 (2026-05-21): added test_rasterize_coverage_2026_05_21.py with 58 tests, all passing on a CUDA host. Closes Cat 2 HIGH +/-Inf and NaN burn-value gaps that pass-1 left untouched: pin +Inf / -Inf / Inf+(-Inf)/NaN polygon, point, and line burn behaviour across numpy / cupy / dask+numpy / dask+cupy, plus Inf+finite under sum stays Inf, Inf+(-Inf) under sum collapses to NaN, min(Inf, 1.0) and max(-Inf, 1.0) pick the finite value, and Inf-as-bound is rejected with the same ValueError as NaN-as-bound (pass-1 only tested the NaN-bound rejection). Closes Cat 1 MEDIUM nested GeometryCollection on all four backends: a GC inside a GC has no direct test today even though rasterize.py:1995 documents recursive unpacking, and the deeply-nested-3-levels eager test pins the recursion depth limit isn't 1 or 2. Closes Cat 1 MEDIUM columns= (multi-column) parity on cupy and dask+cupy (TestMultiColumn covered numpy/dask+numpy only); pin three columns of props on GPU so the (N, P) loop survives the kernel boundary. Closes Cat 3 LOW rectangular-pixel parity with resolution=(rx, ry) across backends. Filed source-bug issue #2255: GPU max/min merge silently suppresses NaN burn values -- CPU returns NaN (1.0 > NaN is False, keeps NaN); GPU returns 1.0 because the kernel inits the output buffer to -inf for max (or +inf for min) and atomicMax/Min is NaN-suppressing under IEEE device semantics. Pinned both the CPU NaN-propagating behaviour and the GPU NaN-suppressing behaviour as paired tests (test_nan_burn_overlaps_max_cpu_propagates vs test_nan_burn_overlaps_max_gpu_suppresses_nan, plus test_nan_burn_single_geom_max_gpu_returns_neg_inf for the single-write-on-GPU-returns-buffer-init case) so the divergence is visible in CI until the GPU kernels are aligned. Source untouched. Pass 1 (2026-05-17): added test_rasterize_coverage_2026_05_17.py with 34 tests, all passing on a CUDA host. Closes four documented public-API gaps left after the pass-0 audit. (1) Cat 3 HIGH 1x1 single-pixel raster -- test_rasterize.py covers 1xN strips and Nx1 strips but never width=1 AND height=1, so the polygon scanline / line Bresenham / point burn kernels all ship without the single-cell degenerate case; the new TestSinglePixelRaster class pins polygon/point/line on eager numpy plus polygon parity across cupy / dask+numpy / dask+cupy. (2) Cat 4 HIGH like= template-raster parameter is documented at rasterize.py:2038 and implemented by _extract_grid_from_like (line 1930) but no test exercises it; TestLikeParameter pins dtype/bounds/coords inheritance, the three override branches (dtype, bounds, width/height), the three validation branches (not-DataArray, 3D, wrong dim names) and like= on all four backends. Mutation against the like-dtype branch (rasterize.py:2183-2184) flipped the inheritance test red. (3) Cat 4 HIGH resolution= happy path -- only the oversize-rejection error path was tested (line 304); TestResolutionParameter pins the scalar branch, the tuple branch, the ceil-and-clamp-to-1 semantics, and resolution= on all four backends. (4) Cat 4 HIGH non-empty GeometryCollection unpacking is documented at rasterize.py:1995 and implemented by _classify_geometries_loop (line 228) but only the empty-GC case was tested (line 269); TestGeometryCollection pins polygon+point and polygon+line+point collections on eager numpy plus parity across cupy / dask+numpy / dask+cupy so the loop classifier's polygon/line/point sub-bucketing has direct coverage. Cat 1 MEDIUM gap closed: eager cupy all_touched=True parity vs eager numpy (TestEagerCupyAllTouched) -- the existing test only covered dask+cupy all_touched, leaving the direct GPU all_touched kernel untested. Cat 2 MEDIUM gap closed: int32 dtype with default NaN fill silently casts to the int32-min sentinel (TestIntegerDtypeNanFill) -- pin the cast so any future ValueError-raises switch is visible as a code-review diff. Pre-existing 143 passing + 2 skipped tests in test_rasterize.py untouched."
|
|
16
|
+
reproject,2026-05-10,,HIGH,1;4;5,"Added 39 tests: LiteCRS direct coverage, itrf_transform behaviour/roundtrip/array, itrf_frames, geoid_height numerical correctness + raster happy-path, vertical helpers (ellipsoidal<->orthometric/depth), reproject() lat/lon and latitude/longitude dim propagation. Note: _merge_arrays_cupy is imported but unused (no cupy merge dispatch in merge()); flagged as feature gap not test gap."
|
|
17
|
+
Pass 17 (2026-05-18): added test_mask_nodata_gpu_vrt_2052.py closing Cat 1 HIGH backend-coverage gap on the mask_nodata= opt-out kwarg (#2052). The kwarg was added in #2052 and wired through the four public readers (open_geotiff, read_geotiff_gpu, read_geotiff_dask, read_vrt), but test_mask_nodata_kwarg_2052.py only exercised the eager-numpy and dask+numpy branches. The pure-GPU mask gating at _backends/gpu.py:709, the dask+GPU dispatcher forwarding at _backends/gpu.py:991, the eager VRT mask gating at _backends/vrt.py:320, and the chunked VRT graph builder at _backends/vrt.py:408/588 had zero direct coverage. 19 new tests, all passing on GPU host: GPU eager + dask+GPU mask_nodata=False preserves uint16, GPU defaults still promote to float64, dispatcher thread-through for open_geotiff(gpu=True, mask_nodata=False) and open_geotiff(gpu=True, chunks=N, mask_nodata=False), VRT eager and chunked branches mirror, cross-backend parity (eager vs dask, eager vs GPU, eager vs dask+GPU, eager vs VRT) bit-exact under mask_nodata=False, direct read_geotiff_dask entry-point coverage. Fixture uses tiled+deflate compression so the pure nvCOMP decode path is exercised, not the CPU-fallback piggyback path. Mutation against gpu.py:709 (force mask_nodata=True) flipped 4 GPU tests red; mutation against vrt.py eager mask gate flipped 4 VRT tests red. Cat 1 HIGH (backend coverage on mask_nodata=False for GPU, dask+GPU, VRT eager, VRT chunked). Pass 16 (2026-05-15): added test_max_cloud_bytes_dispatcher_silent_drop_2026_05_15.py closing Cat 4 HIGH parameter-coverage gap on the open_geotiff dispatcher's max_cloud_bytes kwarg. The kwarg was added in #1928 (eager fsspec budget) and re-ordered into the canonical reader signature by #1957, but open_geotiff only forwards it to _read_to_array on the eager non-VRT branch (__init__.py:431). The GPU branch at line 410, the dask branch at line 422, and the VRT branch at line 362 never reference the kwarg, so open_geotiff(p, max_cloud_bytes=8, gpu=True) / open_geotiff(p, max_cloud_bytes=8, chunks=N) / open_geotiff(vrt, max_cloud_bytes=8) all silently drop the budget. Same class of dispatcher-silently-drops-backend-kwarg bug fixed by #1561 / #1605 / #1685 / #1810 for other kwargs; the two sibling kwargs on_gpu_failure (line 339) and missing_sources (line 355) already raise ValueError when used on a path where they do not apply. 11 tests: 4 xfail(strict=True) pinning the fix surface (gpu, dask, vrt, dask+gpu), 3 passing pins on the current silent-drop behaviour so the fix is visible as a diff, 4 positive pins that the eager local + file-like paths accept the kwarg (docstring no-op contract). Filed issue #1974 for the dispatcher fix (sweep is test-only). Cat 4 HIGH (silent backend-kwarg drop). Pass 15 (2026-05-15): added test_write_vrt_bool_nodata_1921.py closing Cat 1 HIGH backend-parity gap on bool nodata rejection. Issue #1911 added the isinstance(nodata, (bool, np.bool_)) -> TypeError guard at to_geotiff and build_geo_tags, but the sibling writers were left unchecked: write_vrt(nodata=True) silently emits <NoDataValue>True</NoDataValue> into the VRT XML (str(True) drops the sentinel because no reader parses 'True' as numeric); write_geotiff_gpu direct call relies on the build_geo_tags defense-in-depth rather than an entry-point check, so a future refactor moving that guard would regress the GPU writer with no test coverage. 17 new tests: 4 xfail (strict=True) pinning the write_vrt fix surface (issue #1921), 1 passing pin on the current buggy str(True) emission so the fix is visible as a diff, 6 numeric/None happy-path tests on write_vrt, 4 GPU writer direct-call bool-reject tests (4 dtypes x 1 call), 1 to_geotiff(gpu=True) dispatcher thread-through. Filed issue #1921 for the write_vrt fix (sweep is test-only). Cat 1 HIGH (write_vrt backend parity bug) + Cat 1 MEDIUM (write_geotiff_gpu defense-in-depth pin). Pass 14 (2026-05-15): added test_dask_streaming_write_degenerate_2026_05_15.py closing Cat 3 HIGH and Cat 2 HIGH/MEDIUM gaps on the dask streaming write path (to_geotiff with dask-backed DataArray, #1084). test_streaming_write.py covered 100x100 with a NaN block plus a 2x2 small raster but had nothing 1-pixel-row, 1-pixel-column, all-NaN, all-Inf, or +/-Inf-mixed. The streaming tile-row segmenter (#1485) on a 1-pixel-tall raster and the streaming nodata-mask coercion on an all-NaN chunk were reachable only with a dask input and had no direct coverage; a regression on either would not surface from the eager numpy path or the write_geotiff_gpu path (pass 5 covered the GPU writer's degenerate shapes). 16 new tests, all passing: 1x1 chunk-matches-shape + nodata-attr round-trip + uint16, 1xN single chunk + chunks-split-columns + wide-segmented-by-buffer (#1485 streaming_buffer_bytes=1 forces the segmenter), Nx1 single chunk + chunks-split-rows, all-NaN with finite sentinel + all-NaN without sentinel, mixed NaN/+Inf/-Inf preserving Inf bit-exact + sentinel masking NaN only, all-+Inf and all--Inf, predictor=3 (float predictor) round-trip on float32 + float64 plus int-dtype ValueError. predictor=3 streaming coverage extends the small-chunk and int-rejection geometry around test_predictor_fp_write_1313.test_predictor3_streaming_dask (which already covers a 128x192 predictor=3 dask streaming write with a Predictor-tag assertion). Cat 3 HIGH (1x1/1xN/Nx1) + Cat 2 HIGH (all-NaN with sentinel) + Cat 2 MEDIUM (mixed-Inf, all-Inf) + Cat 4 MEDIUM (predictor=3 streaming). Pass 13 (2026-05-13): added test_size_param_validation_gpu_vrt_1776.py closing Cat 4 HIGH parameter-coverage gap on size-arg validation. Issue #1752 added tile_size validation to to_geotiff and chunks validation to read_geotiff_dask, but the matching kwargs on three sibling entry points were left unchecked: write_geotiff_gpu(tile_size=) raised ZeroDivisionError for 0, struct.error for -1, TypeError for 256.0; read_geotiff_gpu(chunks=) and read_vrt(chunks=) raised ZeroDivisionError for 0 and silently accepted negative values. Factored two shared validators (_validate_tile_size_arg, _validate_chunks_arg) and called them up front from each entry point. 34 new tests, all passing on GPU host: tile_size matrix on write_geotiff_gpu (0/-1/256.0/True/False/positive/np.int64), chunks matrix on read_geotiff_gpu and read_vrt (0/-1/(0,N)/(N,-1)/wrong-length/bool/non-int/(N,float)/positive/np.int64), dispatcher thread-through tests (open_geotiff(gpu=True, chunks=0), to_geotiff(gpu=True, tile_size=0)). Pre-existing 13 #1752 tests still pass after refactor. Filed issue #1776. Pass 12 (2026-05-12): added test_gpu_writer_overview_mode_and_compression_level_1740.py closing Cat 4 HIGH and Cat 4 MEDIUM parameter-coverage gaps. (1) write_geotiff_gpu(overview_resampling='mode') and the dedicated _block_reduce_2d_gpu mode-fallback branch (_gpu_decode.py:3051-3056) had zero direct tests; six of the seven overview_resampling modes were covered (mean/nearest by test_features, min/max/median by pass 6, cubic by test_signature_parity_1631) but mode was the odd one out -- a regression dropping the mode dispatch from _block_reduce_2d_gpu would fall through to the mean reshape branch and emit wrong overview pixels for integer rasters. (2) write_geotiff_gpu(compression_level=) documented as accepted-but-ignored had no test; the CPU writer rejects out-of-range levels with ValueError, the GPU writer is documented not to -- a regression wiring the GPU writer up to the CPU range validator would silently break every to_geotiff(gpu=True, compression_level=X) caller for in-range levels and noisily for out-of-range. 19 tests, all passing on GPU host: _block_reduce_2d_gpu(method='mode') CPU-parity on 4x4 deterministic + random 8x8 + dtype-preserved across u8/u16/i16/i32, write_geotiff_gpu(cog=True, overview_resampling='mode') end-to-end round trip, to_geotiff(gpu=True, ..., overview_resampling='mode') dispatcher thread-through, GPU-vs-CPU pixel parity on 8x8 input, write_geotiff_gpu(compression_level=) in-range matrix on zstd/deflate, out-of-range matrix (zstd=999/-5, deflate=50/0) accepted without raising + round-trip preserved, to_geotiff(gpu=True, compression_level=999) dispatcher thread-through, companion CPU rejects-OOR pin to lock the asymmetry. Mutation against the mode branch (drop the 'if method == mode' block in _block_reduce_2d_gpu) flipped 9 mode tests red. Filed issue #1740. Pass 11 (2026-05-12): added test_gpu_writer_cpu_fallback_codecs_2026_05_12.py closing a Cat 4 HIGH parameter-coverage gap on write_geotiff_gpu compression= modes for the CPU-fallback codecs (lzw, packbits, lz4, lerc, jpeg2000/j2k). Pass 7 (test_gpu_writer_compression_modes_2026_05_11) covered only none/deflate/zstd/jpeg; the remaining five codecs route through dedicated branches in gpu_compress_tiles (_gpu_decode.py:2974-3019) with CPU fallbacks (lerc_compress, jpeg2000_compress, cpu_compress) that had zero direct tests via write_geotiff_gpu. A regression in routing/tag-wiring/fallback dispatch would ship silently because the internal reader uses the same compression-tag table. 17 tests, all passing on GPU host: lzw/packbits/lz4 round-trip + compression-tag pin on uint16, lerc lossless float32 + uint16 round-trip + tag pin, jpeg2000 uint8 single-band + RGB multi-band lossless round-trip + j2k-alias parity + tag pin, GPU-vs-CPU writer pixel parity for lzw/packbits, to_geotiff(gpu=True, compression=lzw/packbits) dispatcher thread-through. Mutation against compression dispatch (swap lzw bytes to zstd; swap lerc bytes to deflate) flipped round-trip tests red. Filed issue #1706. Pass 10 (2026-05-12): added test_kwarg_behaviour_2026_05_12_v2.py closing two Cat 4 HIGH parameter-coverage gaps. (1) write_geotiff_gpu(predictor=True/2/3) had zero direct tests; the GPU writer threads predictor= through normalize_predictor and gpu_compress_tiles into five CUDA encode kernels (_predictor_encode_kernel_u8/u16/u32/u64 for predictor=2, _fp_predictor_encode_kernel for predictor=3) and a regression dropping the encode-kernel calls would ship corrupt files. (2) read_vrt(window=) had no behaviour tests (only a signature pin in test_signature_annotations_1654); the kwarg is documented and _vrt.read_vrt implements full windowed-read semantics (clip, multi-source overlap, src/dst scaling, GeoTransform origin shift on coords + attrs['transform']). 23 tests, all passing on GPU host: predictor=True/2 round-trips on u8/u16/i32 + 3-band RGB samples_per_pixel stride; predictor=3 lossless round-trip on f32 and f64; predictor=3 int-dtype ValueError (CPU/GPU parity); CPU/GPU pixel-exact parity for pred=2 u16 and pred=3 f32; read_vrt(window=) subregion + full + clamp-overflow + clamp-negative + 2x1 mosaic seam straddle + offset past seam + transform-attr origin shift + y/x coords half-pixel shift + window+band + window+chunks (dask) + window+gpu (cupy) + window+gpu+chunks (dask+cupy). Mutation against the encode dispatch flipped 7 predictor tests red. Filed issue #1690. Pass 9 (2026-05-12): added test_kwarg_behaviour_2026_05_12.py closing three Cat 4 MEDIUM parameter-coverage gaps plus one Cat 4 LOW error path. write_vrt documented kwargs (relative/crs_wkt/nodata) had a smoke-test pinning that the kwargs are accepted but no test verified the override *effect* -- a regression dropping the override branch and silently using the default-from-first-source would ship undetected. read_geotiff_gpu(dtype=) cast had zero direct tests; the eager path has TestDtypeEager and dask has TestDtypeDask but the GPU branch had no equivalent. write_geotiff_gpu(bigtiff=) threads through to _assemble_tiff(force_bigtiff=) but no test asserted the on-disk header byte switches; the CPU writer had it via test_features::test_force_bigtiff_via_public_api. write_vrt(source_files=[]) ValueError was uncovered. 26 tests, all passing on GPU host: write_vrt relative=True/False XML attribute + path inspection + parse-back round-trip, write_vrt crs_wkt= override distinct-from-default XML check, write_vrt nodata= override + default-from-source coverage, write_vrt([]) ValueError + no-file side effect, read_geotiff_gpu dtype= matrix (float64->float32, float64->float16, uint16->int32, uint16->uint8, float-to-int raise, dtype=None preserves native), open_geotiff(gpu=True, dtype=) dispatcher, read_geotiff_gpu(chunks=, dtype=) dask+GPU branch, write_geotiff_gpu bigtiff=True/False/None header verification, to_geotiff(gpu=True, bigtiff=True) dispatcher thread-through. Pass 8 (2026-05-11): added test_lz4_compression_level_2026_05_11.py closing Cat 4 MEDIUM parameter-coverage gap on compression='lz4' + compression_level=. _LEVEL_RANGES advertises lz4: (0, 16) but only deflate (1, 9) and zstd (1, 22) had direct level boundary + round-trip + reject tests. The range check is the gatekeeper -- lz4_compress silently accepts any int level -- so a regression dropping 'lz4' from _LEVEL_RANGES would ship undetected. 18 tests, all passing: round-trip at levels 0/1/9/16 (lossless), default-level no-arg path, higher-level-not-larger smoke check on compressible input, out-of-range reject at -1/-10/17/100 on eager path, valid-range message format pin (lz4 valid: 0-16), dask streaming round-trip at 0/1/8/16, dask streaming out-of-range reject at -1/17/50 (separate _LEVEL_RANGES call site). Pass 7 (2026-05-11): added test_gpu_writer_compression_modes_2026_05_11.py closing Cat 4 HIGH gap on write_geotiff_gpu compression= modes. The writer documents zstd (default, fastest GPU), deflate, jpeg, and none, but only deflate + none had round-trip tests; the default zstd and the jpeg (nvJPEG/Pillow) paths shipped without targeted coverage. 11 new tests, all passing on GPU host: zstd round-trip + default-codec pinning, jpeg round-trip on 3-band RGB uint8 + 1-band greyscale, TIFF compression-tag header check across none/deflate/zstd/jpeg, plain deflate + none round-trips outside the COG/sentinel paths, and a cross-codec lossless parity check (zstd/deflate/none agree pixel-exact). nvJPEG path was exercised live, not just the Pillow fallback. Pass 6 (2026-05-11): added test_overview_resampling_min_max_median_2026_05_11.py covering Cat 4 HIGH parameter-coverage gap on overview_resampling=min/max/median. CPU end-to-end paths were already covered by test_cog_overview_nodata_1613::test_cpu_cog_overview_aggregations_ignore_sentinel; the GPU end-to-end paths and the direct CPU+GPU block-reducer branches had no targeted tests, so a regression on those code paths would ship undetected. 26 tests, all passing on GPU host: block-reducer unit tests (finite + partial-NaN), end-to-end COG writes for both to_geotiff and write_geotiff_gpu, CPU/GPU parity for to_geotiff(gpu=True), CPU nodata-sentinel regression check, and ValueError error-path tests for unknown method names on both backends. Pass 5 (2026-05-11): added test_degenerate_shapes_backends_2026_05_11.py covering Cat 3 HIGH geometric gaps (1x1 / 1xN / Nx1 reads on dask+numpy, GPU, dask+cupy backends; 1x1 / 1xN / Nx1 writes through write_geotiff_gpu) and Cat 2 MEDIUM NaN/Inf gaps (all-NaN read on GPU + dask+cupy, Inf / -Inf reads on all non-eager backends, NaN sentinel mask on dask read path including sentinel block split across chunk boundary). 23 tests, all passing on GPU host. Prior passes still hold: pass 4 (r4) closed read_geotiff_gpu/dask name= + max_pixels= kwargs (Cat 4), pass 3 (r3) closed read_vrt GPU/dask+GPU backend dispatch (Cat 1) and dtype/name kwargs (Cat 4)."
|
|
18
|
+
rasterize,2026-05-21,2255,HIGH,1;2;3,"Pass 2 (2026-05-21): added test_rasterize_coverage_2026_05_21.py with 58 tests, all passing on a CUDA host. Closes Cat 2 HIGH +/-Inf and NaN burn-value gaps that pass-1 left untouched: pin +Inf / -Inf / Inf+(-Inf)/NaN polygon, point, and line burn behaviour across numpy / cupy / dask+numpy / dask+cupy, plus Inf+finite under sum stays Inf, Inf+(-Inf) under sum collapses to NaN, min(Inf, 1.0) and max(-Inf, 1.0) pick the finite value, and Inf-as-bound is rejected with the same ValueError as NaN-as-bound (pass-1 only tested the NaN-bound rejection). Closes Cat 1 MEDIUM nested GeometryCollection on all four backends: a GC inside a GC has no direct test today even though rasterize.py:1995 documents recursive unpacking, and the deeply-nested-3-levels eager test pins the recursion depth limit isn't 1 or 2. Closes Cat 1 MEDIUM columns= (multi-column) parity on cupy and dask+cupy (TestMultiColumn covered numpy/dask+numpy only); pin three columns of props on GPU so the (N, P) loop survives the kernel boundary. Closes Cat 3 LOW rectangular-pixel parity with resolution=(rx, ry) across backends. Filed source-bug issue #2255: GPU max/min merge silently suppresses NaN burn values -- CPU returns NaN (1.0 > NaN is False, keeps NaN); GPU returns 1.0 because the kernel inits the output buffer to -inf for max (or +inf for min) and atomicMax/Min is NaN-suppressing under IEEE device semantics. Pinned both the CPU NaN-propagating behaviour and the GPU NaN-suppressing behaviour as paired tests (test_nan_burn_overlaps_max_cpu_propagates vs test_nan_burn_overlaps_max_gpu_suppresses_nan, plus test_nan_burn_single_geom_max_gpu_returns_neg_inf for the single-write-on-GPU-returns-buffer-init case) so the divergence is visible in CI until the GPU kernels are aligned. Source untouched. Pass 1 (2026-05-17): added test_rasterize_coverage_2026_05_17.py with 34 tests, all passing on a CUDA host. Closes four documented public-API gaps left after the pass-0 audit. (1) Cat 3 HIGH 1x1 single-pixel raster -- test_rasterize.py covers 1xN strips and Nx1 strips but never width=1 AND height=1, so the polygon scanline / line Bresenham / point burn kernels all ship without the single-cell degenerate case; the new TestSinglePixelRaster class pins polygon/point/line on eager numpy plus polygon parity across cupy / dask+numpy / dask+cupy. (2) Cat 4 HIGH like= template-raster parameter is documented at rasterize.py:2038 and implemented by _extract_grid_from_like (line 1930) but no test exercises it; TestLikeParameter pins dtype/bounds/coords inheritance, the three override branches (dtype, bounds, width/height), the three validation branches (not-DataArray, 3D, wrong dim names) and like= on all four backends. Mutation against the like-dtype branch (rasterize.py:2183-2184) flipped the inheritance test red. (3) Cat 4 HIGH resolution= happy path -- only the oversize-rejection error path was tested (line 304); TestResolutionParameter pins the scalar branch, the tuple branch, the ceil-and-clamp-to-1 semantics, and resolution= on all four backends. (4) Cat 4 HIGH non-empty GeometryCollection unpacking is documented at rasterize.py:1995 and implemented by _classify_geometries_loop (line 228) but only the empty-GC case was tested (line 269); TestGeometryCollection pins polygon+point and polygon+line+point collections on eager numpy plus parity across cupy / dask+numpy / dask+cupy so the loop classifier's polygon/line/point sub-bucketing has direct coverage. Cat 1 MEDIUM gap closed: eager cupy all_touched=True parity vs eager numpy (TestEagerCupyAllTouched) -- the existing test only covered dask+cupy all_touched, leaving the direct GPU all_touched kernel untested. Cat 2 MEDIUM gap closed: int32 dtype with default NaN fill silently casts to the int32-min sentinel (TestIntegerDtypeNanFill) -- pin the cast so any future ValueError-raises switch is visible as a code-review diff. Pre-existing 143 passing + 2 skipped tests in test_rasterize.py untouched."
|
|
19
|
+
reproject,2026-05-27,,MEDIUM,1,"Pass 2 (2026-05-27): added test_reproject_coverage_2026_05_27.py with 10 tests, all passing on a CUDA host. Closes Cat 1 MEDIUM backend-coverage gaps left after pass 1: (a) bounds_policy=#2187 had numpy + dask+numpy coverage but no cupy / dask+cupy tests -- a regression dropping the kwarg from the GPU dispatchers would ship undetected; TestBoundsPolicyCupy and TestBoundsPolicyDaskCupy pin raw/clamp/bogus on both GPU backends and assert clamp-grid parity with numpy. (b) test_reproject_handles_inf_input only covered eager numpy; the dask, cupy, and dask+cupy chunk workers each ship their own bilinear/cubic resampler so a regression raising on +/-Inf in any one backend would not surface from the existing test. Four new tests close the matrix (dask+numpy, cupy, dask+cupy with scattered +/-Inf cells; cupy with all-Inf raster checking no spurious finite cells appear). Note carried forward from pass 1: _merge_arrays_cupy is imported but unused -- no cupy merge dispatch in merge(); feature gap not test gap. Added 39 tests: LiteCRS direct coverage, itrf_transform behaviour/roundtrip/array, itrf_frames, geoid_height numerical correctness + raster happy-path, vertical helpers (ellipsoidal<->orthometric/depth), reproject() lat/lon and latitude/longitude dim propagation. Note: _merge_arrays_cupy is imported but unused (no cupy merge dispatch in merge()); flagged as feature gap not test gap."
|
|
20
|
+
reproject,2026-05-10,,HIGH,1;4;5,"Added 39 tests: LiteCRS direct coverage, itrf_transform behaviour/roundtrip/array, itrf_frames, geoid_height numerical correctness + raster happy-path, vertical helpers (ellipsoidal<->orthometric/depth), reproject() lat/lon and latitude/longitude dim propagation. Note: _merge_arrays_cupy is imported but unused (no cupy merge dispatch in merge()); flagged as feature gap not test gap."
|
|
21
|
+
resample,2026-05-29,2547;2615,HIGH,1;2;3;5,"Pass 2 (2026-05-29): added test_resample_cupy_agg_fallback_2615.py (6 tests, all passing on CUDA host). Closes Cat 1 MEDIUM backend-coverage gap: the cupy eager aggregate CPU fallback for average/min/max at a NON-integer downsample factor (_run_cupy fy==int(fy) branch in resample.py ~L957-973) was never exercised; existing TestCuPyParity used 12x12 scale 0.5 (integer factor 2 -> GPU reshape path) and only median/mode hit the host fallback. New tests use 10x10 scale 0.3 (factor 3.33) for average/min/max parity vs numpy plus a NaN-masked variant. Issue #2615. Module is otherwise very thoroughly covered (test_resample.py + 3 supplementary files); no remaining HIGH gaps found. Pass 1 (2026-05-27): added test_resample_coverage_2026_05_27.py with 70 tests (68 passing, 2 skipped). Closes Cat 3 HIGH Nx1 single-column gap across numpy/cupy/dask+numpy/dask+cupy x 8 methods (nearest/bilinear/cubic/average/min/max/median/mode) plus Nx1 upsample-nearest parity and Nx1 cross-backend aggregate parity. Closes Cat 2 MEDIUM NaN-parity gap on cupy and dask+cupy (existing TestCuPyParity/TestDaskCuPyParity used random data without NaN; the weight-mask gate and spline-prepad had no GPU NaN coverage). Closes Cat 3 MEDIUM all-equal-value raster across 8 methods (downsample) and 3 interp methods (upsample) plus a constant-with-NaN aggregate variant. Closes Cat 5 MEDIUM non-default dim-name propagation: lat/lon, latitude/longitude, and (channel, lat, lon) 3D round-trip without being renamed to y/x; per-dim attrs (units) preserved. Closes Cat 3 MEDIUM empty-raster behaviour pin: 0-row and 0-col rasters raise (currently IndexError) -- contract covered. Filed source-bug issue #2547: cubic on dask backends fails for Nx1 / arrays smaller than depth=16; the 2 skipped tests in this file gate on that fix landing. Source untouched."
|
|
22
|
+
zonal,2026-05-27,,HIGH,1;3;4;5,"Pass 1 (2026-05-27): added test_zonal_backend_coverage_2026_05_27.py with 32 tests, all passing on a CUDA host. Closes Cat 1 HIGH backend-coverage gaps: crosstab cupy + dask+cupy (_crosstab_cupy / _crosstab_dask_cupy were dispatched but never invoked by tests), regions cupy + dask+cupy (_regions_cupy via cupyx.scipy.ndimage + _regions_dask_cupy), trim dask+numpy + cupy + dask+cupy (_trim_bounds_dask isnan path and cupy data.get() path), crop dask+numpy + cupy + dask+cupy (_crop_bounds_dask + cupy data.get() path), apply 3D cupy + dask+cupy (per-layer kernel launch over the third axis in _apply_cupy and _apply_dask_cupy). Existing test_zonal.py covered only numpy + dask+numpy for crosstab/regions/trim/crop and 2D-only for cupy apply. Closes Cat 3 MEDIUM 1x1 / 1xN / Nx1 strip edge cases for trim, crop, and regions. Closes Cat 4 LOW pins: regions(neighborhood=6) ValueError, suggest_zonal_canvas(crs='Geographic') aspect-ratio pin and invalid-crs KeyError, crosstab cupy zone_ids/cat_ids filter, crosstab cupy agg='percentage'. Closes Cat 5 MEDIUM: regions coords/attrs propagation across numpy + dask+numpy, trim/crop name='trim'/'crop' default + attrs preservation. Also pins the documented numpy-vs-dask trim asymmetry on NaN sentinel (numpy _trim does equality which never matches NaN; dask _trim_bounds_dask has dedicated isnan branch). Mutation against the cupy.asnumpy() conversion in _crosstab_cupy flipped test_crosstab_cupy_matches_numpy red. Source untouched."
|
|
23
|
+
Pass 17 (2026-05-18): added test_mask_nodata_gpu_vrt_2052.py closing Cat 1 HIGH backend-coverage gap on the mask_nodata= opt-out kwarg (#2052). The kwarg was added in #2052 and wired through the four public readers (open_geotiff, read_geotiff_gpu, read_geotiff_dask, read_vrt), but test_mask_nodata_kwarg_2052.py only exercised the eager-numpy and dask+numpy branches. The pure-GPU mask gating at _backends/gpu.py:709, the dask+GPU dispatcher forwarding at _backends/gpu.py:991, the eager VRT mask gating at _backends/vrt.py:320, and the chunked VRT graph builder at _backends/vrt.py:408/588 had zero direct coverage. 19 new tests, all passing on GPU host: GPU eager + dask+GPU mask_nodata=False preserves uint16, GPU defaults still promote to float64, dispatcher thread-through for open_geotiff(gpu=True, mask_nodata=False) and open_geotiff(gpu=True, chunks=N, mask_nodata=False), VRT eager and chunked branches mirror, cross-backend parity (eager vs dask, eager vs GPU, eager vs dask+GPU, eager vs VRT) bit-exact under mask_nodata=False, direct read_geotiff_dask entry-point coverage. Fixture uses tiled+deflate compression so the pure nvCOMP decode path is exercised, not the CPU-fallback piggyback path. Mutation against gpu.py:709 (force mask_nodata=True) flipped 4 GPU tests red; mutation against vrt.py eager mask gate flipped 4 VRT tests red. Cat 1 HIGH (backend coverage on mask_nodata=False for GPU, dask+GPU, VRT eager, VRT chunked). Pass 16 (2026-05-15): added test_max_cloud_bytes_dispatcher_silent_drop_2026_05_15.py closing Cat 4 HIGH parameter-coverage gap on the open_geotiff dispatcher's max_cloud_bytes kwarg. The kwarg was added in #1928 (eager fsspec budget) and re-ordered into the canonical reader signature by #1957, but open_geotiff only forwards it to _read_to_array on the eager non-VRT branch (__init__.py:431). The GPU branch at line 410, the dask branch at line 422, and the VRT branch at line 362 never reference the kwarg, so open_geotiff(p, max_cloud_bytes=8, gpu=True) / open_geotiff(p, max_cloud_bytes=8, chunks=N) / open_geotiff(vrt, max_cloud_bytes=8) all silently drop the budget. Same class of dispatcher-silently-drops-backend-kwarg bug fixed by #1561 / #1605 / #1685 / #1810 for other kwargs; the two sibling kwargs on_gpu_failure (line 339) and missing_sources (line 355) already raise ValueError when used on a path where they do not apply. 11 tests: 4 xfail(strict=True) pinning the fix surface (gpu, dask, vrt, dask+gpu), 3 passing pins on the current silent-drop behaviour so the fix is visible as a diff, 4 positive pins that the eager local + file-like paths accept the kwarg (docstring no-op contract). Filed issue #1974 for the dispatcher fix (sweep is test-only). Cat 4 HIGH (silent backend-kwarg drop). Pass 15 (2026-05-15): added test_write_vrt_bool_nodata_1921.py closing Cat 1 HIGH backend-parity gap on bool nodata rejection. Issue #1911 added the isinstance(nodata, (bool, np.bool_)) -> TypeError guard at to_geotiff and build_geo_tags, but the sibling writers were left unchecked: write_vrt(nodata=True) silently emits <NoDataValue>True</NoDataValue> into the VRT XML (str(True) drops the sentinel because no reader parses 'True' as numeric); write_geotiff_gpu direct call relies on the build_geo_tags defense-in-depth rather than an entry-point check, so a future refactor moving that guard would regress the GPU writer with no test coverage. 17 new tests: 4 xfail (strict=True) pinning the write_vrt fix surface (issue #1921), 1 passing pin on the current buggy str(True) emission so the fix is visible as a diff, 6 numeric/None happy-path tests on write_vrt, 4 GPU writer direct-call bool-reject tests (4 dtypes x 1 call), 1 to_geotiff(gpu=True) dispatcher thread-through. Filed issue #1921 for the write_vrt fix (sweep is test-only). Cat 1 HIGH (write_vrt backend parity bug) + Cat 1 MEDIUM (write_geotiff_gpu defense-in-depth pin). Pass 14 (2026-05-15): added test_dask_streaming_write_degenerate_2026_05_15.py closing Cat 3 HIGH and Cat 2 HIGH/MEDIUM gaps on the dask streaming write path (to_geotiff with dask-backed DataArray, #1084). test_streaming_write.py covered 100x100 with a NaN block plus a 2x2 small raster but had nothing 1-pixel-row, 1-pixel-column, all-NaN, all-Inf, or +/-Inf-mixed. The streaming tile-row segmenter (#1485) on a 1-pixel-tall raster and the streaming nodata-mask coercion on an all-NaN chunk were reachable only with a dask input and had no direct coverage; a regression on either would not surface from the eager numpy path or the write_geotiff_gpu path (pass 5 covered the GPU writer's degenerate shapes). 16 new tests, all passing: 1x1 chunk-matches-shape + nodata-attr round-trip + uint16, 1xN single chunk + chunks-split-columns + wide-segmented-by-buffer (#1485 streaming_buffer_bytes=1 forces the segmenter), Nx1 single chunk + chunks-split-rows, all-NaN with finite sentinel + all-NaN without sentinel, mixed NaN/+Inf/-Inf preserving Inf bit-exact + sentinel masking NaN only, all-+Inf and all--Inf, predictor=3 (float predictor) round-trip on float32 + float64 plus int-dtype ValueError. predictor=3 streaming coverage extends the small-chunk and int-rejection geometry around test_predictor_fp_write_1313.test_predictor3_streaming_dask (which already covers a 128x192 predictor=3 dask streaming write with a Predictor-tag assertion). Cat 3 HIGH (1x1/1xN/Nx1) + Cat 2 HIGH (all-NaN with sentinel) + Cat 2 MEDIUM (mixed-Inf, all-Inf) + Cat 4 MEDIUM (predictor=3 streaming). Pass 13 (2026-05-13): added test_size_param_validation_gpu_vrt_1776.py closing Cat 4 HIGH parameter-coverage gap on size-arg validation. Issue #1752 added tile_size validation to to_geotiff and chunks validation to read_geotiff_dask, but the matching kwargs on three sibling entry points were left unchecked: write_geotiff_gpu(tile_size=) raised ZeroDivisionError for 0, struct.error for -1, TypeError for 256.0; read_geotiff_gpu(chunks=) and read_vrt(chunks=) raised ZeroDivisionError for 0 and silently accepted negative values. Factored two shared validators (_validate_tile_size_arg, _validate_chunks_arg) and called them up front from each entry point. 34 new tests, all passing on GPU host: tile_size matrix on write_geotiff_gpu (0/-1/256.0/True/False/positive/np.int64), chunks matrix on read_geotiff_gpu and read_vrt (0/-1/(0,N)/(N,-1)/wrong-length/bool/non-int/(N,float)/positive/np.int64), dispatcher thread-through tests (open_geotiff(gpu=True, chunks=0), to_geotiff(gpu=True, tile_size=0)). Pre-existing 13 #1752 tests still pass after refactor. Filed issue #1776. Pass 12 (2026-05-12): added test_gpu_writer_overview_mode_and_compression_level_1740.py closing Cat 4 HIGH and Cat 4 MEDIUM parameter-coverage gaps. (1) write_geotiff_gpu(overview_resampling='mode') and the dedicated _block_reduce_2d_gpu mode-fallback branch (_gpu_decode.py:3051-3056) had zero direct tests; six of the seven overview_resampling modes were covered (mean/nearest by test_features, min/max/median by pass 6, cubic by test_signature_parity_1631) but mode was the odd one out -- a regression dropping the mode dispatch from _block_reduce_2d_gpu would fall through to the mean reshape branch and emit wrong overview pixels for integer rasters. (2) write_geotiff_gpu(compression_level=) documented as accepted-but-ignored had no test; the CPU writer rejects out-of-range levels with ValueError, the GPU writer is documented not to -- a regression wiring the GPU writer up to the CPU range validator would silently break every to_geotiff(gpu=True, compression_level=X) caller for in-range levels and noisily for out-of-range. 19 tests, all passing on GPU host: _block_reduce_2d_gpu(method='mode') CPU-parity on 4x4 deterministic + random 8x8 + dtype-preserved across u8/u16/i16/i32, write_geotiff_gpu(cog=True, overview_resampling='mode') end-to-end round trip, to_geotiff(gpu=True, ..., overview_resampling='mode') dispatcher thread-through, GPU-vs-CPU pixel parity on 8x8 input, write_geotiff_gpu(compression_level=) in-range matrix on zstd/deflate, out-of-range matrix (zstd=999/-5, deflate=50/0) accepted without raising + round-trip preserved, to_geotiff(gpu=True, compression_level=999) dispatcher thread-through, companion CPU rejects-OOR pin to lock the asymmetry. Mutation against the mode branch (drop the 'if method == mode' block in _block_reduce_2d_gpu) flipped 9 mode tests red. Filed issue #1740. Pass 11 (2026-05-12): added test_gpu_writer_cpu_fallback_codecs_2026_05_12.py closing a Cat 4 HIGH parameter-coverage gap on write_geotiff_gpu compression= modes for the CPU-fallback codecs (lzw, packbits, lz4, lerc, jpeg2000/j2k). Pass 7 (test_gpu_writer_compression_modes_2026_05_11) covered only none/deflate/zstd/jpeg; the remaining five codecs route through dedicated branches in gpu_compress_tiles (_gpu_decode.py:2974-3019) with CPU fallbacks (lerc_compress, jpeg2000_compress, cpu_compress) that had zero direct tests via write_geotiff_gpu. A regression in routing/tag-wiring/fallback dispatch would ship silently because the internal reader uses the same compression-tag table. 17 tests, all passing on GPU host: lzw/packbits/lz4 round-trip + compression-tag pin on uint16, lerc lossless float32 + uint16 round-trip + tag pin, jpeg2000 uint8 single-band + RGB multi-band lossless round-trip + j2k-alias parity + tag pin, GPU-vs-CPU writer pixel parity for lzw/packbits, to_geotiff(gpu=True, compression=lzw/packbits) dispatcher thread-through. Mutation against compression dispatch (swap lzw bytes to zstd; swap lerc bytes to deflate) flipped round-trip tests red. Filed issue #1706. Pass 10 (2026-05-12): added test_kwarg_behaviour_2026_05_12_v2.py closing two Cat 4 HIGH parameter-coverage gaps. (1) write_geotiff_gpu(predictor=True/2/3) had zero direct tests; the GPU writer threads predictor= through normalize_predictor and gpu_compress_tiles into five CUDA encode kernels (_predictor_encode_kernel_u8/u16/u32/u64 for predictor=2, _fp_predictor_encode_kernel for predictor=3) and a regression dropping the encode-kernel calls would ship corrupt files. (2) read_vrt(window=) had no behaviour tests (only a signature pin in test_signature_annotations_1654); the kwarg is documented and _vrt.read_vrt implements full windowed-read semantics (clip, multi-source overlap, src/dst scaling, GeoTransform origin shift on coords + attrs['transform']). 23 tests, all passing on GPU host: predictor=True/2 round-trips on u8/u16/i32 + 3-band RGB samples_per_pixel stride; predictor=3 lossless round-trip on f32 and f64; predictor=3 int-dtype ValueError (CPU/GPU parity); CPU/GPU pixel-exact parity for pred=2 u16 and pred=3 f32; read_vrt(window=) subregion + full + clamp-overflow + clamp-negative + 2x1 mosaic seam straddle + offset past seam + transform-attr origin shift + y/x coords half-pixel shift + window+band + window+chunks (dask) + window+gpu (cupy) + window+gpu+chunks (dask+cupy). Mutation against the encode dispatch flipped 7 predictor tests red. Filed issue #1690. Pass 9 (2026-05-12): added test_kwarg_behaviour_2026_05_12.py closing three Cat 4 MEDIUM parameter-coverage gaps plus one Cat 4 LOW error path. write_vrt documented kwargs (relative/crs_wkt/nodata) had a smoke-test pinning that the kwargs are accepted but no test verified the override *effect* -- a regression dropping the override branch and silently using the default-from-first-source would ship undetected. read_geotiff_gpu(dtype=) cast had zero direct tests; the eager path has TestDtypeEager and dask has TestDtypeDask but the GPU branch had no equivalent. write_geotiff_gpu(bigtiff=) threads through to _assemble_tiff(force_bigtiff=) but no test asserted the on-disk header byte switches; the CPU writer had it via test_features::test_force_bigtiff_via_public_api. write_vrt(source_files=[]) ValueError was uncovered. 26 tests, all passing on GPU host: write_vrt relative=True/False XML attribute + path inspection + parse-back round-trip, write_vrt crs_wkt= override distinct-from-default XML check, write_vrt nodata= override + default-from-source coverage, write_vrt([]) ValueError + no-file side effect, read_geotiff_gpu dtype= matrix (float64->float32, float64->float16, uint16->int32, uint16->uint8, float-to-int raise, dtype=None preserves native), open_geotiff(gpu=True, dtype=) dispatcher, read_geotiff_gpu(chunks=, dtype=) dask+GPU branch, write_geotiff_gpu bigtiff=True/False/None header verification, to_geotiff(gpu=True, bigtiff=True) dispatcher thread-through. Pass 8 (2026-05-11): added test_lz4_compression_level_2026_05_11.py closing Cat 4 MEDIUM parameter-coverage gap on compression='lz4' + compression_level=. _LEVEL_RANGES advertises lz4: (0, 16) but only deflate (1, 9) and zstd (1, 22) had direct level boundary + round-trip + reject tests. The range check is the gatekeeper -- lz4_compress silently accepts any int level -- so a regression dropping 'lz4' from _LEVEL_RANGES would ship undetected. 18 tests, all passing: round-trip at levels 0/1/9/16 (lossless), default-level no-arg path, higher-level-not-larger smoke check on compressible input, out-of-range reject at -1/-10/17/100 on eager path, valid-range message format pin (lz4 valid: 0-16), dask streaming round-trip at 0/1/8/16, dask streaming out-of-range reject at -1/17/50 (separate _LEVEL_RANGES call site). Pass 7 (2026-05-11): added test_gpu_writer_compression_modes_2026_05_11.py closing Cat 4 HIGH gap on write_geotiff_gpu compression= modes. The writer documents zstd (default, fastest GPU), deflate, jpeg, and none, but only deflate + none had round-trip tests; the default zstd and the jpeg (nvJPEG/Pillow) paths shipped without targeted coverage. 11 new tests, all passing on GPU host: zstd round-trip + default-codec pinning, jpeg round-trip on 3-band RGB uint8 + 1-band greyscale, TIFF compression-tag header check across none/deflate/zstd/jpeg, plain deflate + none round-trips outside the COG/sentinel paths, and a cross-codec lossless parity check (zstd/deflate/none agree pixel-exact). nvJPEG path was exercised live, not just the Pillow fallback. Pass 6 (2026-05-11): added test_overview_resampling_min_max_median_2026_05_11.py covering Cat 4 HIGH parameter-coverage gap on overview_resampling=min/max/median. CPU end-to-end paths were already covered by test_cog_overview_nodata_1613::test_cpu_cog_overview_aggregations_ignore_sentinel; the GPU end-to-end paths and the direct CPU+GPU block-reducer branches had no targeted tests, so a regression on those code paths would ship undetected. 26 tests, all passing on GPU host: block-reducer unit tests (finite + partial-NaN), end-to-end COG writes for both to_geotiff and write_geotiff_gpu, CPU/GPU parity for to_geotiff(gpu=True), CPU nodata-sentinel regression check, and ValueError error-path tests for unknown method names on both backends. Pass 5 (2026-05-11): added test_degenerate_shapes_backends_2026_05_11.py covering Cat 3 HIGH geometric gaps (1x1 / 1xN / Nx1 reads on dask+numpy, GPU, dask+cupy backends; 1x1 / 1xN / Nx1 writes through write_geotiff_gpu) and Cat 2 MEDIUM NaN/Inf gaps (all-NaN read on GPU + dask+cupy, Inf / -Inf reads on all non-eager backends, NaN sentinel mask on dask read path including sentinel block split across chunk boundary). 23 tests, all passing on GPU host. Prior passes still hold: pass 4 (r4) closed read_geotiff_gpu/dask name= + max_pixels= kwargs (Cat 4), pass 3 (r3) closed read_vrt GPU/dask+GPU backend dispatch (Cat 1) and dtype/name kwargs (Cat 4)."
|
|
24
|
+
rasterize,2026-05-21,2255,HIGH,1;2;3,"Pass 2 (2026-05-21): added test_rasterize_coverage_2026_05_21.py with 58 tests, all passing on a CUDA host. Closes Cat 2 HIGH +/-Inf and NaN burn-value gaps that pass-1 left untouched: pin +Inf / -Inf / Inf+(-Inf)/NaN polygon, point, and line burn behaviour across numpy / cupy / dask+numpy / dask+cupy, plus Inf+finite under sum stays Inf, Inf+(-Inf) under sum collapses to NaN, min(Inf, 1.0) and max(-Inf, 1.0) pick the finite value, and Inf-as-bound is rejected with the same ValueError as NaN-as-bound (pass-1 only tested the NaN-bound rejection). Closes Cat 1 MEDIUM nested GeometryCollection on all four backends: a GC inside a GC has no direct test today even though rasterize.py:1995 documents recursive unpacking, and the deeply-nested-3-levels eager test pins the recursion depth limit isn't 1 or 2. Closes Cat 1 MEDIUM columns= (multi-column) parity on cupy and dask+cupy (TestMultiColumn covered numpy/dask+numpy only); pin three columns of props on GPU so the (N, P) loop survives the kernel boundary. Closes Cat 3 LOW rectangular-pixel parity with resolution=(rx, ry) across backends. Filed source-bug issue #2255: GPU max/min merge silently suppresses NaN burn values -- CPU returns NaN (1.0 > NaN is False, keeps NaN); GPU returns 1.0 because the kernel inits the output buffer to -inf for max (or +inf for min) and atomicMax/Min is NaN-suppressing under IEEE device semantics. Pinned both the CPU NaN-propagating behaviour and the GPU NaN-suppressing behaviour as paired tests (test_nan_burn_overlaps_max_cpu_propagates vs test_nan_burn_overlaps_max_gpu_suppresses_nan, plus test_nan_burn_single_geom_max_gpu_returns_neg_inf for the single-write-on-GPU-returns-buffer-init case) so the divergence is visible in CI until the GPU kernels are aligned. Source untouched. Pass 1 (2026-05-17): added test_rasterize_coverage_2026_05_17.py with 34 tests, all passing on a CUDA host. Closes four documented public-API gaps left after the pass-0 audit. (1) Cat 3 HIGH 1x1 single-pixel raster -- test_rasterize.py covers 1xN strips and Nx1 strips but never width=1 AND height=1, so the polygon scanline / line Bresenham / point burn kernels all ship without the single-cell degenerate case; the new TestSinglePixelRaster class pins polygon/point/line on eager numpy plus polygon parity across cupy / dask+numpy / dask+cupy. (2) Cat 4 HIGH like= template-raster parameter is documented at rasterize.py:2038 and implemented by _extract_grid_from_like (line 1930) but no test exercises it; TestLikeParameter pins dtype/bounds/coords inheritance, the three override branches (dtype, bounds, width/height), the three validation branches (not-DataArray, 3D, wrong dim names) and like= on all four backends. Mutation against the like-dtype branch (rasterize.py:2183-2184) flipped the inheritance test red. (3) Cat 4 HIGH resolution= happy path -- only the oversize-rejection error path was tested (line 304); TestResolutionParameter pins the scalar branch, the tuple branch, the ceil-and-clamp-to-1 semantics, and resolution= on all four backends. (4) Cat 4 HIGH non-empty GeometryCollection unpacking is documented at rasterize.py:1995 and implemented by _classify_geometries_loop (line 228) but only the empty-GC case was tested (line 269); TestGeometryCollection pins polygon+point and polygon+line+point collections on eager numpy plus parity across cupy / dask+numpy / dask+cupy so the loop classifier's polygon/line/point sub-bucketing has direct coverage. Cat 1 MEDIUM gap closed: eager cupy all_touched=True parity vs eager numpy (TestEagerCupyAllTouched) -- the existing test only covered dask+cupy all_touched, leaving the direct GPU all_touched kernel untested. Cat 2 MEDIUM gap closed: int32 dtype with default NaN fill silently casts to the int32-min sentinel (TestIntegerDtypeNanFill) -- pin the cast so any future ValueError-raises switch is visible as a code-review diff. Pre-existing 143 passing + 2 skipped tests in test_rasterize.py untouched."
|
|
25
|
+
reproject,2026-05-27,,MEDIUM,1,"Pass 2 (2026-05-27): added test_reproject_coverage_2026_05_27.py with 10 tests, all passing on a CUDA host. Closes Cat 1 MEDIUM backend-coverage gaps left after pass 1: (a) bounds_policy=#2187 had numpy + dask+numpy coverage but no cupy / dask+cupy tests -- a regression dropping the kwarg from the GPU dispatchers would ship undetected; TestBoundsPolicyCupy and TestBoundsPolicyDaskCupy pin raw/clamp/bogus on both GPU backends and assert clamp-grid parity with numpy. (b) test_reproject_handles_inf_input only covered eager numpy; the dask, cupy, and dask+cupy chunk workers each ship their own bilinear/cubic resampler so a regression raising on +/-Inf in any one backend would not surface from the existing test. Four new tests close the matrix (dask+numpy, cupy, dask+cupy with scattered +/-Inf cells; cupy with all-Inf raster checking no spurious finite cells appear). Note carried forward from pass 1: _merge_arrays_cupy is imported but unused -- no cupy merge dispatch in merge(); feature gap not test gap. Added 39 tests: LiteCRS direct coverage, itrf_transform behaviour/roundtrip/array, itrf_frames, geoid_height numerical correctness + raster happy-path, vertical helpers (ellipsoidal<->orthometric/depth), reproject() lat/lon and latitude/longitude dim propagation. Note: _merge_arrays_cupy is imported but unused (no cupy merge dispatch in merge()); flagged as feature gap not test gap."
|
|
26
|
+
reproject,2026-05-10,,HIGH,1;4;5,"Added 39 tests: LiteCRS direct coverage, itrf_transform behaviour/roundtrip/array, itrf_frames, geoid_height numerical correctness + raster happy-path, vertical helpers (ellipsoidal<->orthometric/depth), reproject() lat/lon and latitude/longitude dim propagation. Note: _merge_arrays_cupy is imported but unused (no cupy merge dispatch in merge()); flagged as feature gap not test gap."
|
|
27
|
+
zonal,2026-05-27,,HIGH,1;3;4;5,"Pass 1 (2026-05-27): added test_zonal_backend_coverage_2026_05_27.py with 32 tests, all passing on a CUDA host. Closes Cat 1 HIGH backend-coverage gaps: crosstab cupy + dask+cupy (_crosstab_cupy / _crosstab_dask_cupy were dispatched but never invoked by tests), regions cupy + dask+cupy (_regions_cupy via cupyx.scipy.ndimage + _regions_dask_cupy), trim dask+numpy + cupy + dask+cupy (_trim_bounds_dask isnan path and cupy data.get() path), crop dask+numpy + cupy + dask+cupy (_crop_bounds_dask + cupy data.get() path), apply 3D cupy + dask+cupy (per-layer kernel launch over the third axis in _apply_cupy and _apply_dask_cupy). Existing test_zonal.py covered only numpy + dask+numpy for crosstab/regions/trim/crop and 2D-only for cupy apply. Closes Cat 3 MEDIUM 1x1 / 1xN / Nx1 strip edge cases for trim, crop, and regions. Closes Cat 4 LOW pins: regions(neighborhood=6) ValueError, suggest_zonal_canvas(crs='Geographic') aspect-ratio pin and invalid-crs KeyError, crosstab cupy zone_ids/cat_ids filter, crosstab cupy agg='percentage'. Closes Cat 5 MEDIUM: regions coords/attrs propagation across numpy + dask+numpy, trim/crop name='trim'/'crop' default + attrs preservation. Also pins the documented numpy-vs-dask trim asymmetry on NaN sentinel (numpy _trim does equality which never matches NaN; dask _trim_bounds_dask has dedicated isnan branch). Mutation against the cupy.asnumpy() conversion in _crosstab_cupy flipped test_crosstab_cupy_matches_numpy red. Source untouched."
|
|
28
|
+
rasterize,2026-05-29,2614,MEDIUM,4,"Pass 4 (2026-05-29): added test_rasterize_coverage_2026_05_29.py with 11 tests, all passing (pure-Python validation paths, no CUDA needed); filed issue #2614 and opened a test-only PR. Closes Cat 4 MEDIUM error-path gaps that all three prior passes left untouched. (1) Partial width/height: the (width is None) != (height is None) guard in rasterize() raises ValueError naming the given and missing dimension, documented in the docstring, but neither the width-only nor height-only branch had a test; pin both directions plus the width-only+resolution case proving the guard fires before the resolution branch. (2) resolution= input type/shape validation: the type/shape branches (non-number/non-sequence string|dict; wrong-ndim numpy array; wrong-length sequence len 1|3|4; non-numeric elements) had no coverage -- test_rasterize.py's test_invalid_resolution_scalar/tuple only exercise non-finite/non-positive VALUES, not these type/shape guards, so a regression loosening or reordering them would ship silently; pin each branch to its message plus a positive control that a 1-D length-2 numpy array is still accepted. Source untouched."
|
|
29
|
+
Pass 17 (2026-05-18): added test_mask_nodata_gpu_vrt_2052.py closing Cat 1 HIGH backend-coverage gap on the mask_nodata= opt-out kwarg (#2052). The kwarg was added in #2052 and wired through the four public readers (open_geotiff, read_geotiff_gpu, read_geotiff_dask, read_vrt), but test_mask_nodata_kwarg_2052.py only exercised the eager-numpy and dask+numpy branches. The pure-GPU mask gating at _backends/gpu.py:709, the dask+GPU dispatcher forwarding at _backends/gpu.py:991, the eager VRT mask gating at _backends/vrt.py:320, and the chunked VRT graph builder at _backends/vrt.py:408/588 had zero direct coverage. 19 new tests, all passing on GPU host: GPU eager + dask+GPU mask_nodata=False preserves uint16, GPU defaults still promote to float64, dispatcher thread-through for open_geotiff(gpu=True, mask_nodata=False) and open_geotiff(gpu=True, chunks=N, mask_nodata=False), VRT eager and chunked branches mirror, cross-backend parity (eager vs dask, eager vs GPU, eager vs dask+GPU, eager vs VRT) bit-exact under mask_nodata=False, direct read_geotiff_dask entry-point coverage. Fixture uses tiled+deflate compression so the pure nvCOMP decode path is exercised, not the CPU-fallback piggyback path. Mutation against gpu.py:709 (force mask_nodata=True) flipped 4 GPU tests red; mutation against vrt.py eager mask gate flipped 4 VRT tests red. Cat 1 HIGH (backend coverage on mask_nodata=False for GPU, dask+GPU, VRT eager, VRT chunked). Pass 16 (2026-05-15): added test_max_cloud_bytes_dispatcher_silent_drop_2026_05_15.py closing Cat 4 HIGH parameter-coverage gap on the open_geotiff dispatcher's max_cloud_bytes kwarg. The kwarg was added in #1928 (eager fsspec budget) and re-ordered into the canonical reader signature by #1957, but open_geotiff only forwards it to _read_to_array on the eager non-VRT branch (__init__.py:431). The GPU branch at line 410, the dask branch at line 422, and the VRT branch at line 362 never reference the kwarg, so open_geotiff(p, max_cloud_bytes=8, gpu=True) / open_geotiff(p, max_cloud_bytes=8, chunks=N) / open_geotiff(vrt, max_cloud_bytes=8) all silently drop the budget. Same class of dispatcher-silently-drops-backend-kwarg bug fixed by #1561 / #1605 / #1685 / #1810 for other kwargs; the two sibling kwargs on_gpu_failure (line 339) and missing_sources (line 355) already raise ValueError when used on a path where they do not apply. 11 tests: 4 xfail(strict=True) pinning the fix surface (gpu, dask, vrt, dask+gpu), 3 passing pins on the current silent-drop behaviour so the fix is visible as a diff, 4 positive pins that the eager local + file-like paths accept the kwarg (docstring no-op contract). Filed issue #1974 for the dispatcher fix (sweep is test-only). Cat 4 HIGH (silent backend-kwarg drop). Pass 15 (2026-05-15): added test_write_vrt_bool_nodata_1921.py closing Cat 1 HIGH backend-parity gap on bool nodata rejection. Issue #1911 added the isinstance(nodata, (bool, np.bool_)) -> TypeError guard at to_geotiff and build_geo_tags, but the sibling writers were left unchecked: write_vrt(nodata=True) silently emits <NoDataValue>True</NoDataValue> into the VRT XML (str(True) drops the sentinel because no reader parses 'True' as numeric); write_geotiff_gpu direct call relies on the build_geo_tags defense-in-depth rather than an entry-point check, so a future refactor moving that guard would regress the GPU writer with no test coverage. 17 new tests: 4 xfail (strict=True) pinning the write_vrt fix surface (issue #1921), 1 passing pin on the current buggy str(True) emission so the fix is visible as a diff, 6 numeric/None happy-path tests on write_vrt, 4 GPU writer direct-call bool-reject tests (4 dtypes x 1 call), 1 to_geotiff(gpu=True) dispatcher thread-through. Filed issue #1921 for the write_vrt fix (sweep is test-only). Cat 1 HIGH (write_vrt backend parity bug) + Cat 1 MEDIUM (write_geotiff_gpu defense-in-depth pin). Pass 14 (2026-05-15): added test_dask_streaming_write_degenerate_2026_05_15.py closing Cat 3 HIGH and Cat 2 HIGH/MEDIUM gaps on the dask streaming write path (to_geotiff with dask-backed DataArray, #1084). test_streaming_write.py covered 100x100 with a NaN block plus a 2x2 small raster but had nothing 1-pixel-row, 1-pixel-column, all-NaN, all-Inf, or +/-Inf-mixed. The streaming tile-row segmenter (#1485) on a 1-pixel-tall raster and the streaming nodata-mask coercion on an all-NaN chunk were reachable only with a dask input and had no direct coverage; a regression on either would not surface from the eager numpy path or the write_geotiff_gpu path (pass 5 covered the GPU writer's degenerate shapes). 16 new tests, all passing: 1x1 chunk-matches-shape + nodata-attr round-trip + uint16, 1xN single chunk + chunks-split-columns + wide-segmented-by-buffer (#1485 streaming_buffer_bytes=1 forces the segmenter), Nx1 single chunk + chunks-split-rows, all-NaN with finite sentinel + all-NaN without sentinel, mixed NaN/+Inf/-Inf preserving Inf bit-exact + sentinel masking NaN only, all-+Inf and all--Inf, predictor=3 (float predictor) round-trip on float32 + float64 plus int-dtype ValueError. predictor=3 streaming coverage extends the small-chunk and int-rejection geometry around test_predictor_fp_write_1313.test_predictor3_streaming_dask (which already covers a 128x192 predictor=3 dask streaming write with a Predictor-tag assertion). Cat 3 HIGH (1x1/1xN/Nx1) + Cat 2 HIGH (all-NaN with sentinel) + Cat 2 MEDIUM (mixed-Inf, all-Inf) + Cat 4 MEDIUM (predictor=3 streaming). Pass 13 (2026-05-13): added test_size_param_validation_gpu_vrt_1776.py closing Cat 4 HIGH parameter-coverage gap on size-arg validation. Issue #1752 added tile_size validation to to_geotiff and chunks validation to read_geotiff_dask, but the matching kwargs on three sibling entry points were left unchecked: write_geotiff_gpu(tile_size=) raised ZeroDivisionError for 0, struct.error for -1, TypeError for 256.0; read_geotiff_gpu(chunks=) and read_vrt(chunks=) raised ZeroDivisionError for 0 and silently accepted negative values. Factored two shared validators (_validate_tile_size_arg, _validate_chunks_arg) and called them up front from each entry point. 34 new tests, all passing on GPU host: tile_size matrix on write_geotiff_gpu (0/-1/256.0/True/False/positive/np.int64), chunks matrix on read_geotiff_gpu and read_vrt (0/-1/(0,N)/(N,-1)/wrong-length/bool/non-int/(N,float)/positive/np.int64), dispatcher thread-through tests (open_geotiff(gpu=True, chunks=0), to_geotiff(gpu=True, tile_size=0)). Pre-existing 13 #1752 tests still pass after refactor. Filed issue #1776. Pass 12 (2026-05-12): added test_gpu_writer_overview_mode_and_compression_level_1740.py closing Cat 4 HIGH and Cat 4 MEDIUM parameter-coverage gaps. (1) write_geotiff_gpu(overview_resampling='mode') and the dedicated _block_reduce_2d_gpu mode-fallback branch (_gpu_decode.py:3051-3056) had zero direct tests; six of the seven overview_resampling modes were covered (mean/nearest by test_features, min/max/median by pass 6, cubic by test_signature_parity_1631) but mode was the odd one out -- a regression dropping the mode dispatch from _block_reduce_2d_gpu would fall through to the mean reshape branch and emit wrong overview pixels for integer rasters. (2) write_geotiff_gpu(compression_level=) documented as accepted-but-ignored had no test; the CPU writer rejects out-of-range levels with ValueError, the GPU writer is documented not to -- a regression wiring the GPU writer up to the CPU range validator would silently break every to_geotiff(gpu=True, compression_level=X) caller for in-range levels and noisily for out-of-range. 19 tests, all passing on GPU host: _block_reduce_2d_gpu(method='mode') CPU-parity on 4x4 deterministic + random 8x8 + dtype-preserved across u8/u16/i16/i32, write_geotiff_gpu(cog=True, overview_resampling='mode') end-to-end round trip, to_geotiff(gpu=True, ..., overview_resampling='mode') dispatcher thread-through, GPU-vs-CPU pixel parity on 8x8 input, write_geotiff_gpu(compression_level=) in-range matrix on zstd/deflate, out-of-range matrix (zstd=999/-5, deflate=50/0) accepted without raising + round-trip preserved, to_geotiff(gpu=True, compression_level=999) dispatcher thread-through, companion CPU rejects-OOR pin to lock the asymmetry. Mutation against the mode branch (drop the 'if method == mode' block in _block_reduce_2d_gpu) flipped 9 mode tests red. Filed issue #1740. Pass 11 (2026-05-12): added test_gpu_writer_cpu_fallback_codecs_2026_05_12.py closing a Cat 4 HIGH parameter-coverage gap on write_geotiff_gpu compression= modes for the CPU-fallback codecs (lzw, packbits, lz4, lerc, jpeg2000/j2k). Pass 7 (test_gpu_writer_compression_modes_2026_05_11) covered only none/deflate/zstd/jpeg; the remaining five codecs route through dedicated branches in gpu_compress_tiles (_gpu_decode.py:2974-3019) with CPU fallbacks (lerc_compress, jpeg2000_compress, cpu_compress) that had zero direct tests via write_geotiff_gpu. A regression in routing/tag-wiring/fallback dispatch would ship silently because the internal reader uses the same compression-tag table. 17 tests, all passing on GPU host: lzw/packbits/lz4 round-trip + compression-tag pin on uint16, lerc lossless float32 + uint16 round-trip + tag pin, jpeg2000 uint8 single-band + RGB multi-band lossless round-trip + j2k-alias parity + tag pin, GPU-vs-CPU writer pixel parity for lzw/packbits, to_geotiff(gpu=True, compression=lzw/packbits) dispatcher thread-through. Mutation against compression dispatch (swap lzw bytes to zstd; swap lerc bytes to deflate) flipped round-trip tests red. Filed issue #1706. Pass 10 (2026-05-12): added test_kwarg_behaviour_2026_05_12_v2.py closing two Cat 4 HIGH parameter-coverage gaps. (1) write_geotiff_gpu(predictor=True/2/3) had zero direct tests; the GPU writer threads predictor= through normalize_predictor and gpu_compress_tiles into five CUDA encode kernels (_predictor_encode_kernel_u8/u16/u32/u64 for predictor=2, _fp_predictor_encode_kernel for predictor=3) and a regression dropping the encode-kernel calls would ship corrupt files. (2) read_vrt(window=) had no behaviour tests (only a signature pin in test_signature_annotations_1654); the kwarg is documented and _vrt.read_vrt implements full windowed-read semantics (clip, multi-source overlap, src/dst scaling, GeoTransform origin shift on coords + attrs['transform']). 23 tests, all passing on GPU host: predictor=True/2 round-trips on u8/u16/i32 + 3-band RGB samples_per_pixel stride; predictor=3 lossless round-trip on f32 and f64; predictor=3 int-dtype ValueError (CPU/GPU parity); CPU/GPU pixel-exact parity for pred=2 u16 and pred=3 f32; read_vrt(window=) subregion + full + clamp-overflow + clamp-negative + 2x1 mosaic seam straddle + offset past seam + transform-attr origin shift + y/x coords half-pixel shift + window+band + window+chunks (dask) + window+gpu (cupy) + window+gpu+chunks (dask+cupy). Mutation against the encode dispatch flipped 7 predictor tests red. Filed issue #1690. Pass 9 (2026-05-12): added test_kwarg_behaviour_2026_05_12.py closing three Cat 4 MEDIUM parameter-coverage gaps plus one Cat 4 LOW error path. write_vrt documented kwargs (relative/crs_wkt/nodata) had a smoke-test pinning that the kwargs are accepted but no test verified the override *effect* -- a regression dropping the override branch and silently using the default-from-first-source would ship undetected. read_geotiff_gpu(dtype=) cast had zero direct tests; the eager path has TestDtypeEager and dask has TestDtypeDask but the GPU branch had no equivalent. write_geotiff_gpu(bigtiff=) threads through to _assemble_tiff(force_bigtiff=) but no test asserted the on-disk header byte switches; the CPU writer had it via test_features::test_force_bigtiff_via_public_api. write_vrt(source_files=[]) ValueError was uncovered. 26 tests, all passing on GPU host: write_vrt relative=True/False XML attribute + path inspection + parse-back round-trip, write_vrt crs_wkt= override distinct-from-default XML check, write_vrt nodata= override + default-from-source coverage, write_vrt([]) ValueError + no-file side effect, read_geotiff_gpu dtype= matrix (float64->float32, float64->float16, uint16->int32, uint16->uint8, float-to-int raise, dtype=None preserves native), open_geotiff(gpu=True, dtype=) dispatcher, read_geotiff_gpu(chunks=, dtype=) dask+GPU branch, write_geotiff_gpu bigtiff=True/False/None header verification, to_geotiff(gpu=True, bigtiff=True) dispatcher thread-through. Pass 8 (2026-05-11): added test_lz4_compression_level_2026_05_11.py closing Cat 4 MEDIUM parameter-coverage gap on compression='lz4' + compression_level=. _LEVEL_RANGES advertises lz4: (0, 16) but only deflate (1, 9) and zstd (1, 22) had direct level boundary + round-trip + reject tests. The range check is the gatekeeper -- lz4_compress silently accepts any int level -- so a regression dropping 'lz4' from _LEVEL_RANGES would ship undetected. 18 tests, all passing: round-trip at levels 0/1/9/16 (lossless), default-level no-arg path, higher-level-not-larger smoke check on compressible input, out-of-range reject at -1/-10/17/100 on eager path, valid-range message format pin (lz4 valid: 0-16), dask streaming round-trip at 0/1/8/16, dask streaming out-of-range reject at -1/17/50 (separate _LEVEL_RANGES call site). Pass 7 (2026-05-11): added test_gpu_writer_compression_modes_2026_05_11.py closing Cat 4 HIGH gap on write_geotiff_gpu compression= modes. The writer documents zstd (default, fastest GPU), deflate, jpeg, and none, but only deflate + none had round-trip tests; the default zstd and the jpeg (nvJPEG/Pillow) paths shipped without targeted coverage. 11 new tests, all passing on GPU host: zstd round-trip + default-codec pinning, jpeg round-trip on 3-band RGB uint8 + 1-band greyscale, TIFF compression-tag header check across none/deflate/zstd/jpeg, plain deflate + none round-trips outside the COG/sentinel paths, and a cross-codec lossless parity check (zstd/deflate/none agree pixel-exact). nvJPEG path was exercised live, not just the Pillow fallback. Pass 6 (2026-05-11): added test_overview_resampling_min_max_median_2026_05_11.py covering Cat 4 HIGH parameter-coverage gap on overview_resampling=min/max/median. CPU end-to-end paths were already covered by test_cog_overview_nodata_1613::test_cpu_cog_overview_aggregations_ignore_sentinel; the GPU end-to-end paths and the direct CPU+GPU block-reducer branches had no targeted tests, so a regression on those code paths would ship undetected. 26 tests, all passing on GPU host: block-reducer unit tests (finite + partial-NaN), end-to-end COG writes for both to_geotiff and write_geotiff_gpu, CPU/GPU parity for to_geotiff(gpu=True), CPU nodata-sentinel regression check, and ValueError error-path tests for unknown method names on both backends. Pass 5 (2026-05-11): added test_degenerate_shapes_backends_2026_05_11.py covering Cat 3 HIGH geometric gaps (1x1 / 1xN / Nx1 reads on dask+numpy, GPU, dask+cupy backends; 1x1 / 1xN / Nx1 writes through write_geotiff_gpu) and Cat 2 MEDIUM NaN/Inf gaps (all-NaN read on GPU + dask+cupy, Inf / -Inf reads on all non-eager backends, NaN sentinel mask on dask read path including sentinel block split across chunk boundary). 23 tests, all passing on GPU host. Prior passes still hold: pass 4 (r4) closed read_geotiff_gpu/dask name= + max_pixels= kwargs (Cat 4), pass 3 (r3) closed read_vrt GPU/dask+GPU backend dispatch (Cat 1) and dtype/name kwargs (Cat 4)."
|
|
30
|
+
rasterize,2026-05-21,2255,HIGH,1;2;3,"Pass 2 (2026-05-21): added test_rasterize_coverage_2026_05_21.py with 58 tests, all passing on a CUDA host. Closes Cat 2 HIGH +/-Inf and NaN burn-value gaps that pass-1 left untouched: pin +Inf / -Inf / Inf+(-Inf)/NaN polygon, point, and line burn behaviour across numpy / cupy / dask+numpy / dask+cupy, plus Inf+finite under sum stays Inf, Inf+(-Inf) under sum collapses to NaN, min(Inf, 1.0) and max(-Inf, 1.0) pick the finite value, and Inf-as-bound is rejected with the same ValueError as NaN-as-bound (pass-1 only tested the NaN-bound rejection). Closes Cat 1 MEDIUM nested GeometryCollection on all four backends: a GC inside a GC has no direct test today even though rasterize.py:1995 documents recursive unpacking, and the deeply-nested-3-levels eager test pins the recursion depth limit isn't 1 or 2. Closes Cat 1 MEDIUM columns= (multi-column) parity on cupy and dask+cupy (TestMultiColumn covered numpy/dask+numpy only); pin three columns of props on GPU so the (N, P) loop survives the kernel boundary. Closes Cat 3 LOW rectangular-pixel parity with resolution=(rx, ry) across backends. Filed source-bug issue #2255: GPU max/min merge silently suppresses NaN burn values -- CPU returns NaN (1.0 > NaN is False, keeps NaN); GPU returns 1.0 because the kernel inits the output buffer to -inf for max (or +inf for min) and atomicMax/Min is NaN-suppressing under IEEE device semantics. Pinned both the CPU NaN-propagating behaviour and the GPU NaN-suppressing behaviour as paired tests (test_nan_burn_overlaps_max_cpu_propagates vs test_nan_burn_overlaps_max_gpu_suppresses_nan, plus test_nan_burn_single_geom_max_gpu_returns_neg_inf for the single-write-on-GPU-returns-buffer-init case) so the divergence is visible in CI until the GPU kernels are aligned. Source untouched. Pass 1 (2026-05-17): added test_rasterize_coverage_2026_05_17.py with 34 tests, all passing on a CUDA host. Closes four documented public-API gaps left after the pass-0 audit. (1) Cat 3 HIGH 1x1 single-pixel raster -- test_rasterize.py covers 1xN strips and Nx1 strips but never width=1 AND height=1, so the polygon scanline / line Bresenham / point burn kernels all ship without the single-cell degenerate case; the new TestSinglePixelRaster class pins polygon/point/line on eager numpy plus polygon parity across cupy / dask+numpy / dask+cupy. (2) Cat 4 HIGH like= template-raster parameter is documented at rasterize.py:2038 and implemented by _extract_grid_from_like (line 1930) but no test exercises it; TestLikeParameter pins dtype/bounds/coords inheritance, the three override branches (dtype, bounds, width/height), the three validation branches (not-DataArray, 3D, wrong dim names) and like= on all four backends. Mutation against the like-dtype branch (rasterize.py:2183-2184) flipped the inheritance test red. (3) Cat 4 HIGH resolution= happy path -- only the oversize-rejection error path was tested (line 304); TestResolutionParameter pins the scalar branch, the tuple branch, the ceil-and-clamp-to-1 semantics, and resolution= on all four backends. (4) Cat 4 HIGH non-empty GeometryCollection unpacking is documented at rasterize.py:1995 and implemented by _classify_geometries_loop (line 228) but only the empty-GC case was tested (line 269); TestGeometryCollection pins polygon+point and polygon+line+point collections on eager numpy plus parity across cupy / dask+numpy / dask+cupy so the loop classifier's polygon/line/point sub-bucketing has direct coverage. Cat 1 MEDIUM gap closed: eager cupy all_touched=True parity vs eager numpy (TestEagerCupyAllTouched) -- the existing test only covered dask+cupy all_touched, leaving the direct GPU all_touched kernel untested. Cat 2 MEDIUM gap closed: int32 dtype with default NaN fill silently casts to the int32-min sentinel (TestIntegerDtypeNanFill) -- pin the cast so any future ValueError-raises switch is visible as a code-review diff. Pre-existing 143 passing + 2 skipped tests in test_rasterize.py untouched."
|
|
31
|
+
reproject,2026-05-27,,MEDIUM,1,"Pass 2 (2026-05-27): added test_reproject_coverage_2026_05_27.py with 10 tests, all passing on a CUDA host. Closes Cat 1 MEDIUM backend-coverage gaps left after pass 1: (a) bounds_policy=#2187 had numpy + dask+numpy coverage but no cupy / dask+cupy tests -- a regression dropping the kwarg from the GPU dispatchers would ship undetected; TestBoundsPolicyCupy and TestBoundsPolicyDaskCupy pin raw/clamp/bogus on both GPU backends and assert clamp-grid parity with numpy. (b) test_reproject_handles_inf_input only covered eager numpy; the dask, cupy, and dask+cupy chunk workers each ship their own bilinear/cubic resampler so a regression raising on +/-Inf in any one backend would not surface from the existing test. Four new tests close the matrix (dask+numpy, cupy, dask+cupy with scattered +/-Inf cells; cupy with all-Inf raster checking no spurious finite cells appear). Note carried forward from pass 1: _merge_arrays_cupy is imported but unused -- no cupy merge dispatch in merge(); feature gap not test gap. Added 39 tests: LiteCRS direct coverage, itrf_transform behaviour/roundtrip/array, itrf_frames, geoid_height numerical correctness + raster happy-path, vertical helpers (ellipsoidal<->orthometric/depth), reproject() lat/lon and latitude/longitude dim propagation. Note: _merge_arrays_cupy is imported but unused (no cupy merge dispatch in merge()); flagged as feature gap not test gap."
|
|
32
|
+
reproject,2026-05-10,,HIGH,1;4;5,"Added 39 tests: LiteCRS direct coverage, itrf_transform behaviour/roundtrip/array, itrf_frames, geoid_height numerical correctness + raster happy-path, vertical helpers (ellipsoidal<->orthometric/depth), reproject() lat/lon and latitude/longitude dim propagation. Note: _merge_arrays_cupy is imported but unused (no cupy merge dispatch in merge()); flagged as feature gap not test gap."
|
|
33
|
+
resample,2026-05-27,2547,HIGH,2;3;5,"Pass 1 (2026-05-27): added test_resample_coverage_2026_05_27.py with 70 tests (68 passing, 2 skipped). Closes Cat 3 HIGH Nx1 single-column gap across numpy/cupy/dask+numpy/dask+cupy x 8 methods (nearest/bilinear/cubic/average/min/max/median/mode) plus Nx1 upsample-nearest parity and Nx1 cross-backend aggregate parity. Closes Cat 2 MEDIUM NaN-parity gap on cupy and dask+cupy (existing TestCuPyParity/TestDaskCuPyParity used random data without NaN; the weight-mask gate and spline-prepad had no GPU NaN coverage). Closes Cat 3 MEDIUM all-equal-value raster across 8 methods (downsample) and 3 interp methods (upsample) plus a constant-with-NaN aggregate variant. Closes Cat 5 MEDIUM non-default dim-name propagation: lat/lon, latitude/longitude, and (channel, lat, lon) 3D round-trip without being renamed to y/x; per-dim attrs (units) preserved. Closes Cat 3 MEDIUM empty-raster behaviour pin: 0-row and 0-col rasters raise (currently IndexError) -- contract covered. Filed source-bug issue #2547: cubic on dask backends fails for Nx1 / arrays smaller than depth=16; the 2 skipped tests in this file gate on that fix landing. Source untouched."
|
|
34
|
+
zonal,2026-05-29,2619,MEDIUM,1,"Pass 2 (2026-05-29): one Cat 1 MEDIUM backend-coverage gap remained after pass 1 -- 3D crosstab on cupy / dask+cupy. The 3D GPU paths (_crosstab_cupy / _crosstab_dask_cupy with a 3D categorical values array, layer=, agg='count') were reachable and correct but untested; the existing 3D crosstab tests (test_crosstab_3d_count, test_crosstab_3d_agg_method, test_nodata_values_crosstab_3d) only parametrize numpy / dask+numpy. Added 3 parity tests to test_zonal_backend_coverage_2026_05_27.py (test_crosstab_3d_count_cupy_matches_numpy, test_crosstab_3d_count_dask_cupy_matches_numpy, test_crosstab_3d_nodata_cupy_matches_numpy) asserting cupy and dask+cupy results match numpy for agg='count' including a nodata_values case. All passed live on a CUDA host. Issue #2619, PR #2625. Test-only, no source change. Remaining LOW (documented, not fixed): get_full_extent has no direct unit test (exercised indirectly via suggest_zonal_canvas); non-square cellsize handling not exercised. Pass 1 (2026-05-27): added test_zonal_backend_coverage_2026_05_27.py with 32 tests, all passing on a CUDA host. Closes Cat 1 HIGH backend-coverage gaps: crosstab cupy + dask+cupy (_crosstab_cupy / _crosstab_dask_cupy were dispatched but never invoked by tests), regions cupy + dask+cupy (_regions_cupy via cupyx.scipy.ndimage + _regions_dask_cupy), trim dask+numpy + cupy + dask+cupy (_trim_bounds_dask isnan path and cupy data.get() path), crop dask+numpy + cupy + dask+cupy (_crop_bounds_dask + cupy data.get() path), apply 3D cupy + dask+cupy (per-layer kernel launch over the third axis in _apply_cupy and _apply_dask_cupy). Existing test_zonal.py covered only numpy + dask+numpy for crosstab/regions/trim/crop and 2D-only for cupy apply. Closes Cat 3 MEDIUM 1x1 / 1xN / Nx1 strip edge cases for trim, crop, and regions. Closes Cat 4 LOW pins: regions(neighborhood=6) ValueError, suggest_zonal_canvas(crs='Geographic') aspect-ratio pin and invalid-crs KeyError, crosstab cupy zone_ids/cat_ids filter, crosstab cupy agg='percentage'. Closes Cat 5 MEDIUM: regions coords/attrs propagation across numpy + dask+numpy, trim/crop name='trim'/'crop' default + attrs preservation. Also pins the documented numpy-vs-dask trim asymmetry on NaN sentinel (numpy _trim does equality which never matches NaN; dask _trim_bounds_dask has dedicated isnan branch). Mutation against the cupy.asnumpy() conversion in _crosstab_cupy flipped test_crosstab_cupy_matches_numpy red. Source untouched."
|
|
35
|
+
reproject,2026-05-29,2618,HIGH,3,"Pass 2026-05-29: reproject already has a deep suite (369 tests in test_reproject.py + coverage/gate files) covering all 4 backends, NaN/Inf/all-NaN/all-Inf, 1x1/2x2, metadata, vertical shift, bounds_policy x backends, integer nodata x backends. Gaps found: Cat 3 HIGH single-row (1xN) and single-col (Nx1) strip rasters never tested (hit size<2 branch of _validate_regular_axis + degenerate resampling axis); Cat 3 MEDIUM constant-value/zero-gradient raster never reprojected. Added TestDegenerateShapeReproject (12 tests): 1xN+Nx1 strips x numpy/dask/cupy/dask+cupy, constant raster numpy value-preservation + cross-backend parity. All 12 executed and passed on a CUDA host. Test-only, no source change (#2618). LOW (documented only): _merge._merge_arrays_cupy imported but never called by merge() (host-bounces via _merge_arrays_numpy) - dead-code source observation not a test gap; non-square cellsize reproject only covered via resolution-tuple validation errors not a successful anisotropic run."
|
|
36
|
+
viewshed,2026-05-29,2693,HIGH,1;2;5,"Pass 1 (2026-05-29): added 4 new test groups to test_viewshed.py (13 new tests + 1 xfail, all passing/xfailing on a CUDA+RTX host). Closes Cat 1 HIGH backend-coverage gap: the dask+cupy dispatch path in _viewshed_dask (Tier B) and _viewshed_windowed (max_distance) was registered but never invoked by any test -- added test_viewshed_dask_cupy_flat (analytical-angle parity, atol 0.03) and test_viewshed_dask_cupy_max_distance (windowed GPU run; observer cell 180, corners INVISIBLE). Both use non-zero flat terrain (1.3) because the RTX mesh builder rejects an all-zero raster (#1378). Closes Cat 5 HIGH metadata-preservation gap: only the numpy test_viewshed called general_output_checks; the cupy/dask/dask+cupy and max_distance paths never asserted attrs/coords/dims/array-type preservation. Added parametrised test_viewshed_metadata_preserved over {numpy,cupy,dask+numpy,dask+cupy} x {full, max_distance=2.0}: asserts attrs==, dims==, shape==, x/y coords allclose; runs general_output_checks (full type parity) for all backends except dask+cupy. Closes Cat 2 HIGH NaN-input gap and surfaced source bug #2693: viewshed on a numpy raster crashes with ValueError 'node not found' from _delete_from_tree when a NaN cell sits at certain positions (e.g. (2,4) in a 5x5 with observer at (2,2)), while NaN at (1,1)/(0,0)/(4,4) runs fine. Added test_viewshed_nan_input_supported_positions (parametrised working positions, asserts observer=180 and NaN cell is INVISIBLE/NaN) plus test_viewshed_nan_input_crashing_position (xfail strict, raises, links #2693). Noted but NOT fixed (source change out of scope for test sweep): the dask+cupy backend does not preserve the cupy backing -- _viewshed_dask computes then rewraps via da.from_array(result_np), so the output computes to numpy not cupy; general_output_checks is skipped for dask+cupy for that reason (candidate for the metadata/backend-parity sweep). LOW (documented only): non-square cell sizes; 1x1 and 1xN geometry covered behaviourally by probing (run without error). Test-only PR; viewshed.py untouched."
|
|
37
|
+
polygonize,2026-05-29,2623,MEDIUM,4,"Pass 3 (2026-05-29): added test_polygonize_mask_dtype_coverage_2026_05_29.py (41 passed, 8 xfailed on a CUDA host). Closes Cat 4 MEDIUM parameter-coverage gap: mask= is documented to accept bool/integer/float values but every prior test passed only a bool mask. Integer masks (int32/int64) now pinned against the same-backend bool-mask output on all four backends x both raster dtypes x connectivity 4/8; float-mask-on-integer-raster also pinned. Each backend is compared to its OWN bool reference to isolate mask-dtype from the unrelated numpy-vs-dask hole-vs-single-ring representation difference. Mutation (drop the not-mask[ij] exclusion in _calculate_regions) flips 11 tests red incl. the pixel-exclusion sanity anchor; clean md5 restore. Surfaced source bug #2623: a float-dtype mask on a float-dtype raster raises TypeError at polygonize.py:918 (mask & nan_mask; bitwise_and undefined for float&bool; cupy/dask route floats through _polygonize_numpy so they crash too; int masks coerce fine). 8 float-mask cases marked xfail(strict, raises=TypeError) referencing #2623. Test-only; source untouched. | Pass 2 (2026-05-27): added test_polygonize_atol_rtol_backend_coverage_2026_05_27.py with 15 tests, all passing on a CUDA host. Closes Cat 4 MEDIUM parameter-coverage gap on atol/rtol forwarding through the cupy and dask+cupy backends. atol/rtol were exposed by #2173 / #2194 and thread through _polygonize_cupy (polygonize.py:808) and _polygonize_dask (polygonize.py:1719); the dask path further plumbs them into dask.delayed(_polygonize_chunk)(...) at lines 1748-1754 and into _bucket_key_for_value for cross-chunk merge bucketing at lines 1757-1758. Pre-existing tests covered non-default atol/rtol only on numpy and dask+numpy. The cupy and dask+cupy dispatchers were untested -- a regression dropping the kwargs there would silently change the float polygon count and would not be caught. Same dispatcher-silently-drops-kwarg pattern fixed by #1561 / #1605 / #1685 / #1810 / #1974 on adjacent GeoTIFF surfaces. 15 tests: cupy strict-equality + default-tolerance pin on _REPRO_2173, dask+cupy strict-equality single-chunk + multi-chunk (engages cross-chunk merge bucket) + default-tolerance multi-chunk pin, cupy intermediate-atol small/large pair, dask+cupy intermediate-atol single/multi-chunk small + single-chunk large, cupy integer atol-ignored matrix, dask+cupy integer atol-ignored single-chunk + multi-chunk, cupy rtol-only large/small matrix. Mutation against _polygonize_cupy float branch (drop atol/rtol kwargs in the _polygonize_numpy forward call at polygonize.py:823-825) flips 3 of 5 cupy tests red; mutation against dask.delayed(_polygonize_chunk)(...) at polygonize.py:1748-1754 (drop atol, rtol args) flips 2 of 6 dask+cupy tests red. Confirmed clean restore via md5sum. Source untouched. Filed issue #2537 (test-only). Cat 4 MEDIUM (parameter coverage on cupy + dask+cupy atol/rtol forwarding). Pass 1 (2026-05-19): added test_polygonize_coverage_2026_05_19.py with 58 tests, all passing on a CUDA host. Closes Cat 3 HIGH 1x1 / Nx1 single-column geometric gaps (Nx1 exercises the nx==1 padding path at polygonize.py:565 and the cupy nx==1 numpy-fallback at polygonize.py:671), Cat 3 MEDIUM 1xN single-row and all-equal-value rasters on all four backends. Closes Cat 2 HIGH NaN parity for cupy + dask+cupy (numpy/dask were already covered by test_polygonize_nan_pixels_excluded*), Cat 2 MEDIUM all-NaN raster on all four backends, Cat 2 HIGH +/-Inf pins on all four backends. Filed source-bug issue #2155: numpy/dask/dask+cupy backends silently absorb Inf cells into adjacent finite polygons because _is_close reduces abs(inf-inf) to nan; cupy backend handles Inf correctly. Pins lock the asymmetric behaviour so the fix is visible. Closes Cat 1 MEDIUM simplify_tolerance + mask= parity gaps on dask+cupy backend (numpy/cupy/dask were already covered). Closes Cat 4 MEDIUM column_name non-default value across geopandas/spatialpandas/geojson return types and Cat 4 MEDIUM validation error paths (bad connectivity, bad transform length, mask shape mismatch, mask underlying-type mismatch). Cat 5 N/A: polygonize returns lists/dataframes, not a DataArray with attrs to propagate."
|
|
38
|
+
proximity,2026-06-02,2692,HIGH,1;2;3;4;5,"Pass 2 (2026-06-02): added 18 tests to test_proximity.py closing the two MEDIUM gaps Pass 1 left open, all RUN and passing on a CUDA host across numpy/cupy/dask+numpy/dask+cupy (15 cross-backend + 3 error-path). Source untouched. Cat 4 MEDIUM (error path): _process raises ValueError when raster.dims != (y, x) (proximity.py:1043) but no test exercised the swapped x/y guard; test_wrong_dim_order_raises pins it for proximity/allocation/direction. Cat 2 MEDIUM (all-NaN input): Pass 1 noted all-NaN/all-zero on eager numpy+cupy was unpinned; test_all_nan_raster_all_nan_output pins an all-NaN 6x6 raster -> all-NaN float32 output on all four backends x three functions. Remaining LOW (documented): invalid distance_metric string silently falls back to EUCLIDEAN (proximity.py:1049-1051). || PREVIOUS: Pass 1 (2026-05-29): added 65 tests to test_proximity.py closing three coverage gaps, all RUN and passing on a CUDA host (numpy/cupy/dask+numpy/dask+cupy). Issue #2692, PR opened. Source untouched. Cat 3 HIGH: degenerate raster shapes (1x1 single pixel, Nx1 column strip, 1xN row strip) had zero coverage for proximity/allocation/direction on any backend; they stress the line-sweep kernel boundaries (_process_proximity_line) and the GPU brute-force kernel grid sizing (_proximity_cuda_kernel via cuda_args). Pinned all three shapes x three functions x four backends against hand-checked expected values; mutation of a pinned direction expectation confirms teeth. Cat 1/4 HIGH: allocation and direction only ran EUCLIDEAN across backends; MANHATTAN and GREAT_CIRCLE were cross-backend-tested for proximity only. Pinned both metrics x two functions x four backends against the numpy baseline (all match). Cat 5 MEDIUM: no test set non-empty res/crs attrs so the attrs-preservation assertion in general_output_checks compared two empty dicts. proximity reads attrs['res'] via get_dataarray_resolution for bounded-dask chunk padding, so added attrs round-trip tests on four backends plus a bounded-dask test where a res attr matching the coordinate spacing must equal the numpy baseline. A res attr that lies about the spacing mis-sizes the map_overlap depth; source fragility, not a test gap, left for a separate accuracy issue. Cat 2 (NaN/Inf input) already covered by the shared test_raster fixture (embeds np.inf and np.nan, runs on four backends). Remaining LOW: all-NaN / all-zero input on eager numpy+cupy not directly pinned."
|
|
39
|
+
rasterize,2026-05-29,2614,MEDIUM,4,"Pass 4 (2026-05-29): added test_rasterize_coverage_2026_05_29.py with 11 tests, all passing (pure-Python validation paths, no CUDA needed); filed issue #2614 and opened a test-only PR. Closes Cat 4 MEDIUM error-path gaps that all three prior passes left untouched. (1) Partial width/height: the (width is None) != (height is None) guard in rasterize() raises ValueError naming the given and missing dimension, documented in the docstring, but neither the width-only nor height-only branch had a test; pin both directions plus the width-only+resolution case proving the guard fires before the resolution branch. (2) resolution= input type/shape validation: the type/shape branches (non-number/non-sequence string|dict; wrong-ndim numpy array; wrong-length sequence len 1|3|4; non-numeric elements) had no coverage -- test_rasterize.py's test_invalid_resolution_scalar/tuple only exercise non-finite/non-positive VALUES, not these type/shape guards, so a regression loosening or reordering them would ship silently; pin each branch to its message plus a positive control that a 1-D length-2 numpy array is still accepted. Source untouched."
|
|
40
|
+
reproject,2026-05-29,2618,HIGH,3,"Pass 2026-05-29: reproject already has a deep suite (369 tests in test_reproject.py + coverage/gate files) covering all 4 backends, NaN/Inf/all-NaN/all-Inf, 1x1/2x2, metadata, vertical shift, bounds_policy x backends, integer nodata x backends. Gaps found: Cat 3 HIGH single-row (1xN) and single-col (Nx1) strip rasters never tested (hit size<2 branch of _validate_regular_axis + degenerate resampling axis); Cat 3 MEDIUM constant-value/zero-gradient raster never reprojected. Added TestDegenerateShapeReproject (12 tests): 1xN+Nx1 strips x numpy/dask/cupy/dask+cupy, constant raster numpy value-preservation + cross-backend parity. All 12 executed and passed on a CUDA host. Test-only, no source change (#2618). LOW (documented only): _merge._merge_arrays_cupy imported but never called by merge() (host-bounces via _merge_arrays_numpy) - dead-code source observation not a test gap; non-square cellsize reproject only covered via resolution-tuple validation errors not a successful anisotropic run."
|
|
41
|
+
resample,2026-05-29,2547;2615,HIGH,1;2;3;5,"Pass 2 (2026-05-29): added test_resample_cupy_agg_fallback_2615.py (6 tests, all passing on CUDA host). Closes Cat 1 MEDIUM backend-coverage gap: the cupy eager aggregate CPU fallback for average/min/max at a NON-integer downsample factor (_run_cupy fy==int(fy) branch in resample.py ~L957-973) was never exercised; existing TestCuPyParity used 12x12 scale 0.5 (integer factor 2 -> GPU reshape path) and only median/mode hit the host fallback. New tests use 10x10 scale 0.3 (factor 3.33) for average/min/max parity vs numpy plus a NaN-masked variant. Issue #2615. Module is otherwise very thoroughly covered (test_resample.py + 3 supplementary files); no remaining HIGH gaps found. Pass 1 (2026-05-27): added test_resample_coverage_2026_05_27.py with 70 tests (68 passing, 2 skipped). Closes Cat 3 HIGH Nx1 single-column gap across numpy/cupy/dask+numpy/dask+cupy x 8 methods (nearest/bilinear/cubic/average/min/max/median/mode) plus Nx1 upsample-nearest parity and Nx1 cross-backend aggregate parity. Closes Cat 2 MEDIUM NaN-parity gap on cupy and dask+cupy (existing TestCuPyParity/TestDaskCuPyParity used random data without NaN; the weight-mask gate and spline-prepad had no GPU NaN coverage). Closes Cat 3 MEDIUM all-equal-value raster across 8 methods (downsample) and 3 interp methods (upsample) plus a constant-with-NaN aggregate variant. Closes Cat 5 MEDIUM non-default dim-name propagation: lat/lon, latitude/longitude, and (channel, lat, lon) 3D round-trip without being renamed to y/x; per-dim attrs (units) preserved. Closes Cat 3 MEDIUM empty-raster behaviour pin: 0-row and 0-col rasters raise (currently IndexError) -- contract covered. Filed source-bug issue #2547: cubic on dask backends fails for Nx1 / arrays smaller than depth=16; the 2 skipped tests in this file gate on that fix landing. Source untouched."
|
|
42
|
+
slope,2026-05-29,2697,MEDIUM,3,"PR #2703: added degenerate-shape tests (1x1/1xN/Nx1) for all 4 planar backends + geodesic; no live bug, pins all-NaN+shape contract. CUDA host: cupy/dask+cupy ran. Backend/NaN/param/metadata coverage already complete."
|
|
43
|
+
zonal,2026-05-29,2619,MEDIUM,1,"Pass 2 (2026-05-29): one Cat 1 MEDIUM backend-coverage gap remained after pass 1 -- 3D crosstab on cupy / dask+cupy. The 3D GPU paths (_crosstab_cupy / _crosstab_dask_cupy with a 3D categorical values array, layer=, agg='count') were reachable and correct but untested; the existing 3D crosstab tests (test_crosstab_3d_count, test_crosstab_3d_agg_method, test_nodata_values_crosstab_3d) only parametrize numpy / dask+numpy. Added 3 parity tests to test_zonal_backend_coverage_2026_05_27.py (test_crosstab_3d_count_cupy_matches_numpy, test_crosstab_3d_count_dask_cupy_matches_numpy, test_crosstab_3d_nodata_cupy_matches_numpy) asserting cupy and dask+cupy results match numpy for agg='count' including a nodata_values case. All passed live on a CUDA host. Issue #2619, PR #2625. Test-only, no source change. Remaining LOW (documented, not fixed): get_full_extent has no direct unit test (exercised indirectly via suggest_zonal_canvas); non-square cellsize handling not exercised. Pass 1 (2026-05-27): added test_zonal_backend_coverage_2026_05_27.py with 32 tests, all passing on a CUDA host. Closes Cat 1 HIGH backend-coverage gaps: crosstab cupy + dask+cupy (_crosstab_cupy / _crosstab_dask_cupy were dispatched but never invoked by tests), regions cupy + dask+cupy (_regions_cupy via cupyx.scipy.ndimage + _regions_dask_cupy), trim dask+numpy + cupy + dask+cupy (_trim_bounds_dask isnan path and cupy data.get() path), crop dask+numpy + cupy + dask+cupy (_crop_bounds_dask + cupy data.get() path), apply 3D cupy + dask+cupy (per-layer kernel launch over the third axis in _apply_cupy and _apply_dask_cupy). Existing test_zonal.py covered only numpy + dask+numpy for crosstab/regions/trim/crop and 2D-only for cupy apply. Closes Cat 3 MEDIUM 1x1 / 1xN / Nx1 strip edge cases for trim, crop, and regions. Closes Cat 4 LOW pins: regions(neighborhood=6) ValueError, suggest_zonal_canvas(crs='Geographic') aspect-ratio pin and invalid-crs KeyError, crosstab cupy zone_ids/cat_ids filter, crosstab cupy agg='percentage'. Closes Cat 5 MEDIUM: regions coords/attrs propagation across numpy + dask+numpy, trim/crop name='trim'/'crop' default + attrs preservation. Also pins the documented numpy-vs-dask trim asymmetry on NaN sentinel (numpy _trim does equality which never matches NaN; dask _trim_bounds_dask has dedicated isnan branch). Mutation against the cupy.asnumpy() conversion in _crosstab_cupy flipped test_crosstab_cupy_matches_numpy red. Source untouched."
|
|
44
|
+
focal,2026-05-29,2732,HIGH,1,"Pass (2026-05-29): added test_hotspots_dask_cupy to test_focal.py closing Cat 1 HIGH backend-coverage gap. hotspots() registers dask_cupy_func=_hotspots_dask_cupy (focal.py L1414) but no test invoked it, while mean/apply/focal_stats each have a dedicated dask+cupy test. New test compares dask+cupy vs numpy on chunk interior (matches test_apply_dask_cupy/test_focal_stats_dask_cupy style). RUN on CUDA host: passes; spy confirmed routing through _hotspots_dask_cupy; path matches numpy exactly so no source fix needed. LOW (documented not fixed): Inf/-Inf inputs untested across focal funcs; 1x1 raster not explicitly tested for mean/apply/hotspots (focal_stats 1x1 covered by test_variety_single_cell). Issue #2732."
|