xarray-spatial 0.10.10__tar.gz → 0.10.11__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.
Files changed (550) hide show
  1. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.claude/sweep-accuracy-state.csv +4 -4
  2. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.claude/sweep-api-consistency-state.csv +3 -0
  3. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.claude/sweep-metadata-state.csv +1 -0
  4. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.claude/sweep-performance-state.csv +4 -4
  5. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.claude/sweep-style-state.csv +8 -2
  6. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.claude/sweep-test-coverage-state.csv +8 -3
  7. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/CHANGELOG.md +35 -0
  8. {xarray_spatial-0.10.10/xarray_spatial.egg-info → xarray_spatial-0.10.11}/PKG-INFO +1 -1
  9. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11/xarray_spatial.egg-info}/PKG-INFO +1 -1
  10. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xarray_spatial.egg-info/SOURCES.txt +2 -0
  11. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/_version.py +3 -3
  12. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/accessor.py +24 -0
  13. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/aspect.py +4 -6
  14. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/classify.py +44 -19
  15. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/dasymetric.py +101 -46
  16. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/diffusion.py +4 -17
  17. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/fire.py +12 -17
  18. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_xarray_backend.py +65 -34
  19. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/test_xarray_backend_coregister_3376.py +67 -34
  20. xarray_spatial-0.10.11/xrspatial/geotiff/tests/test_xarray_backend_coregister_target_3379.py +104 -0
  21. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/morphology.py +13 -18
  22. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/multispectral.py +9 -9
  23. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/proximity.py +8 -2
  24. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/rasterize.py +167 -14
  25. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_accessor.py +47 -0
  26. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_aspect.py +95 -0
  27. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_classify.py +174 -0
  28. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_dasymetric.py +294 -0
  29. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_diffusion.py +195 -0
  30. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_fire.py +63 -0
  31. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_morphology.py +169 -0
  32. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_multispectral.py +206 -0
  33. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_proximity.py +135 -0
  34. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize.py +165 -0
  35. xarray_spatial-0.10.11/xrspatial/tests/test_rasterize_all_touched_dask_grid_line_3384.py +131 -0
  36. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_viewshed.py +85 -0
  37. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_zonal.py +28 -0
  38. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/zonal.py +7 -5
  39. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.claude/sweep-security-state.csv +0 -0
  40. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.codex/sweep-accuracy-state.csv +0 -0
  41. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.codex/sweep-api-consistency-state.csv +0 -0
  42. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.codex/sweep-metadata-state.csv +0 -0
  43. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.codex/sweep-performance-state.csv +0 -0
  44. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.codex/sweep-security-state.csv +0 -0
  45. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.codex/sweep-style-state.csv +0 -0
  46. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.codex/sweep-test-coverage-state.csv +0 -0
  47. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.efficiency-audit-baseline.json +0 -0
  48. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.efficiency-audit-baseline.prev.json +0 -0
  49. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.gitattributes +0 -0
  50. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  51. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.github/ISSUE_TEMPLATE/feature-proposal.md +0 -0
  52. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.github/ISSUE_TEMPLATE/new-contributor.md +0 -0
  53. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.github/labeler.yml +0 -0
  54. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.github/pull_request_template.md +0 -0
  55. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.github/workflows/benchmarks.yml +0 -0
  56. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.github/workflows/copilot-review.yml +0 -0
  57. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.github/workflows/docs.yml +0 -0
  58. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.github/workflows/labeler.yml +0 -0
  59. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.github/workflows/pypi-publish.yml +0 -0
  60. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.github/workflows/test-cog-validator.yml +0 -0
  61. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.github/workflows/test-geotiff-corpus.yml +0 -0
  62. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.github/workflows/test.yml +0 -0
  63. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.github/workflows/welcome-contributor.yml +0 -0
  64. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.gitignore +0 -0
  65. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.kilo/sweep-accuracy-state.csv +0 -0
  66. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.kilo/sweep-api-consistency-state.csv +0 -0
  67. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.kilo/sweep-metadata-state.csv +0 -0
  68. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.kilo/sweep-performance-state.csv +0 -0
  69. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.kilo/sweep-security-state.csv +0 -0
  70. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.kilo/sweep-style-state.csv +0 -0
  71. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.kilo/sweep-test-coverage-state.csv +0 -0
  72. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/.readthedocs.yml +0 -0
  73. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/AI_POLICY.md +0 -0
  74. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/CLAUDE.md +0 -0
  75. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/CODE_OF_CONDUCT.md +0 -0
  76. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/CONTRIBUTING.md +0 -0
  77. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/Citation-styles.md +0 -0
  78. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/LICENSE.txt +0 -0
  79. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/MANIFEST.in +0 -0
  80. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/README.md +0 -0
  81. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/RELEASE.md +0 -0
  82. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/codecov.yml +0 -0
  83. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/pyproject.toml +0 -0
  84. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/setup.cfg +0 -0
  85. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/setup.py +0 -0
  86. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xarray_spatial.egg-info/dependency_links.txt +0 -0
  87. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xarray_spatial.egg-info/entry_points.txt +0 -0
  88. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xarray_spatial.egg-info/not-zip-safe +0 -0
  89. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xarray_spatial.egg-info/requires.txt +0 -0
  90. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xarray_spatial.egg-info/top_level.txt +0 -0
  91. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/__init__.py +0 -0
  92. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/__main__.py +0 -0
  93. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/analytics.py +0 -0
  94. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/balanced_allocation.py +0 -0
  95. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/bilateral.py +0 -0
  96. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/bump.py +0 -0
  97. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/contour.py +0 -0
  98. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/convolution.py +0 -0
  99. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/corridor.py +0 -0
  100. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/cost_distance.py +0 -0
  101. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/curvature.py +0 -0
  102. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/dataset_support.py +0 -0
  103. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/datasets/__init__.py +0 -0
  104. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/datasets/sentinel-2/blue_band.nc +0 -0
  105. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/datasets/sentinel-2/green_band.nc +0 -0
  106. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/datasets/sentinel-2/nir_band.nc +0 -0
  107. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/datasets/sentinel-2/red_band.nc +0 -0
  108. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/datasets/sentinel-2/swir1_band.nc +0 -0
  109. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/datasets/sentinel-2/swir2_band.nc +0 -0
  110. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/diagnostics.py +0 -0
  111. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/edge_detection.py +0 -0
  112. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/emerging_hotspots.py +0 -0
  113. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/erosion.py +0 -0
  114. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/experimental/__init__.py +0 -0
  115. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/experimental/min_observable_height.py +0 -0
  116. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/flood.py +0 -0
  117. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/focal.py +0 -0
  118. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geodesic.py +0 -0
  119. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/__init__.py +0 -0
  120. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_attrs.py +0 -0
  121. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_backends/__init__.py +0 -0
  122. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_backends/_gpu_helpers.py +0 -0
  123. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_backends/dask.py +0 -0
  124. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_backends/gpu.py +0 -0
  125. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_backends/vrt.py +0 -0
  126. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_cog_http.py +0 -0
  127. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_compression.py +0 -0
  128. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_coords.py +0 -0
  129. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_crs.py +0 -0
  130. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_decode.py +0 -0
  131. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_dtypes.py +0 -0
  132. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_encode.py +0 -0
  133. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_errors.py +0 -0
  134. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_geotags.py +0 -0
  135. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_gpu_decode.py +0 -0
  136. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_header.py +0 -0
  137. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_layout.py +0 -0
  138. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_nodata.py +0 -0
  139. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_overview.py +0 -0
  140. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_overview_kernels.py +0 -0
  141. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_reader.py +0 -0
  142. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_runtime.py +0 -0
  143. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_safe_xml.py +0 -0
  144. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_sidecar.py +0 -0
  145. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_sources.py +0 -0
  146. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_validation.py +0 -0
  147. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_vrt.py +0 -0
  148. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_vrt_validation.py +0 -0
  149. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_write_layout.py +0 -0
  150. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_writer.py +0 -0
  151. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_writers/__init__.py +0 -0
  152. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_writers/eager.py +0 -0
  153. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_writers/gpu.py +0 -0
  154. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/_writers/vrt.py +0 -0
  155. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/__init__.py +0 -0
  156. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/_geotiff_fixtures.py +0 -0
  157. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/_helpers/__init__.py +0 -0
  158. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/_helpers/markers.py +0 -0
  159. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/_helpers/tiff_builders.py +0 -0
  160. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/_helpers/tiff_surgery.py +0 -0
  161. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/attrs/__init__.py +0 -0
  162. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/attrs/test_contract.py +0 -0
  163. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/bench_vs_rioxarray.py +0 -0
  164. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/conftest.py +0 -0
  165. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/README.md +0 -0
  166. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/__init__.py +0 -0
  167. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/_marks.py +0 -0
  168. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/_oracle.py +0 -0
  169. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/cog_internal_overview_uint16.tif +0 -0
  170. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/compression_deflate_predictor2_uint16.tif +0 -0
  171. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/compression_deflate_predictor3_float32.tif +0 -0
  172. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/compression_jpeg_uint8_ycbcr.tif +0 -0
  173. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/compression_lerc_float32.tif +0 -0
  174. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/compression_lzw_predictor2_int16.tif +0 -0
  175. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/compression_none_uint8.tif +0 -0
  176. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/crs_citation_only.tif +0 -0
  177. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/crs_epsg_3857.tif +0 -0
  178. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/crs_wkt_utm10n.tif +0 -0
  179. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_float32.tif +0 -0
  180. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_float64.tif +0 -0
  181. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_int16.tif +0 -0
  182. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_int32.tif +0 -0
  183. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_int8.tif +0 -0
  184. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_uint16.tif +0 -0
  185. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_uint32.tif +0 -0
  186. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/dtype_uint8.tif +0 -0
  187. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/extra_tags_uint16.tif +0 -0
  188. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/gdal_metadata_namespaced_uint16.tif +0 -0
  189. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/nodata_int_sentinel_uint16.tif +0 -0
  190. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/nodata_miniswhite_uint8.tif +0 -0
  191. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/nodata_nan_float32.tif +0 -0
  192. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/overview_external_ovr_uint16.tif +0 -0
  193. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/overview_external_ovr_uint16.tif.ovr +0 -0
  194. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/overview_internal_uint16.tif +0 -0
  195. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/planar_separate_uint8_rgb.tif +0 -0
  196. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/sparse_tiled_uint16.tif +0 -0
  197. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/stripped_be_uint16.tif +0 -0
  198. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/stripped_le_uint16.tif +0 -0
  199. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/tiled_be_uint16.tif +0 -0
  200. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/fixtures/tiled_le_uint16.tif +0 -0
  201. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/generate.py +0 -0
  202. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/manifest.yaml +0 -0
  203. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_compression.py +0 -0
  204. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_corpus_determinism.py +0 -0
  205. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_dask_gpu.py +0 -0
  206. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_dask_numpy.py +0 -0
  207. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_dtype_variants.py +0 -0
  208. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_eager_numpy.py +0 -0
  209. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_fsspec.py +0 -0
  210. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_gpu.py +0 -0
  211. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_http.py +0 -0
  212. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_layout_endian.py +0 -0
  213. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_manifest.py +0 -0
  214. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_metadata_tags.py +0 -0
  215. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_nodata_sentinels.py +0 -0
  216. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_oracle.py +0 -0
  217. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_overview_cog.py +0 -0
  218. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/golden_corpus/test_vrt.py +0 -0
  219. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/gpu/__init__.py +0 -0
  220. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/gpu/test_codec.py +0 -0
  221. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/gpu/test_kernels_and_kwargs.py +0 -0
  222. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/gpu/test_reader.py +0 -0
  223. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/gpu/test_writer.py +0 -0
  224. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/integration/__init__.py +0 -0
  225. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/integration/test_dask_pipeline.py +0 -0
  226. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/integration/test_gpu_pipeline.py +0 -0
  227. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/integration/test_http_sources.py +0 -0
  228. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/integration/test_sidecar.py +0 -0
  229. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/parity/__init__.py +0 -0
  230. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/parity/test_api_consolidation.py +0 -0
  231. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/parity/test_backend_matrix.py +0 -0
  232. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/parity/test_finalization.py +0 -0
  233. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/parity/test_pixel_equality.py +0 -0
  234. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/parity/test_reference.py +0 -0
  235. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/parity/test_signature_contract.py +0 -0
  236. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/__init__.py +0 -0
  237. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_basic.py +0 -0
  238. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_bbox_2555.py +0 -0
  239. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_bbox_vrt_2668.py +0 -0
  240. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_cloud_source_concurrency_3361.py +0 -0
  241. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_compression.py +0 -0
  242. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_coords.py +0 -0
  243. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_crs.py +0 -0
  244. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_degenerate_shapes.py +0 -0
  245. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_dtypes.py +0 -0
  246. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_endianness.py +0 -0
  247. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_georef.py +0 -0
  248. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_mask_and_scale_dtype_parity_3066.py +0 -0
  249. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_nodata.py +0 -0
  250. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_overview.py +0 -0
  251. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_rioxarray_compat_2961.py +0 -0
  252. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_scale_zero_3104.py +0 -0
  253. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_streaming.py +0 -0
  254. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_tiling.py +0 -0
  255. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/read/test_unpack_noop_doc_3263.py +0 -0
  256. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/release_gates/__init__.py +0 -0
  257. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/release_gates/test_features.py +0 -0
  258. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/release_gates/test_stable_features.py +0 -0
  259. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/test_edge_cases.py +0 -0
  260. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/test_fuzz_hypothesis.py +0 -0
  261. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/test_polish.py +0 -0
  262. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/test_round_trip.py +0 -0
  263. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/test_security.py +0 -0
  264. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/test_shutdown_cleanup_2486.py +0 -0
  265. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/test_stable_only_bbox_ordering_2869.py +0 -0
  266. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/test_stable_only_remote_2821.py +0 -0
  267. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/test_xarray_backend_3365.py +0 -0
  268. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/unit/__init__.py +0 -0
  269. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/unit/test_codec_roundtrip.py +0 -0
  270. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/unit/test_compression.py +0 -0
  271. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/unit/test_degenerate_pixel_size_3331.py +0 -0
  272. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/unit/test_exception_exports_3265.py +0 -0
  273. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/unit/test_geotags.py +0 -0
  274. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/unit/test_header.py +0 -0
  275. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/unit/test_ifd.py +0 -0
  276. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/unit/test_input_validation.py +0 -0
  277. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/unit/test_metadata.py +0 -0
  278. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/unit/test_photometric.py +0 -0
  279. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/unit/test_predictor.py +0 -0
  280. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/unit/test_safe_xml.py +0 -0
  281. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/unit/test_signatures.py +0 -0
  282. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/vrt/__init__.py +0 -0
  283. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/vrt/test_dtype_conversion.py +0 -0
  284. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/vrt/test_metadata.py +0 -0
  285. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/vrt/test_missing_sources.py +0 -0
  286. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/vrt/test_non_georef_placement_3116.py +0 -0
  287. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/vrt/test_parity.py +0 -0
  288. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/vrt/test_source_opt_ins_2672.py +0 -0
  289. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/vrt/test_validation.py +0 -0
  290. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/vrt/test_window.py +0 -0
  291. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/__init__.py +0 -0
  292. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/test_basic.py +0 -0
  293. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/test_bigtiff.py +0 -0
  294. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/test_cog.py +0 -0
  295. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/test_crs.py +0 -0
  296. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/test_nodata.py +0 -0
  297. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/test_overview.py +0 -0
  298. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/test_pack_3064.py +0 -0
  299. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/test_pack_64bit_sentinel_3264.py +0 -0
  300. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/test_pack_band_subset_3161.py +0 -0
  301. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/test_pack_float_width_3080.py +0 -0
  302. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/test_pack_lazy_nan_guard_3235.py +0 -0
  303. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/test_pack_nodata_kwarg_3168.py +0 -0
  304. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/test_pack_range_guard_3260.py +0 -0
  305. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/test_streaming.py +0 -0
  306. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/geotiff/tests/write/test_vrt_atomic.py +0 -0
  307. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/glcm.py +0 -0
  308. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/gpu_rtx/__init__.py +0 -0
  309. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/gpu_rtx/_memory.py +0 -0
  310. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/gpu_rtx/cuda_utils.py +0 -0
  311. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/gpu_rtx/hillshade.py +0 -0
  312. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/gpu_rtx/mesh_utils.py +0 -0
  313. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/gpu_rtx/viewshed.py +0 -0
  314. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hillshade.py +0 -0
  315. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/__init__.py +0 -0
  316. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/_boundary_store.py +0 -0
  317. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/basin_d8.py +0 -0
  318. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/fill_d8.py +0 -0
  319. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/flow_accumulation_d8.py +0 -0
  320. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/flow_accumulation_dinf.py +0 -0
  321. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/flow_accumulation_mfd.py +0 -0
  322. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/flow_direction_d8.py +0 -0
  323. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/flow_direction_dinf.py +0 -0
  324. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/flow_direction_mfd.py +0 -0
  325. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/flow_length_d8.py +0 -0
  326. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/flow_length_dinf.py +0 -0
  327. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/flow_length_mfd.py +0 -0
  328. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/flow_path_d8.py +0 -0
  329. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/flow_path_dinf.py +0 -0
  330. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/flow_path_mfd.py +0 -0
  331. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/hand_d8.py +0 -0
  332. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/hand_dinf.py +0 -0
  333. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/hand_mfd.py +0 -0
  334. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/sink_d8.py +0 -0
  335. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/snap_pour_point_d8.py +0 -0
  336. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/stream_link_d8.py +0 -0
  337. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/stream_link_dinf.py +0 -0
  338. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/stream_link_mfd.py +0 -0
  339. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/stream_order_d8.py +0 -0
  340. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/stream_order_dinf.py +0 -0
  341. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/stream_order_mfd.py +0 -0
  342. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/__init__.py +0 -0
  343. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/conftest.py +0 -0
  344. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_basin_d8.py +0 -0
  345. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_fill_d8.py +0 -0
  346. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_flow_accumulation_d8.py +0 -0
  347. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_flow_accumulation_dinf.py +0 -0
  348. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_flow_accumulation_mfd.py +0 -0
  349. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_flow_direction_d8.py +0 -0
  350. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_flow_direction_dinf.py +0 -0
  351. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_flow_direction_mfd.py +0 -0
  352. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_flow_length_d8.py +0 -0
  353. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_flow_length_dinf.py +0 -0
  354. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_flow_length_mfd.py +0 -0
  355. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_flow_path_d8.py +0 -0
  356. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_flow_path_dinf.py +0 -0
  357. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_flow_path_mfd.py +0 -0
  358. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_hand_d8.py +0 -0
  359. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_hand_dinf.py +0 -0
  360. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_hand_mfd.py +0 -0
  361. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_sink_d8.py +0 -0
  362. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_snap_pour_point_d8.py +0 -0
  363. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_stream_link_d8.py +0 -0
  364. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_stream_link_dinf.py +0 -0
  365. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_stream_link_mfd.py +0 -0
  366. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_stream_order_d8.py +0 -0
  367. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_stream_order_dinf.py +0 -0
  368. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_stream_order_mfd.py +0 -0
  369. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_twi_d8.py +0 -0
  370. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_validate_cellsize.py +0 -0
  371. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_validate_mfd_companion_shape.py +0 -0
  372. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_validate_mfd_fractions.py +0 -0
  373. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_validate_scalar_params.py +0 -0
  374. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_validate_secondary_args.py +0 -0
  375. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_watershed_d8.py +0 -0
  376. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_watershed_dinf.py +0 -0
  377. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/tests/test_watershed_mfd.py +0 -0
  378. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/twi_d8.py +0 -0
  379. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/watershed_d8.py +0 -0
  380. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/watershed_dinf.py +0 -0
  381. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/hydro/watershed_mfd.py +0 -0
  382. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/interpolate/__init__.py +0 -0
  383. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/interpolate/_idw.py +0 -0
  384. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/interpolate/_kriging.py +0 -0
  385. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/interpolate/_spline.py +0 -0
  386. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/interpolate/_validation.py +0 -0
  387. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/kde.py +0 -0
  388. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/mahalanobis.py +0 -0
  389. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/mcda/__init__.py +0 -0
  390. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/mcda/combine.py +0 -0
  391. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/mcda/constrain.py +0 -0
  392. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/mcda/sensitivity.py +0 -0
  393. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/mcda/standardize.py +0 -0
  394. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/mcda/weights.py +0 -0
  395. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/normalize.py +0 -0
  396. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/pathfinding.py +0 -0
  397. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/perlin.py +0 -0
  398. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/polygon_clip.py +0 -0
  399. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/polygonize.py +0 -0
  400. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/preview.py +0 -0
  401. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/reproject/__init__.py +0 -0
  402. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/reproject/_crs_utils.py +0 -0
  403. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/reproject/_grid.py +0 -0
  404. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/reproject/_interpolate.py +0 -0
  405. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/reproject/_itrf.py +0 -0
  406. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/reproject/_lite_crs.py +0 -0
  407. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/reproject/_merge.py +0 -0
  408. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/reproject/_projections.py +0 -0
  409. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/reproject/_projections_cuda.py +0 -0
  410. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/reproject/_transform.py +0 -0
  411. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/reproject/_vertical.py +0 -0
  412. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/reproject/grids/us_nga_egm96_15.tif +0 -0
  413. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/resample.py +0 -0
  414. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/sieve.py +0 -0
  415. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/sky_view_factor.py +0 -0
  416. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/slope.py +0 -0
  417. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/surface_distance.py +0 -0
  418. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/terrain.py +0 -0
  419. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/terrain_metrics.py +0 -0
  420. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/__init__.py +0 -0
  421. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/bench_reproject_vs_rioxarray.py +0 -0
  422. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/conftest.py +0 -0
  423. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/general_checks.py +0 -0
  424. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_analytics.py +0 -0
  425. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_balanced_allocation.py +0 -0
  426. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_bilateral.py +0 -0
  427. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_bump.py +0 -0
  428. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_contour.py +0 -0
  429. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_convolution.py +0 -0
  430. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_corridor.py +0 -0
  431. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_cost_distance.py +0 -0
  432. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_curvature.py +0 -0
  433. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_dask_cupy_gaps.py +0 -0
  434. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_dask_laziness.py +0 -0
  435. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_dask_task_names.py +0 -0
  436. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_dataset_support.py +0 -0
  437. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_datasets.py +0 -0
  438. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_diagnostics.py +0 -0
  439. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_edge_detection.py +0 -0
  440. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_emerging_hotspots.py +0 -0
  441. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_erosion.py +0 -0
  442. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_flood.py +0 -0
  443. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_focal.py +0 -0
  444. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_fused_overlap.py +0 -0
  445. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_geodesic_aspect.py +0 -0
  446. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_geodesic_slope.py +0 -0
  447. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_glcm.py +0 -0
  448. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_glcm_metric_order.py +0 -0
  449. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_gpu_rtx_has_rtx.py +0 -0
  450. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_gpu_rtx_memory.py +0 -0
  451. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_gpu_rtx_mesh.py +0 -0
  452. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_hillshade.py +0 -0
  453. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_hypsometric_integral.py +0 -0
  454. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_interpolation.py +0 -0
  455. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_kde.py +0 -0
  456. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_lite_crs.py +0 -0
  457. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_mahalanobis.py +0 -0
  458. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_mcda.py +0 -0
  459. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_min_observable_height.py +0 -0
  460. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_morphology_derived.py +0 -0
  461. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_multi_overlap.py +0 -0
  462. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_normalize.py +0 -0
  463. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_northness_eastness.py +0 -0
  464. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_open_geotiff_coregister.py +0 -0
  465. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_open_geotiff_resampling.py +0 -0
  466. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_optional_shapely.py +0 -0
  467. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_pathfinding.py +0 -0
  468. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_perlin.py +0 -0
  469. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_polygon_clip.py +0 -0
  470. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_polygonize.py +0 -0
  471. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_polygonize_atol_rtol_backend_coverage_2026_05_27.py +0 -0
  472. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_polygonize_coverage_2026_05_19.py +0 -0
  473. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_polygonize_dask_row_batch_2608.py +0 -0
  474. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_polygonize_issue_2172.py +0 -0
  475. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_polygonize_issue_2583.py +0 -0
  476. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_polygonize_issue_2606.py +0 -0
  477. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_polygonize_issue_2666.py +0 -0
  478. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_polygonize_issue_2677.py +0 -0
  479. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_polygonize_issue_3292.py +0 -0
  480. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_polygonize_issue_3303.py +0 -0
  481. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_polygonize_mask_chunk_mismatch_3299.py +0 -0
  482. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_polygonize_mask_dtype_coverage_2026_05_29.py +0 -0
  483. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_preview.py +0 -0
  484. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_accuracy.py +0 -0
  485. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_all_touched_supercover_2169.py +0 -0
  486. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_alloc_3107.py +0 -0
  487. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_coverage_2026_05_17.py +0 -0
  488. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_coverage_2026_05_21.py +0 -0
  489. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_coverage_2026_05_27.py +0 -0
  490. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_coverage_2026_05_29.py +0 -0
  491. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_coverage_2026_06_09.py +0 -0
  492. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_crs_mismatch_3058.py +0 -0
  493. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_descending_x_2568.py +0 -0
  494. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_dtype_annot_3291.py +0 -0
  495. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_fill_dtype_3054.py +0 -0
  496. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_geom_crs_3087.py +0 -0
  497. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_gpu_alias_3089.py +0 -0
  498. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_gpu_callable_warn_3057.py +0 -0
  499. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_gpu_race_2167.py +0 -0
  500. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_int_precision_3056.py +0 -0
  501. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_linearring_3055.py +0 -0
  502. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_lines_all_touched_3102.py +0 -0
  503. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_merge_dedup_3304.py +0 -0
  504. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_mixed_type_ordered_merge_3296.py +0 -0
  505. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_nan_int_fill_2504.py +0 -0
  506. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_nan_propagation_2255.py +0 -0
  507. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_nonfinite_burn_3085.py +0 -0
  508. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_nonfinite_burn_3088.py +0 -0
  509. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_nonfinite_coords_3295.py +0 -0
  510. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_partial_dims_2569.py +0 -0
  511. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_props_hoist_2506.py +0 -0
  512. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_resolution_exact_2573.py +0 -0
  513. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_resolution_validation_2576.py +0 -0
  514. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_signature_annot_2250.py +0 -0
  515. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_signed_step_2566.py +0 -0
  516. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rasterize_tile_props_slice_2020.py +0 -0
  517. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_rechunk_no_shuffle.py +0 -0
  518. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_reproject.py +0 -0
  519. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_reproject_coverage_2026_05_27.py +0 -0
  520. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_reproject_cupy_gate_2564.py +0 -0
  521. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_reproject_cupy_promotion_3281.py +0 -0
  522. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_reproject_inverse_kernels_3274.py +0 -0
  523. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_reproject_itrf_scale_3276.py +0 -0
  524. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_reproject_parallel_kernels_3141.py +0 -0
  525. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_reproject_pyproj_warning_3242.py +0 -0
  526. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_reproject_sphere_ellipsoid_guard_3275.py +0 -0
  527. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_reproject_streaming_3101.py +0 -0
  528. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_resample.py +0 -0
  529. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_resample_coverage_2026_05_27.py +0 -0
  530. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_resample_cupy_agg_fallback_2615.py +0 -0
  531. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_resample_input_validation_2574.py +0 -0
  532. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_resample_irregular_coords_2663.py +0 -0
  533. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_resample_nodata_dask_parity_3073.py +0 -0
  534. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_resample_signature_annot_2544.py +0 -0
  535. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_sieve.py +0 -0
  536. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_sieve_gdal_parity.py +0 -0
  537. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_sky_view_factor.py +0 -0
  538. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_slope.py +0 -0
  539. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_surface_distance.py +0 -0
  540. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_sweep_state_csv_merge_2754.py +0 -0
  541. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_terrain.py +0 -0
  542. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_terrain_metrics.py +0 -0
  543. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_utils.py +0 -0
  544. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_validation.py +0 -0
  545. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_visibility.py +0 -0
  546. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/tests/test_zonal_backend_coverage_2026_05_27.py +0 -0
  547. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/utils.py +0 -0
  548. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/viewshed.py +0 -0
  549. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/visibility.py +0 -0
  550. {xarray_spatial-0.10.10 → xarray_spatial-0.10.11}/xrspatial/worley.py +0 -0
@@ -6,11 +6,11 @@ contour,2026-05-01,,,,"Marching squares correct: NaN check uses self-inequality,
6
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
7
  cost_distance,2026-06-16,3369,CRITICAL,5,"CRITICAL heap overflow (#3369/this PR): numba Dijkstra kernels _cost_distance_kernel + _cost_distance_tile_kernel sized the binary min-heap at height*width, but a lazy-deletion heap pushes a pixel on every improving relaxation, so push count exceeds h*w on non-uniform friction. _heap_push then writes OOB -> heap corruption, SIGABRT (exit 134, 'corrupted size vs prev_size') on iterative dask path; UB on numpy path. Reference heapq Dijkstra hits 44 pushes on a 6x6=36 grid. Fix: max_heap = h*w*(n_neighbors+1), tile kernel adds +2*(w+h)+4 for phase-2 boundary seeds. Verified: cupy relax kernel (parallel Bellman-Ford) does NOT use this heap, GPU path unaffected. CUDA available; numpy/cupy/dask+numpy/dask+cupy all agree post-fix over 30+40 random adversarial grids; 88 module tests pass (4 new regression tests). Cats 1-4 clean: dist float64 / out float32 fine; inf/nan/zero friction all impassable (tested); bounds guards use >=h/>=w; planar algorithm, no curvature expected. Supersedes prior #1191 (cupy max_iterations h+w->h*w, fixed PR #1192)."
8
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.
9
+ dasymetric,2026-06-20,3403,MEDIUM,2;5,"Cat2/Cat5: disaggregate(limiting_variable) silently dropped a zone's whole value when no pixel could absorb it (all pixels in a cap-0 class, e.g. all zero-weight) - returned finite zeros summing to 0 instead of NaN, violating the documented conservation property; weighted method returns NaN for the same input. Fix #3403/this PR: set zone result to NaN when n_overflow==0, matching weighted. Also flagged (LOW, not fixed, documented only): non-finite +Inf weights are not sanitized like negatives are - an Inf weight poisons the whole zone (wsum=inf -> NaN/0, mass lost), consistent across numpy/cupy/dask backends (not a divergence). Cat1/Cat3/Cat4 clean: weighted division is by sum of positives (no cancellation), no neighborhood stencil off-by-one (pycnophylactic shifts are symmetric and bounds-guarded), no geodesic/curvature math. CUDA available; cupy + dask+cupy parity verified (62 tests pass incl. cross-backend). Backends: cupy + dask paths delegate to the numpy core so the fix covers all four."
10
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
11
  edge_detection,2026-05-01,,,,Thin wrappers around convolve_2d with fixed Sobel/Prewitt/Laplacian kernels; no issues found
12
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.
13
+ fire,2026-06-19,3394,MEDIUM,5,"Cat5: dask+numpy map_blocks declared float64 (meta default) while ngjit kernels return float32; numpy/cupy/dask+cupy all float32. Fixed 6 wrappers with dtype=np.float32 (PR #3396); bsc already dtype=int8. Cats 1-4 clean: per-pixel ops, no stencil/accumulation/projected-distance; NaN via x!=x; CUDA bounds strict <; rdnbr/ros divisions guarded. cupy+dask+cupy tests run on GPU host."
14
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
15
  focal,2026-06-10,3214,MEDIUM,1;5,"mean() dtype divergence: numpy/dask+numpy cast to float64 (astype(float)) while cupy/dask+cupy forced float32, so output dtype was backend-dependent and float64 rasters lost precision on GPU (offset 1e7: GPU error 0.58 > true spread 0.42, same class as fixed #2831). mean() was left out of the #2769 _promote_float contract that apply/focal_stats follow. Fix #3214: _promote_float in mean(), drop hardcoded cupy.float32 in _mean_cupy/_mean_dask_cupy, excludes cast to working dtype for cross-backend match parity. CUDA available; all 4 backends executed (245 focal tests pass incl new 3214 dtype tests). Cats 2-4 clean: GPU kernels two-pass std/var (#2831 fix verified), NaN checks via v!=v, map_overlap depths == kernel radius, Gi* validated against reference test. LOW (documented, not fixed): mean() excludes mask only the center pixel; excluded sentinel values (e.g. -9999) still contribute to neighboring cells' means on all backends -- docstring says 'left unchanged rather than averaged', backend-consistent."
16
16
  geotiff,2026-06-14,3331,MEDIUM,2,"Pass 28 (2026-06-14, deep-sweep accuracy): MEDIUM fixed -- issue #3331 / PR #3332. Direct GeoTIFF read accepted a zero or non-finite ModelPixelScale (33550) / ModelTransformation (34264) diagonal: _geotags._extract_transform read sx=scale[0]/sy=scale[1] (and m[0]/m[5]) with no finite-nonzero check, then coords_from_pixel_geometry built arange(N)*pixel_width+origin, so a zero scale collapsed the whole axis onto the origin (constant, non-georeferenced coords) and a NaN/Inf scale produced an all-NaN/all-Inf axis -- a degenerate raster returned with no error on all four backends. Asymmetric: the VRT read path already rejected zero res_x/res_y (VRTUnsupportedError, _vrt_validation.py:202, comment notes the eager read 'would currently surface this as an opaque coord error' -- but it actually surfaced nothing) and the writer rejected zero-step coords (NonUniformCoordsError, _validation.py:1361). Fix adds DegeneratePixelSizeError (GeoTIFFAmbiguousMetadataError subclass, exported + docs table) and a _check_finite_nonzero_pixel_size guard in _extract_transform covering the scale-only, tiepoint+scale, and ModelTransformation paths; the unit-scale tiepoint fallback (literal 1.0) and the allow_rotated no-georef path are untouched. Guard sits in the shared extract path so numpy/dask/gpu/dask+gpu all reject identically -- verified all four locally on a degenerate fixture (CUDA available). NaN ModelTransformation diagonal still rejected: the rotation-tol check short-circuits on NaN (x>NaN is False) and falls through to the new guard. 29 tests in tests/unit/test_degenerate_pixel_size_3331.py (zero/NaN/+-Inf x 3 paths, subclass contract, positive controls, end-to-end open_geotiff). Existing geotiff unit+read suites: 2271 passed, 5 skipped; flake8+isort clean. Scope this pass: numerical core re-audit (overview kernels, coords transform math, decode predictor/nodata, dtypes) via 3 parallel readers -- overview float32-vs-float64 mean accumulator 'divergence' DISMISSED (the ngjit kernel float path and the numpy nanmean float path are mutually exclusive: float mean/min/max/median route to the kernel and return early, _overview.py:340-353); coords single-neighbor pixel-size recovery LOW (standard, negligible, bypassed by transform attr); decode/predictor/nodata clean (#3098 int-sentinel-before-float64-promote pattern not repeated). cuda-available. | Pass 27 (2026-06-12, deep-sweep): HIGH fixed -- issue #3260. to_geotiff(pack=True) cast the packed values to the integer dtype from attrs['mask_and_scale_dtype'] with no range or finiteness check: finite values outside the dtype range wrapped in the astype (4000.0 with SCALE=0.1 on int16 packs to 40000 and landed on disk as -25536, unpacking to -2553.6) and +/-Inf cast to a platform-defined integer (0 on linux/x86), all silently on all four backends; dask deferred the cast into the write's compute so there was no warning at all. Internally inconsistent: the adjacent NaN-no-sentinel guard exists precisely because 'the astype below would silently wrap'. Fix adds _pack_guard_int_range after the round, before the cast (eager raises at call time, dask via map_blocks from the write's single compute, mirroring #3235); exclusive upper bound iinfo.max+1 stays exact in float64 so int64/uint64 reject exactly-2**63 instead of wrapping. Also fixed en route: eager cupy no-sentinel integer pack crashed with TypeError in bool(out.isnull().any()) (implicit cupy->numpy); now routed through the cupy-safe _pack_guard_no_nan. 15 new tests in tests/write/test_pack_range_guard_3260.py covering all four backends (CUDA available, gpu legs executed), boundary iinfo.min/max round trips, round-back-into-range, uint underflow, and the 2**63 float64 bound. Scope this pass: post-2026-06-09 commits only (pack/unpack #3174/#3175/#3239/#3240/#3241, VRT offsets #3135, GPU streaming writer) since Pass 26 covered the rest 3 days earlier; overview kernels, _coords transform math, _decode predictor/orientation/LERC fill, and _nodata lifecycle re-read with no new findings; GPU streaming writer reviewed (per-band NaN rewrite and tile-row alignment mirror the full-array path). cuda-available. | Pass 26 (2026-06-09, deep-sweep): MEDIUM fixed -- issue #3098. _apply_eager_nodata_mask in _attrs.py compared the integer nodata sentinel AFTER promoting the buffer to float64, so int64/uint64 sentinels above 2**53 (INT64_MAX/UINT64_MAX) swallowed up to 512/1024 nearby valid values into NaN on the numpy-eager and cupy-eager backends, while the dask per-chunk mask (_delayed_read_window), the GPU GDS chunk path (_apply_nodata_mask_gpu), and the VRT path all compare at native integer width and masked only exact hits. Reproduced end-to-end: int64 file with nodata=INT64_MAX and values INT64_MAX-1..-513 gave 4 NaN eager vs 1 NaN dask. Same function was internally inconsistent: the mask_nodata=False pixels_present scan already compared at native width. Fix computes the mask at source dtype width before the float64 promotion (promotion itself stays unconditional per #2990); one site fixes both numpy and cupy eager since GPU routes through the helper via duck typing. 5 regression tests in tests/read/test_nodata.py (int64 exact-hit, eager-vs-dask parity, uint64, near-sentinel-no-hit pixels_present, gpu eager); verified on all 4 backends with CUDA. Also audited this pass with no findings: overview reduce kernels CPU vs GPU (empirical parity run incl. float32 median midpoint analysis: RN(a+b)/2 == RN((a+b)/2) so no divergence), unpack/pack scale-offset paths (#3075/#3065, mask-before-scale ordering consistent eager/dask, dask+gpu reuses CPU dask graph), bbox-to-window floor/ceil (GDAL touched semantics), VRT nearest mapping floor((out+0.5)*src/out) and Int64 nodata native-width round-trip, predictor 2/3 GPU kernels (lossless), writer NaN-to-sentinel gates. cuda-available; GPU paths executed, not just reviewed. | 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."
@@ -27,8 +27,8 @@ normalize,2026-05-01,,,,rescale and standardize across all 4 backends. NaN/inf f
27
27
  perlin,2026-04-10T12:00:00Z,,,,Improved Perlin noise implementation correct. Fade/gradient functions verified. Backend-consistent. Continuous at cell boundaries.
28
28
  polygon_clip,2026-06-10,3186,HIGH,5,"Cat5 backend inconsistency: dask+cupy clip_polygon rasterizes the mask with a uniform chunk size from the raster's first chunk, then feeds raster+mask to da.map_blocks (positional block pairing). Non-uniform raster chunks gave the mask a different block layout -> IndexError/ValueError (or silent mis-stamp). Repro (8,6) rechunk ((3,5),(6,)) on dask+cupy raised ValueError Shapes do not align; dask+numpy was fine via xarray.where rechunk. Fix #3186/PR: rechunk cond to raster.data.chunks[-2:] before map_blocks; added non-uniform regression tests for dask+numpy and dask+cupy. use_cuda->gpu migration in that branch was already landed by #3089/#3122. CUDA available; cupy+dask+cupy verified, 25 tests pass. Cats 1-4 clean: numpy path uses raster.where, cupy path operates on raw arrays, NaN inputs preserved, no neighborhood ops/curvature. Prior fix #1197/#1200 (crop+all_touched) merged and unrelated."
29
29
  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."
30
- proximity,2026-06-09,3108,HIGH,4;5,"Cat5/Cat4: bounded GREAT_CIRCLE dask (numpy+cupy) missed targets across the +/-180 antimeridian seam: _halo_depth sized x-halo as linear parallel-arc sum, but haversine is periodic in lon and chords shorten near poles, so array-space adjacency is no lower bound on spherical distance; numpy/cupy (brute force) found the wrap target (~111 km), dask returned NaN. Fixed in #3108 via chord bound 2R asin(cos(lat_max)|sin(dlon/2)|) + x-axis fold when seam/180-deg chord within max_distance (covers over-pole too). CUDA host: cupy + dask+cupy executed, 417+ tests pass. Cat1-3 clean (float32 output documented; NaN via isfinite consistent; bounds guards correct; tie-break unified in #2881). LOW (not fixed): great_circle_distance uses WGS84 equatorial radius 6378137 as sphere radius (~0.1% vs mean-radius convention) but documented and exposed as param."
31
- rasterize,2026-06-12,3304,HIGH,3,"Cat3 HIGH: merge='sum'/'count' burned the same geometry into a pixel 2-3x on all 4 backends (cross-backend parity probes never caught it because all backends shared the bug). Two sources: all_touched=True polygons overlap the scanline center-fill with the supercover boundary pass and re-burn shared ring-vertex cells (count up to 3 for ONE polygon; rasterio MergeAlg.add gives 1); lines re-burn the connecting vertex of consecutive Bresenham segments (count 2 per interior vertex; rasterio gives 1 even for self-crossing lines). Fix #3304/PR #3313: for sum/count only, enumerate line + boundary cells host-side, dedup per (geometry,row,col), drop boundary cells the geometry's own scanline covers via a crossing-parity replay of the fill's exact float arithmetic on the same edge table, burn survivors through the point kernels (numpy/cupy/dask+numpy/dask+cupy). Coverage unchanged (==merge='last'); points intentionally not deduped (GDAL adds once per point, dup MultiPoint=2 matches rasterio). CUDA available; cupy + dask+cupy executed. 790 rasterize tests + 48 new pass. Cats 1/2/4/5 clean this pass: GPU atomic min/max NaN masks (#2255) correct, GPU ceil emulation matches np.ceil for negatives, no curvature surface, backends bit-identical on mixed-geometry probes. Pre-existing known-OK: Bresenham vs GDAL line coverage tie-breaks (pinned in coverage_2026_06_09 tests); dead all_touched branch in _extract_edges_vectorized (callers never pass all_touched) noted, not removed."
30
+ proximity,2026-06-18,3389,MEDIUM,1;5,"Cat1/Cat5 (MEDIUM, fixed #3389): CPU brute-force _distance casts to float32 before best_dist<=max_distance, but the CUDA kernel compared the float64 distance; a max_distance in a target's float32 ulp gap qualified it on numpy/dask+numpy yet rejected it on cupy/dask+cupy (GREAT_CIRCLE proximity + EUCLIDEAN/MANHATTAN allocation/direction). Fixed by rounding best_dist to float32 in the kernel before the range test. CUDA host: 518 tests pass incl cupy + dask+cupy. Out of scope (separate pre-existing edge, predates fix): bounded dask EUCLIDEAN allocation can still NaN a target exactly at a float32-ulp max_distance via the halo-sizing path. Prior #3108 fix (antimeridian halo) still verified. LOW (unfixed): great_circle_distance uses WGS84 equatorial radius 6378137 as sphere radius (~0.1% vs mean-radius), but documented and param-exposed."
31
+ rasterize,2026-06-18,3384,HIGH,1;5,dask all_touched polygon-boundary supercover walk re-extracted per tile in tile-local float pixel coords; floor() tie on an on-grid boundary segment diverged from eager (Cat1 precision -> Cat5 backend split). Fixed by extracting boundary float segments in the global grid frame and shifting by integer tile offset (#3384). Verified numpy/cupy/dask+numpy/dask+cupy.
32
32
  reproject,2026-06-12,3274,HIGH,1;4,"3 confirmed bugs, all kernel-vs-PROJ parity: #3274 HIGH LAEA inverse spurious /rq (2.6 km err for 3035) + _authalic_apa inverse series wrong (4.8 m in AEA/CEA inverses; PROJ 3-term = 1.6 mm), CPU+CUDA kernels both; #3275 HIGH _is_wgs84_compatible_ellipsoid passes R-defined spheres (MODIS sinusoidal 18.9 km err) and _aea_params/_cea_params lack the guard entirely (23.8 km on spherical aea/cea); #3276 MEDIUM itrf helmert scale 1e-9 but PROJ +s is ppm (1e-6), ~23 mm err. Verified clean: merc/emerc/UTM/tmerc/LCC/polar stere (incl lat_ts akm1) forward+inverse <=1e-5 m vs pyproj; resampling kernels NaN handling and GDAL renorm match across numpy/cupy/dask (CUDA run, gpu-vs-cpu 1.3e-7); dask footprint chunk-skip bbox is a superset in all probed cases (no holes). LOW (documented only): _source_footprint_in_target probe array typo uses x-midpoint mx as a latitude in last 3 ys entries (bbox superset, correctness unaffected)."
33
33
  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."
34
34
  sieve,2026-04-13T12:00:00Z,,,,Union-find CCL correct. NaN excluded from labeling. All backends funnel through _sieve_numpy.
@@ -1,9 +1,12 @@
1
1
  module,last_inspected,issue,severity_max,categories_found,notes
2
+ classify,2026-06-20,3398,MEDIUM,1;3,"Sweep 2026-06-20 (deep-sweep-api-consistency-classify-2026-06-20). 1 MEDIUM Cat 1 finding filed as #3398 and fixed on this branch. (MEDIUM Cat 1 positional-order drift) natural_breaks ordered its params (agg, num_sample, name, k) while the other two classifiers that take the same trio order them (agg, k, num_sample, name): quantile(agg, k=4, num_sample, name), maximum_breaks(agg, k=5, num_sample, name). So natural_breaks(raster, 5) silently set num_sample=5 instead of k=5. Fix reorders natural_breaks to (agg, k=5, num_sample=20000, name) and adds a _natural_breaks_legacy_order shim: when k= is a keyword AND a second positional is present (the only way pre-1.0 callers passed num_sample, since k was last and always keyword), the positional is treated as the old num_sample with a DeprecationWarning. Keeps the one example notebook call natural_breaks(raster, 20000, k=4) working. Bundled trivial Cat 3 fix in same PR: binary() was the only public classifier with no type hints -- added agg: xr.DataArray, name: Optional[str], -> xr.DataArray to match the other 9. Tests: test_natural_breaks_positional_k_matches_siblings (new positional k == keyword k) and test_natural_breaks_legacy_positional_num_sample_warns (legacy order warns + maps identically). Full test_classify.py (now 91) + test_validation.py pass. Cat 4 considered NOT a finding: quantile k=4 (quartiles) vs k=5 (quintiles) elsewhere is the documented PySAL/mapclassify convention, not drift. No Cat 2 return drift (all 10 publics return xr.DataArray/Dataset via @supports_dataset, coords/dims/attrs preserved). No Cat 5 orphan API (all 10 re-exported in __init__.py; no __all__ but consistent with module convention). Cross-cutting, notes only: first-arg agg (classify family) vs raster (reproject/rasterize/polygonize) is library-wide drift, out of per-module scope. cuda-validated: CUDA_AVAILABLE=True on this host; natural_breaks new order + legacy shim smoke-tested on numpy AND cupy entry points (both warn + remap), dataset path binds name correctly, binary verified on cupy."
2
3
  focal,2026-06-10,3215;3216,MEDIUM,3;4,"Sweep 2026-06-10 (deep-sweep-api-consistency-focal-2026-06-10). 2 MEDIUM findings filed, fixed on branches -01/-02 off this one. (#3215, MEDIUM Cat 4 cross-backend default parity, branch -01) apply() default func=_calc_mean is an @ngjit CPU function but the cupy/dask+cupy paths launch func as a CUDA kernel via _focal_stats_func_cupy func[griddim, blockdim], so apply(cupy_agg, kernel) raises TypeError 'CPUDispatcher' object is not subscriptable (dask+cupy builds the graph and fails at compute). Prior 2026-05-29 sweep dispositioned this LOW as 'documented in the docstring', but the docstring covers explicit funcs -- the default itself is unusable on 2 of 4 backends. Fix: func=None sentinel resolved per backend (_calc_mean CPU, _focal_mean_cuda GPU), explicit-func behavior unchanged; same PR adds the missing name= param to the apply() docstring (signature has name='focal_apply'; mean/focal_stats/hotspots document theirs). (#3216, MEDIUM Cat 3, branch -02) hotspots() docstring lists 3 backends but dask_cupy_func=_hotspots_dask_cupy is dispatched and works; kernel param documented as binary ('values of 1 indicate the kernel') while hotspots accepts weighted kernels and the Gi* formula in the same docstring uses weights w_ij (apply/focal_stats reject non-binary via _validate_binary_kernel, hotspots deliberately does not). Docs-only fix. LOW documented, not fixed: among the 4 focal publics only mean() has @supports_dataset (Dataset-support drift; feature gap, not an API bug). Cross-cutting, notes only per template: emerging_hotspots(raster=), viewshed(raster=), calc_cellsize(raster) still use raster while focal standardized on agg with a DeprecationWarning shim (#2689/PR #2699); library-wide first-arg drift, belongs to those modules' sweeps. No Cat 1 in-module (agg canonical, raster alias warns, both-args raises). No Cat 2 return drift (mean/apply/hotspots 2D same-type, focal_stats 3D (stats,y,x) as documented). No Cat 5 orphan API (apply/focal_stats/hotspots documented in focal.rst autosummary and consumed via xrspatial.focal module path; only mean re-exported top-level; emerging_hotspots top-level vs hotspots module-level asymmetry noted, additive export would be a design call, not filed). cuda-validated: CUDA_AVAILABLE=True on this host; mean/apply/focal_stats/hotspots smoke-tested on cupy with kwarg parity; the apply default crash reproduced on GPU; hotspots weighted-kernel acceptance verified empirically."
3
4
  geotiff,2026-06-12,3263;3265,MEDIUM,3;5,"Re-sweep 2026-06-12 (deep-sweep-api-consistency-geotiff-2026-06-12); prior pass 2026-06-09 (#3086). Scope: surface changes since 2026-06-09 (pack/unpack fixes #3171-#3241, SUPPORTED_FEATURES reader.unpack/writer.pack/reader.coregister, coregister docs #3248) plus a fresh 5-category pass on open_geotiff/to_geotiff. 2 MEDIUM findings filed and fixed on branches -01/-02 off this one. (#3263, MEDIUM Cat 3, PR #3269, branch -01) open_geotiff unpack docstring said 'A source without scale / offset metadata is a no-op', but unpack=True folds into the masking gate (_finalize_eager_read: mask_and_scale implies masking, rioxarray parity), so a sentinel-bearing uint16 source still comes back float64 with NaN holes; verified identical on all 4 backends (not a parity bug), only a source with neither scale/offset nor a sentinel reads unchanged. Docs-only fix + test_unpack_noop_doc_3263.py pinning wording (scoped to the unpack paragraph) and behavior. (#3265, MEDIUM Cat 5, PR #3273, branch -02) exception-export drift: VRTUnsupportedError (raised 10+ times in _vrt_validation.py on public .vrt reads, documented in geotiff_safe_io.rst which steered users to the private _errors module), CloudSizeLimitError (importable but not in __all__, sibling UnsafeURLError IS exported), and PixelSafetyLimitError (raised by the [stable] max_pixels cap, only importable from _layout/_reader) were the only 3 exceptions raised on public open_geotiff paths missing from the public surface (other 17 exported). Additive fix: import + __all__ + :class: roles in safe_io doc + trigger-point docs naming the exceptions in max_pixels/max_cloud_bytes param docs and geotiff.rst; test_exception_exports_3265.py pins export, identity with private definitions, and a functional max_pixels raise. Clean elsewhere: docstring/signature parity exact on both publics (programmatic check + 218 existing contract tests); no Cat 1 (signatures unchanged since 2026-06-09; pack/unpack pair deliberate), no Cat 2 (DataArray / path returns unchanged), no Cat 4 (shared allow_* defaults match reader/writer; gpu False-vs-None auto-detect documented). SUPPORTED_FEATURES tiers (reader.unpack/writer.pack/reader.coregister experimental) agree with docstring tier markers. coregister= itself lives on accessor.py (excluded module) -- only its SUPPORTED_FEATURES registration is in geotiff, consistent. cuda-validated: CUDA_AVAILABLE=True; open_geotiff smoke-tested with identical kwargs on numpy/cupy/dask/dask+cupy (cpu/gpu pixel parity), to_geotiff gpu=True, cupy pack=True write (#3240 fix confirmed), deprecated aliases mask_and_scale/name/mask_nodata all warn. Both PRs reviewed (COMMENTED) with findings fixed in follow-up commits c14844a8/af3c8a66; branches up to date with origin/main; left for user merge per REVIEW_REQUIRED."
4
5
  hydro-d8,2026-05-29,2709,HIGH,1;5,"Sweep 2026-05-29 (deep-sweep-api-consistency-hydro-d8-2026-05-29). Scope = the 13 D8-variant files only; dinf/mfd read for reference but not modified. 1 HIGH Cat 1 + 1 MEDIUM Cat 5 fixed in this branch (#2709, PR #2716). HIGH Cat 1: stream_order_d8 named its strahler/shreve selector `ordering` while sibling stream_order_dinf/stream_order_mfd use `method`; both names live in the public API and the __init__.py _StreamOrderDispatch special-cases the drift (translates ordering->method for non-d8). Fix adds `method` as an accepted alias on stream_order_d8 (case-insensitive; takes precedence; conflicting ordering+method raises ValueError), keeping `ordering` working so the out-of-scope dispatcher (passes ordering=) and existing callers are unaffected. Full rename to `method` deferred because deprecating `ordering` would warn on every stream_order(routing='d8') call via the dispatcher I cannot touch in this scope. MEDIUM Cat 5: basins_d8 (watershed_d8.py) is a backward-compat wrapper whose docstring said 'use basin instead' but emitted no warning; added DeprecationWarning(stacklevel=2). Tests added for alias parity/precedence/conflict/case-insensitivity and for the basins_d8 warning. Findings documented but NOT filed per template: (LOW Cat 1 cross-module, out of scope) dinf siblings name the first arg `flow_dir_dinf` (stream_link/flow_path/hand/watershed_dinf) while all D8 funcs use the cleaner `flow_dir`; D8 is the better convention so no D8 change -- the drift lives in the dinf files. (LOW Cat 4 defensive-validation drift) hand_d8 validates np.isfinite(threshold) but stream_link_d8/stream_order_d8 (same threshold: float = 100 param) do not; not user-facing signature surprise, document only. No Cat 2 return drift (every D8 public fn returns xr.DataArray with coords/dims/attrs preserved; Dataset in -> Dataset out via @supports_dataset). No Cat 3 missing-hints beyond fill_d8 z_limit (optional, no hint) which mirrors its sibling style. All 13 D8 funcs are re-exported in xrspatial/hydro/__init__.py (no orphan API). cuda-validated: CUDA_AVAILABLE=True on this host; method-alias parity smoke-tested on a cupy DataArray. CI: ubuntu/windows/3.12 GitHub Actions green; macOS-3.14 + ReadTheDocs slow but no failures. NOTE: the /review-pr review comment could not be posted to GitHub (auto-mode permission denial on gh pr review); review findings were applied to code instead (case-insensitive conflict check + str|None hint, commit f8467320)."
5
6
  interpolate,2026-06-12,3285,MEDIUM,2,"Sweep 2026-06-12 (deep-sweep-api-consistency-interpolate-2026-06-12). Scope: idw/_idw.py, kriging/_kriging.py, spline/_spline.py, shared _validation.py. 1 MEDIUM Cat 2 finding filed as #3285, fixed on branch -01 off this one: kriging(return_variance=True) singular-matrix fallback (_kriging.py:499) returns prediction, prediction.copy() so the variance DataArray keeps the prediction's name instead of f'{name}_variance' (normal path :523 names it correctly); reproduced by monkeypatching _build_kriging_matrix to None; anything keying on .name (xr.merge, Dataset build) silently collapses the pair. One-line fix + regression test on the singular path. Clean elsewhere: Cat 1 in-module exact (idw/kriging/spline share x, y, z, template positionals and name= default '<func>'; template matches kde's template=); docstring/signature parity exact on all 3 publics (every param documented, Returns sections match incl. kriging's tuple); Cat 4 no default drift (power=2.0, k=None, fill_value=nan, variogram_model='spherical', nlags=15, smoothing=0.0, all single-owner params); Cat 5 no orphan API (all 3 re-exported in xrspatial/__init__.py and autosummaried in docs/source/reference/interpolation.rst; tests touch private helpers only via module paths). Cross-cutting, notes only per template: fill_value (idw) vs fill (rasterize) for the uncovered-pixel value is library-wide drift (idw matches numpy's fill_value convention, left alone); public functions are untyped module-wide (consistent internally, drifts from typed kde/rasterize/proximity siblings -- annotation pass would span the whole module, LOW, not filed); kde's keyword-only style is the library minority so interpolate's positional style matches the rasterize/proximity majority. GPU k-nearest rejection (NotImplementedError) is deliberate and documented in the k param docstring. cuda-validated: CUDA_AVAILABLE=True on this host; idw/kriging/spline smoke-tested with full kwargs on numpy AND cupy DataArrays (variance name parity confirmed on both), dask+numpy and dask+cupy graph construction verified without compute."
6
7
  mcda,2026-06-10,3148,HIGH,1;2;3;5,"Sweep 2026-06-10 (deep-sweep-api-consistency-mcda-2026-06-10). Fixed in this branch (#3148): (HIGH Cat 1) owa() named its criterion-weight dict criterion_weights while wlc/wpm/sensitivity use weights (same semantics, same _validate_weights); renamed to weights with keyword-only criterion_weights deprecation shim (DeprecationWarning; both names -> TypeError; positional callers untouched). (MEDIUM Cat 2) boolean_overlay annotated criteria as dict-only while every sibling combiner takes xr.Dataset; Dataset already worked via the Mapping interface -- now annotated/documented as xr.Dataset | dict. (MEDIUM Cat 3) ahp_weights docstring Raises claimed ValueError on incomplete comparisons but code warns (UserWarning) and defaults missing pairs to 1 -- docstring now documents Warns behaviour. (MEDIUM Cat 5) ConsistencyResult returned by public ahp_weights but absent from xrspatial/mcda __all__ and docs/source/reference/mcda.rst -- exported and documented. Documented, NOT fixed here: (MEDIUM Cat 2, deferred to parallel sweep-metadata sibling to avoid duplicate PR) constrain() drops attrs via xr.where while the other nine public functions preserve them. (LOW Cat 2) ahp_weights returns (weights, ConsistencyResult) tuple vs rank_weights bare dict -- intentional, documented in both docstrings, no fix. (LOW Cat 4) name=None inherit-input-name (standardize/constrain) vs literal-name defaults (combiners) -- defensible split, document only. Pre-existing backend bugs surfaced by the mandated cupy smoke (accuracy/test-coverage lane, recorded in #3148 body): owa fails on cupy (numpy order-weights array mixed into cupy multiply, combine.py ~336-340) and on ANY dask backend at graph construction (da.sort does not exist, combine.py:356, despite the owa MemoryError message recommending dask); sensitivity(method=monte_carlo) fails on cupy (template.values implicit-conversion guard). constrain on cupy blocked by the known library-wide cupy 13.6 + xarray xr.where astype incompat (dependency-pin issue), not mcda-specific. cuda-validated: CUDA_AVAILABLE=True; all 10 public functions smoke-tested on cupy DataArrays; owa weights=/criterion_weights= shim verified on numpy AND cupy entry points (cupy execution stops at the pre-existing mixed-array bug, signature acceptance confirmed)."
8
+ morphology,2026-06-20,3399,MEDIUM,5,"Sweep 2026-06-20 (deep-sweep-api-consistency-morphology-2026-06-20). 1 MEDIUM Cat 5 finding filed as #3399, fixed in this branch (PR #3409). Cat 5 accessor-parity gap: all 7 public morphology functions are exported in xrspatial/__init__.py, documented, and tested, but the .xrs accessor exposed only morph_erode/dilate/opening/closing on both XrsSpatialDataArrayAccessor and XrsSpatialDatasetAccessor; morph_gradient/white_tophat/black_tophat were missing (da.xrs.morph_gradient -> AttributeError while da.xrs.morph_erode works). Root cause: base 4 ops landed #949 + accessors #1042; derived 3 ops landed later #1026 and the accessor was never updated. Fix adds the 3 forwarding methods to both accessor classes (mechanical, matches existing pattern) plus accessor tests (none existed for morph before) and guards all 7 method names in the expected-methods checks. Clean elsewhere: Cat 1 no in-module naming drift -- all 7 publics share the exact signature (agg, kernel=None, boundary='nan', name='<op>'), verified programmatically; matches kernel-op siblings convolution_2d/focal.apply/hotspots which also use agg/kernel/boundary/name. Cat 2 no return drift (all 7 return xr.DataArray with coords/dims/attrs preserved via _dispatch / @supports_dataset). Cat 3 docstring/signature parity exact on all 7 (every param documented, Returns sections all DataArray). Cat 4 no default drift (kernel=None->3x3 ones, boundary='nan' uniform across all 7). Cross-cutting, notes only per template: convolution_2d orders name before boundary while morphology orders boundary before name (both keyword-defaulted, cosmetic); focal.apply/hotspots accept a keyword-only raster= alias for agg but that alias is focal-only (not a library convention) so morphology lacking it is not drift; library-wide first-arg agg vs raster drift spans 20+ modules, out of per-module scope. cuda-validated: CUDA_AVAILABLE=True on this host; all 7 publics smoke-tested with identical kwargs on numpy AND cupy DataArrays (shape parity, no signature drift between numpy/cupy entry points). PR reviewed (COMMENTED), no findings; branch merged with origin/main (clean), left BLOCKED on REVIEW_REQUIRED for user merge."
9
+ multispectral,2026-06-20,3433,MEDIUM,3,"Sweep 2026-06-20 (deep-sweep-api-consistency-multispectral). 18 public funcs, all single 2D DataArray returns except true_color (3D composite, inherently different). (#3433 MEDIUM Cat 3) nbr() docstring documented swir_agg but signature param is swir2_agg; copying the name from docs raises TypeError. Docs-only rename to swir2_agg + guard test test_docstring_params_match_signature over the 17 index funcs. No deprecation needed. LOW, documented not fixed: (Cat 5) gci/nbr2/ndmi/true_color/ebbi are NOT re-exported in xrspatial/__init__.py while 13 module siblings are; not an orphan API since the canonical documented path is xrspatial.multispectral.X (all 18 in multispectral.rst) and tests import from the submodule -- convenience-surface gap only. (Cat 3 LOW) only savi types name: str; true_color lacks band type hints and uses r/g/b (conventional for an RGB composite). Cross-module note (not filed per template): multispectral has no -> xr.DataArray return annotations while sibling fire.py annotates all 7 -- library-wide convention drift. No Cat 1 in-module (all band inputs are <band>_agg), no Cat 2 (return shapes consistent), no Cat 4 (no mutable defaults; soil_factor=1.0 consistent evi/savi). CUDA available: numpy+cupy smoke-tested, signatures parity-clean, full suite 171 passed."
7
10
  polygonize,2026-06-12,3306;3307,MEDIUM,1;3,"Re-sweep 2026-06-12 (deep-sweep-api-consistency-polygonize-2026-06-12); prior pass 2026-05-19 (#2148). 2 MEDIUM findings filed and fixed on branches -01/-02 off this one. (#3306, MEDIUM Cat 3, branch -01) column_name docstring says 'Only used if return_type is geopandas or spatialpandas' but _to_geojson also consumes it as the per-feature property key (verified: properties={'myval': 1}); docs-only fix + test pinning geojson property naming. (#3307, MEDIUM sibling-behavior drift, branch -02) return_type is the only polygonize parameter validated AFTER the computation: invalid value runs the full backend (spy-verified 1 invocation before raise) while sibling contours() validates up front and lists allowed values; fix hoists the check into the top validation block with an allowed-values message (existing test matches on prefix, unaffected). Re-confirmed prior dispositions, still documented-only per cross-module rule: (HIGH Cat 1 cross-module) connectivity (polygonize, matches GDAL/rasterio/skimage) vs neighborhood (sieve.py, zonal.regions) for the identical 4|8 rook/queen concept -- rename shim belongs in sieve/zonal, out of polygonize scope; (LOW Cat 1 cross-cutting) raster (polygonize/sieve/clip_polygon) vs agg (contours/terrain family) first-arg drift, library-wide, not filed per-module. No new Cat 2 (return_type dispatch shapes match docstring Returns section exactly); no Cat 4 (atol/rtol mirror numpy.isclose, connectivity=4 == sieve neighborhood=4); Cat 5 LOW documented-only: module has no __all__ and the non-underscore internals generated_jit + Turn leak via import-star; polygonize re-exported in __init__.py and accessor, no orphan API. Docstring/signature parity otherwise exact (all 10 params documented, all annotated). Open polygonize issues #3292/#3293 checked -- no overlap with these findings. cuda-validated: CUDA_AVAILABLE=True on this host; polygonize smoke-tested with identical full kwargs on numpy, cupy (int + float atol/rtol=0), and dask+cupy; no backend signature drift."
8
11
  proximity,2026-06-09,3090;3091,HIGH,2;3,"Sweep 2026-06-09 (deep-sweep-api-consistency-proximity-2026-06-09). 1 HIGH Cat 2 finding (#3090): dask+numpy (and unbounded dask+cupy, which converts to it) KDTree path violates the documented lowest-flat-index tie-break in allocation()/direction() whenever the raster has >1 chunk column. _collect_region_targets concatenates targets chunk-major (iy outer, ix inner) so the tree's target order is not global row-major; _kdtree_query_lowest_index then ties to the wrong target. Existing tie-break tests put both targets in the same raster row where chunk order coincides with row-major, so they pass. Repro: 5x5, targets 2@(1,3) and 3@(2,2), chunks (5,3), pixel (2,3) tied at d=1 -> numpy gives 2, dask gives 3. Bounded map_overlap paths are fine (local row-major order is offset-invariant). 1 MEDIUM Cat 3 finding (#3091): all 3 public docstrings claim numpy + dask+numpy support only while cupy/dask+cupy backends exist, are dispatched, and are tested (the tie-break paragraphs in the same docstrings name all 4 backends); direction() opens with a stray copy-pasted slope line ('downward slope direction') plus a doubled 'the the'; allocation example output reads as float64 but the function returns float32; stale '# convert to have same type as of input @raster' comment. Within-module Cat 1/4/5 clean: proximity/allocation/direction share an identical signature (raster, x='x', y='y', target_values=None, max_distance=np.inf, distance_metric='EUCLIDEAN'); consistent with surface_distance siblings (raster/x/y/target_values/max_distance); all 6 public symbols (incl. euclidean/manhattan/great_circle_distance) re-exported in __init__.py, no orphan API. Cross-cutting, documented not filed: sibling distance modules (surface_distance, cost_distance, balanced_allocation) use mutable default target_values: list = [] while proximity uses the None sentinel - the mutable-default fix belongs to those modules; proximity's target_values: list = None hint would be more precise as Optional[list] (LOW, matches library style). cuda-validated: CUDA_AVAILABLE=True on this host; proximity/allocation/direction smoke-tested with identical kwargs on numpy, cupy, dask+numpy, dask+cupy (proximity parity passed; allocation/direction parity failure is finding #3090)."
9
12
  rasterize,2026-06-09,3089,HIGH,1,"Sweep 2026-06-09 (deep-sweep-api-consistency-rasterize-2026-06-09). 1 HIGH Cat 1 fixed in this branch (#3089): rasterize(use_cuda=) vs open_geotiff(gpu=) named the identical GPU-backend opt-in differently; these are the only two public entry points with an explicit GPU boolean (no input array to dispatch on; both pair it with chunks= for dask) and both names were live in the public API at once. Fix renames the positional param to gpu (same slot, positional callers unaffected) and appends use_cuda=None as a deprecated alias: DeprecationWarning on use, TypeError when combined with gpu=True. Docstring, GPU merge warning text, CuPy ImportError text, and polygon_clip.py's internal dask+cupy caller updated (guarded so a legacy use_cuda in rasterize_kw does not collide with the new default); all rasterize test call sites migrated to gpu=; regression tests in test_rasterize_gpu_alias_3089.py pin slot position, warning, TypeError, backend parity, and the warning-free clip_polygon path. Re-inspection after the 2026-05-21 pass (#2250); prior cross-module notes (clip_polygon nodata vs fill, name default drift, polygonize column_name vs column) still documented-only. Docstring/signature parity verified programmatically (17/17 params, order matches). New params since last pass (check_crs, max_pixels) consistent with geotiff naming (max_pixels matches geotiff's). No Cat 2/4/5 findings. LOW noted, not fixed (other module's docs): docs/source/user_guide/focal.ipynb claims convolve_2d takes use_cuda, which it does not. cuda-validated: CUDA_AVAILABLE=True; numpy/cupy/dask+numpy/dask+cupy smoke-tested with identical kwargs, values equal."
@@ -6,6 +6,7 @@ focal,2026-06-10,3217,MEDIUM,4;5,"Re-audited 2026-06-10 (agent-ad0d55a894c6abc60
6
6
  geotiff,2026-06-09,3116,HIGH,2;3,"Re-audited 2026-06-09 (agent-ae89ff94a64e3ee8f worktree, branch deep-sweep-metadata-geotiff-2026-06-09). CUDA available; all 4 backends (numpy/cupy/dask+numpy/dask+cupy) run live. Focus: surfaces changed since the 2026-05-18 audit (unpack rename + GPU/dask+GPU support #3075, pack=True #3065/#3079, masked int->float promotion #2994, bbox= reads, rioxarray param alignment #2963, no-georef VRT coord synthesis #2824, GeoTransform omission #2971). Live probes: unpack attrs (scale_factor/add_offset/mask_and_scale_dtype/nodata/masked_nodata), masked=True promotion, default masked=False, bbox window+transform shift, multi-band band=N, dims/name/coords (incl. coord dtype) all identical across the 4 backends; nodata_pixels_present absent on dask paths is the documented lazy contract, not a bug. pack->unpack round trips verified on numpy/dask/gpu-write; pack of a cupy-backed read raises via the known cupy+xarray xp.astype incompat (see memory cupy_where_astype_incompat; dependency-pin fix, raises loudly, not a metadata bug). VRT reads (full/masked/window/bbox) and no-georef TIFF reads agree across the 4 backends. NEW HIGH finding #3116 (Cat 2+3): to_geotiff(non_georef_da, out.vrt, tile_size=N) wrote a corrupt index for arrays spanning >1 tile -- write_vrt derives placement from each source GeoTransform and non-georef tiles all carry the identity transform, so rasterX/YSize collapsed to one tile and every DstRect landed at the origin; reads silently returned a single tile (24x32 in -> 16x16 out). Gap left by #2966/#2971 (tests only covered one non-georef source). Fix: _write_vrt_tiled threads per-tile pixel offsets through _build_vrt -> write_vrt via internal dst_offsets kwarg; write_vrt refuses >1 all-non-georef sources without explicit placement and rejects dst_offsets alongside georeferenced sources. 18 new tests in tests/vrt/test_non_georef_placement_3116.py incl. 4-backend round trip, dask-backed and plain-ndarray writes, XML DstRect assertions, georef placement regression, and the write_vrt error contract. Full vrt suite 520 passed; write+round-trip suites 1292 passed."
7
7
  interpolate,2026-06-12,3288,MEDIUM,5,kriging K_inv-None fallback was numpy-backed on all backends and misnamed the variance raster; fixed via #3288. All 4 backends verified end-to-end on GPU host. LOW (documented only): template nodatavals/_FillValue copied verbatim while fill_value is the actual output sentinel; tests codify attrs==template.attrs
8
8
  mcda,2026-06-10,3147,HIGH,1,"constrain() dropped all attrs (res/crs/nodatavals) whenever exclude non-empty (xr.where takes attrs from scalar fill); fixed via attrs restore, tests for numpy/dask/dask+cupy. All other mcda funcs keep attrs/coords/dims on all 4 backends. Out-of-scope crashes noted for backend-parity: owa broken on cupy (numpy order-weights x cupy) and on dask (da.sort does not exist); sensitivity monte_carlo crashes on cupy/dask+cupy (.values on cupy); xr.where compute on cupy/dask+cupy hits known cupy13.6/xarray2025.12 incompat."
9
+ multispectral,2026-06-20,3429,MEDIUM,2;3,"true_color() hardcoded y/x dims + dropped extra coords; fixed PR #3434 (all 4 backends verified, CUDA available)"
9
10
  polygonize,2026-06-12,3293,MEDIUM,1,"Audited 2026-06-12 (agent-a86d90abea41b04cf worktree, branch deep-sweep-metadata-polygonize-2026-06-12). CUDA available; all 4 backends (numpy/cupy/dask+numpy/dask+cupy) run live for int, float+NaN, and no-georef rasters. polygonize returns vector output (numpy/awkward/geopandas/spatialpandas/geojson), not a DataArray, so Cats 2-4 reinterpreted as transform/CRS/value-dtype propagation. Transform auto-detect (attrs['transform'] -> rio.transform() -> x/y coords, #2536/#2607) and CRS resolution run in public polygonize() before dispatch, so all 4 backends emit identical columns, bounds, and CRS (verified live). Column value dtype follows input dtype on every backend. NEW MEDIUM finding #3293 (Cat 1): _detect_raster_crs ignored the _xrspatial_no_georef marker that _detect_raster_transform honours, so a geotiff-reader crs_only raster (attrs carry both crs and the marker; metadata_to_attrs writes crs independent of has_georef) produced a GeoDataFrame claiming EPSG:#### over pixel-space geometries -- the #2536 metadata-lies-about-the-data mismatch through the marker channel. contour.py imports the same helper and inherits the fix. Fix: early return None in _detect_raster_crs on the marker + docstring note; 2 new tests in TestPolygonizeCRSPropagation. polygonize+contour suites 274 passed; all 9 auxiliary polygonize test files 303 passed. rotated-read path unaffected (reader drops CRS there). No CRITICAL/HIGH/LOW findings."
10
11
  proximity,2026-05-29,2723,MEDIUM,4;5,"Audited 2026-05-29 (agent-a61dbadc2452a2003 worktree, branch deep-sweep-metadata-proximity-2026-05-29). CUDA+cupy available; all 4 backends (numpy/cupy/dask+numpy/dask+cupy) run live end-to-end for proximity/allocation/direction, both bounded (finite max_distance) and unbounded. Cat 1 (attrs res/crs/transform/nodatavals/_FillValue), Cat 2 (coords + coord dtype), and Cat 3 (dims) all preserved and identical across the 4 backends -- public funcs wrap with xr.DataArray(coords=raster.coords, dims=raster.dims, attrs=raster.attrs). NEW MEDIUM finding #2723 (Cat 4 + Cat 5): (a) bounded dask+numpy path (_process_dask -> da.map_overlap with meta=np.array(())) declared output dtype float64 while the chunk fn returns float32 and numpy/cupy/dask+cupy + the unbounded KDTree path all declare float32; docstrings show dtype=float32. Fix: meta=np.array((), dtype=np.float32). (b) dask backends leaked an internal dask op name (_trim-<hash>, _kdtree_chunk_fn-<hash>, asarray-<hash>) into result.name while numpy/cupy return None. Fix: assign result.name=None after construction in all 3 public funcs (xarray ignores a name=None kwarg for named dask arrays, so the reset must happen post-construction). Same .name-leak class as zonal #2611. PR #2728 off child branch deep-sweep-metadata-proximity-2026-05-29-01. New parametrized regression test test_output_metadata_consistent_across_backends asserts declared dtype float32 + name None across all 4 backends x 3 funcs x bounded/unbounded; full test_proximity.py suite 93 passed. No other CRITICAL/HIGH/MEDIUM/LOW findings."
11
12
  rasterize,2026-06-09,3087,MEDIUM,1,GeoDataFrame .crs dropped on no-like path (Cat 1); fixed via #3087 emitting attrs crs/crs_wkt when output has no CRS. like-path attrs/coords/dims/nodata verified live on all 4 backends (CUDA available); Cats 2-5 clean.
@@ -3,13 +3,13 @@ aspect,2026-05-29,SAFE,compute-bound,1,2688,"dask+cupy geodesic densified full l
3
3
  balanced_allocation,2026-04-16T12:00:00Z,WILL OOM,memory-bound,8,1114,"Re-audit 2026-04-16 after PR 1203 float32 fix. 8 HIGH found (friction.compute L339, argmin.compute in iter loop L182, double all_nan recompute L206, stacked cost_surfaces allocation). Covered by existing documented limitation on #1114. Not refiled."
4
4
  bilateral,2026-03-31T18:00:00Z,SAFE,compute-bound,0,,
5
5
  bump,2026-04-16T12:00:00Z,SAFE,compute-bound,0,1206,Re-audit 2026-04-16: fix verified SAFE. No HIGH findings. MEDIUM: CuPy backend runs CPU kernel then transfers to GPU (documented limitation).
6
- classify,2026-04-16T18:00:00Z,SAFE,compute-bound,0,fixed-in-tree,"Fixed-in-tree 2026-04-16: _run_dask_head_tail_breaks now persists data_clean once and fuses mean+head_count per iter (912ms -> 339ms, 0.37x IMPROVED); added _run_dask_box_plot that samples via _generate_sample_indices instead of boolean fancy indexing on dask array; _run_dask_cupy_box_plot likewise. 85 existing classify tests pass."
6
+ classify,2026-06-20,RISKY,graph-bound,1,3412,"Re-audit 2026-06-20 (CUDA host). 1 HIGH: _generate_sample_indices >10M branch used RandomState.choice(replace=False) which builds a full arange(num_data) permutation -> O(num_data) host alloc (160MB for 20M pop, OOM at 30TB) despite docstring claiming O(num_sample). Backed dask/dask+cupy natural_breaks/maximum_breaks/quantile/percentiles/box_plot. Fixed via np.random.default_rng().choice (Floyd, O(num_sample), still deterministic); peak 160MB->0.4MB. Other paths SAFE: head_tail_breaks already persists+fuses; box_plot samples; cupy kernels low-register; no .values/np.asarray-on-dask/.compute-in-loop. 93 classify tests pass incl GPU."
7
7
  contour,2026-03-31T18:00:00Z,SAFE,compute-bound,0,,
8
8
  convolution,2026-03-31T18:00:00Z,SAFE,compute-bound,0,,
9
9
  corridor,2026-03-31T18:00:00Z,SAFE,compute-bound,0,,
10
10
  cost_distance,2026-06-15,RISKY,memory-bound,1,3342,"Perf sweep 2026-06-15. HIGH: bounded map_overlap branch in _cost_distance_dask gated on full dims (pad>=height/width) not chunk size; pad>chunk collapses to single chunk (#880-class OOM, verified npartitions=1 at chunks=10/pad=96). Fixed: compare pad vs max chunk dim, route to iterative when pad>=chunk (matches GPU path L484). dask+cupy path already correct. Register count 37 (no pressure). nanmin().compute() L478/L1149 intentional scalar. iterative tile_cache full-dataset materialization is documented MemoryError-guarded design (#1118). All 56 tests pass incl GPU."
11
11
  curvature,2026-03-31T18:00:00Z,SAFE,compute-bound,0,,
12
- dasymetric,2026-03-31T18:00:00Z,SAFE,memory-bound,0,1126,Memory guard added to validate_disaggregation. Core disaggregate uses map_blocks.
12
+ dasymetric,2026-06-20,SAFE,compute-bound,0,3408,"MEDIUM: disaggregate() looped per-zone building full-shape boolean masks (O(n_zones x n_pixels)); vectorized via searchsorted + np.add.at to O(n_pixels + n_zones) in PR for #3408 (numpy core, dask per-chunk sums, distribute step). cupy is documented CPU fallback (not flagged); eager zone-sum dask.compute() is required global reduction, bounded per chunk (not flagged). CUDA+cupy validated end-to-end on host."
13
13
  diffusion,2026-03-31T18:00:00Z,WILL OOM,memory-bound,2,1116,Scalar diffusivity now passed as float to chunks. DataArray diffusivity passed as dask array via map_overlap.
14
14
  edge_detection,2026-03-31T18:00:00Z,SAFE,compute-bound,0,,
15
15
  emerging_hotspots,2026-03-31T18:00:00Z,SAFE,compute-bound,0,,
@@ -28,7 +28,7 @@ interpolate_spline,2026-06-04,SAFE,compute-bound,0,,"scope=spline-only. Audited
28
28
  kde,2026-04-14T12:00:00Z,SAFE,compute-bound,0,,Graph construction serialized per-tile. _filter_points_to_tile scans all points per tile. No HIGH findings.
29
29
  mahalanobis,2026-03-31T18:00:00Z,SAFE,compute-bound,0,,False positive. Numpy path materializes by design. Dask path uses lazy reductions + map_blocks.
30
30
  mcda,2026-06-10,SAFE,memory-bound,2,3150,"2 HIGH fixed in PR #3158: owa() dask path crashed (da.sort does not exist; memory guard pointed users at the crashing path) and wpm validation ran one compute() per criterion. MEDIUM fixed in PR #3159 (#3151): cupy piecewise + dask+cupy piecewise/categorical raised TypeError via np.asarray on cupy chunks. MEDIUM fixed in PR #3160 (#3152): monte_carlo sensitivity materialized full dask dataset (now chunk-bounded map_blocks, ~8 tasks/chunk at n_samples=1000) and crashed on cupy via per-sample .values; constrain() deep copy dropped. LOW documented, not fixed: fuzzy_overlay builds ones via layers[0]*0+1; _categorical does one full-array pass per mapping key. Verdict SAFE assumes the 3 PRs merge (pre-fix: WILL OOM for MC-on-dask, owa dask broken). GPU paths validated on CUDA host (cupy 13.6)."
31
- morphology,2026-03-31T18:00:00Z,SAFE,compute-bound,0,,
31
+ morphology,2026-06-20,SAFE,compute-bound,1,3401,memory guard fired on full lazy-dask shape (false MemoryError); skip guard for dask-backed inputs; eager numpy/cupy guard preserved
32
32
  multispectral,2026-05-02,SAFE,compute-bound,0,,"Re-audit 2026-05-02 after PRs 1292 (true_color memory guard) and 1301 (validate_arrays in true_color). Verified SAFE. No HIGH. MEDIUM: da.stack in _true_color_dask/_true_color_dask_cupy at L1702/L1731 creates (1,1,1,1) chunks along band axis (4 bands so impact is minor, scheduling overhead not OOM). LOW: np.zeros((h,w,4)) at L1681 then full overwrite -- np.empty would suffice. All 17 indices use plain map_blocks with no halo; 8192x8192 ndvi graph is 80 tasks, evi/arvi/ebbi 112 tasks."
33
33
  normalize,2026-03-31T18:00:00Z,SAFE,compute-bound,0,1124,Boolean indexing replaced with lazy nanmin/nanmax/nanmean/nanstd.
34
34
  pathfinding,2026-04-15T12:00:00Z,SAFE,compute-bound,0,false-positive,Downgraded. CuPy .get() is required -- A* has no GPU kernel. Per-pixel .compute() is only 2 calls for start/goal validation. seg.values in multi_stop_search collects already-computed results for stitching.
@@ -48,4 +48,4 @@ terrain_metrics,2026-03-31T18:00:00Z,SAFE,memory-bound,0,,
48
48
  viewshed,2026-04-05T12:00:00Z,SAFE,memory-bound,0,fixed-in-tree,Tier B memory estimate tightened from 280 to 368 bytes/pixel (accounts for lexsort double-alloc + computed raster). astype copy=False avoids needless float64 copy.
49
49
  visibility,2026-06-10,RISKY,compute-bound,0,3185,"cumulative_viewshed recomputed dask source per observer (fixed #3185: materialise once when no max_distance); graph grows ~64 tasks/observer with N; line_of_sight single-transect cheap; MEDIUM count temp .astype per observer (LOW, not fixed)"
50
50
  worley,2026-03-31T18:00:00Z,SAFE,compute-bound,0,,
51
- zonal,2026-05-27,SAFE,compute-bound,0,2526,"Pass 2 (2026-05-27): re-audit identified 3 MEDIUM findings. (1) zonal_apply 3D dask path: da.stack(layers, axis=2) left output chunks at size 1 along axis 2 -- filed #2526 and fixed by rechunking back to values_data.chunks[2] in _apply_dask_numpy (zonal.py:1691) and _apply_dask_cupy (zonal.py:1731). Confirmed via graph probe: 256x256 raster chunks=(64,64) 3 bands previously yielded chunks[2]=(1,1,1); now (3,). 1 new test (test_apply_dask_3d_axis2_rechunked_2526). 126 existing zonal tests pass. (2) _stats_cupy (zonal.py:588-608): per-zone x per-stat Python loop with cupy.float_(result) forces O(n_zones * n_stats) GPU<->CPU sync points; not fixed in this pass (CUDA-native rewrite needed, larger refactor). (3) _parallel_variance @delayed reduce iterates over all blocks in driver memory; for very large block counts the single-task merge becomes scheduler-bound but is not OOM since per-block arrays are O(n_zones). Not fixed (algorithmic refactor needed). Dask graph probe: stats(7 stats) on 2560x2560 chunks=256 -> 4449 tasks (44/chunk); stats(mean only) -> 823 tasks (8/chunk); crosstab -> 304 (3/chunk); hypsometric_integral -> 300 (3/chunk). All under 50K cap. SAFE/compute-bound verdict holds. | Fixed-in-tree 2026-04-16: rewrote hypsometric_integral dask path. Eliminated double-compute (_unique_finite_zones removed, each block discovers own zones). Replaced np.stack (O(n_blocks * n_zones) scheduler memory) with streaming dict-merge (O(n_zones)). 29 existing tests pass."
51
+ zonal,2026-06-17,SAFE,compute-bound,0,3381,"Pass 3 (2026-06-17, deep-sweep): 0 HIGH, 1 new MEDIUM found and fixed (#3381/PR pending). _sort_and_stride 3D branch (zonal.py:317) did copy.deepcopy(values).reshape(...) then a per-row Python loop reindexing by sorted_indices; replaced with one vectorized fancy-index values.reshape(n,-1)[:, sorted_indices] (fancy index returns fresh array, input unmutated -- verified), dropping a full-array copy and the loop; orphaned 'import copy' removed. Runs in crosstab() numpy + per-dask-block; cupy rejects 3D so unaffected. New test test_sort_and_stride_3d_no_mutation_and_parity; 194 zonal + 43 backend-coverage tests pass. Carry-over MEDIUMs unchanged (not refixed this pass): _stats_cupy per-zone x per-stat loop with cupy.float_() forces O(n_zones*n_stats) device->host syncs (needs CUDA-native rewrite); _parallel_variance @delayed reduce iterates all blocks in driver (scheduler-bound at huge block counts, not OOM since per-block O(n_zones)). #2526 3D-apply rechunk still fixed. Dask graph counts from Pass 2 hold (stats 7-stat 4449 tasks/44 per chunk, stats-mean 823/8, crosstab 304/3, hypsometric_integral 300/3 on 2560x2560 chunks=256; code paths unchanged, _sort_and_stride fix changes per-task work not task count) -- all under 50K cap. This pass's graph re-probe was killed by host memory pressure from parallel sibling sweeps; counts taken from prior recorded run. SAFE/compute-bound holds. CUDA available on host (cupy 13.6) but cupy zonal stats path is 2D-only and the fix is numpy/dask+numpy."
@@ -1,16 +1,22 @@
1
1
  module,last_inspected,issue,severity_max,categories_found,notes
2
- aspect,2026-05-29,2683,MEDIUM,1,E402+E305 line 38: from xrspatial.geodesic import block sat below _geodesic_cuda_dims; moved up with top-of-file imports. E501 lines 219/263: wrapped two _run_gpu_geodesic_aspect kernel-launch calls (101/109 chars). Cat 4 isort reviewed but NOT applied: slope.py/curvature.py use one-import-per-line for xrspatial.utils so raw isort would make aspect inconsistent. Cat 2/3/5 grep clean. PR #2740. 82 aspect+geodesic tests pass.
2
+ aspect,2026-06-21,3437,MEDIUM,4,flake8 clean; isort drift (geodesic wrap + duplicate utils import line); Cat1/2/3/5 none; fixed via PR for issue 3437
3
+ classify,2026-06-20,3402,HIGH,3;4,"F401 unused not_implemented_func import; isort ordering (cmath group, dataset_support before utils). No Cat 1/2/5. Fixed in rockout PR."
3
4
  contour,2026-05-29,2698,HIGH,3,"F821 line 557: contours() return annotation ""gpd.GeoDataFrame"" referenced gpd not bound at module scope (only imported inside _to_geopandas). Fixed via TYPE_CHECKING-guarded import geopandas as gpd, matching polygonize.py. No runtime change; geopandas stays optional. isort clean. Cat 1/2/4/5 clean. 24 contour tests pass. PR open."
4
5
  cost_distance,2026-06-15,3339,HIGH,1;3;4;5,"Cat 3 F401: removed unused 'from functools import partial' (L32, refactor leftover, not re-exported). Cat 5 mutable default target_values: list = [] was found here too, but the parallel api-consistency PR #3348 fixed it first (same None-sentinel normalization, merged to main); on reconciliation this PR's duplicate source change and its two target_values tests were dropped, so PR #3350 is now lint-only. Cat 1: E302 L63 (1->2 blank lines before @ngjit _heap_push; placed blanks between comment divider and decorator to satisfy both E302 and isort) + E127 x5 (_cost_distance_dask_cupy L461-462, _cost_distance_dask_iterative L1005-1007 signature continuation over-indent). Cat 4 isort: dataset_support before utils, utils from-import reflowed to 100 cols. Cat 2 grep clean. flake8+isort clean on module after fix; cost_distance tests pass (CUDA available). Pre-existing test-file lint (F401 L314/L504, E201 L449, isort drift) left untouched - out of module scope. PR open."
6
+ dasymetric,2026-06-20,3411,HIGH,1;3;4,"F401 is_dask_cupy unused (HIGH); E128 line 323 continuation indent (MED); isort utils import block (MED). Fixed + flake8/isort clean, 61 tests pass. PR #3411. Issue-create was denied by auto-mode classifier (judged LOW); PR went through."
7
+ diffusion,2026-06-20,3421,HIGH,3;4,F401 unused not_implemented_func (HIGH); isort import-ordering drift (MEDIUM); no Cat1/2/5. Fixed in PR off issue 3421.
8
+ fire,2026-06-19,3395,HIGH,3;4,Cat3 F401 math.log unused + F841 S_T unused local; Cat4 isort utils import reorder. No Cat1/2/5. Fixed in #3395 rockout.
5
9
  focal,2026-05-29,2731,HIGH,3;4;5,"F401 not_implemented_func (import line 36, unused, not re-exported). isort: stdlib reorder (import math before from-imports), dropped stray blank lines in import groups, alphabetised+rewrapped convolution/utils from-imports, moved dataset_support import into order. Cat 5: mutable default excludes=[np.nan] in mean() (line 238) -> None sentinel, resolved to [np.nan] in body; never mutated so behaviour preserved; regression test test_mean_default_excludes_does_not_leak added. Cat 1/2 clean. 115 focal tests pass. PR pending."
6
10
  geotiff,2026-06-14,3329,MEDIUM,4,"Re-sweep after #3259: flake8 baseline 0 across all 33 production files (flake8 7.3.0). Cat 4 only: isort (line_length=100) drift in _writers/eager.py (the from .._validation import block wrapped at ~80 cols, not 100); rewrapped, no behavioural change. Cat 1/2/3/5 clean: no E/W/F codes; grep hits for == False (comment), ': dict'/': list' annotations, and type=getaddrinfo kwarg are all false positives, not shadowed builtins or comparisons. isort+flake8 clean after fix; import smoke test ok. PR via #3329."
7
11
  hydro-d8,2026-05-29,2705,HIGH,1;3;4,"flake8+isort over the 13 D8 files only (dinf/mfd out of scope). Cat 3 HIGH: F401 x2 (flow_length_d8 function-local _compute_accum_seeds never called; snap_pour_point_d8 module-level cuda_args unused) - both confirmed dead, no re-export. Cat 1: E127/E128 continuation-indent x90 (mostly multi-line def signatures); E302/E303 blank-line cluster in watershed_d8; E501 x4 (flow_path_d8 + snap_pour_point_d8, wrapped ternaries). Cat 4: isort import-block reordering on all 13 files. No Cat 2 (W-codes), no Cat 5 (grep clean: no bare except, mutable defaults, ==None/==True, or shadowed builtins). flake8+isort clean after fix; 385 D8 tests pass. flow_direction_d8 needed manual blank-line placement to satisfy both isort and E302."
8
12
  interpolate,2026-06-12,3286,HIGH,3;4,"Full subpackage sweep (_idw, _kriging, _spline, _validation). Cat 3 F401: unused 'import math' in _idw.py L5 (IDW kernels are pure arithmetic; _spline.py keeps math for math.log in TPS kernels; not re-exported, __init__ exports only idw/kriging/spline). Cat 4 isort: _idw.py + _spline.py 5-line xrspatial.utils from-import reflowed to 2 lines under line_length=100, matching _kriging.py from #2916. Cat 1/2/5 clean (no E/W codes; grep: no bare except, mutable defaults, ==None/True, shadowed builtins). flake8+isort clean after fix; 66 interpolation tests pass (CUDA available). PR open."
9
13
  interpolate-kriging,2026-06-04,2916,MEDIUM,1;4,"flake8 E128 x2: continuation-line under-indent at the _chunk_var kriging-predict calls in _kriging_dask_numpy (L234) and _kriging_dask_cupy (L324); re-indented to visual-indent column. Cat 4 isort: 5-line from xrspatial.utils (...) block collapses to one 88-char line under line_length=100. Cat 2/3/5 grep clean (no W-codes, F-codes, bare except, mutable defaults, ==None/True, or shadowed builtins). flake8+isort clean after fix; 14 kriging tests pass. PR open."
10
14
  mcda,2026-06-10,3143,HIGH,3;4,F401 dead function-local warnings import in standardize.py; isort drift in weights.py combine.py __init__.py; flake8 otherwise clean; Cat 5 greps clean; fixed via PR for #3143
15
+ morphology,2026-06-20,3400,HIGH,3;4,"F401 xr/has_cuda_and_cupy/not_implemented_func + F841 size (dead); isort ordering. /rockout PR off issue 3400. flake8+isort clean, 74 tests pass."
16
+ multispectral,2026-06-20,3428,HIGH,3;4,F401 unused not_implemented_func (Cat3 HIGH); isort dataset_support before utils (Cat4 MEDIUM); Cat1/2/5 clean; fixed via #3428
11
17
  polygon_clip,2026-06-10,3184,HIGH,3;4,"Cat 3 F401: dropped unused typing imports Sequence/Tuple/Union (line 10, only Optional used) and the dead 'import cupy' inside the _apply_mask dask+cupy closure (line 245); the other 'import cupy' at line 261 is used (cupy.asarray) and kept. None re-exported (__init__ re-exports only clip_polygon). Cat 4 isort: reflowed the 5-name xrspatial.utils from-import block to line_length=100. Cat 1/2/5 grep clean (no E/W codes, no bare except, mutable defaults, ==None/True, or shadowed builtins). flake8+isort clean after fix; 23 test_polygon_clip tests pass. PR open."
12
18
  polygonize,2026-05-27,2534,HIGH,1;3;4,"F401 line 58 (is_cupy_array unused, not re-exported). E127 lines 83/88 (overload continuation indent in generated_jit). isort: 5-line .utils import block collapses to one line at 100-char limit. Cat 2 clean. Cat 5 grep clean."
13
- proximity,2026-05-29,2725,HIGH,1;3;4;5,"F841 line 1274 original_chunks dead local in unbounded dask+cupy branch (refactor leftover). Cat 5 mutable default target_values: list = [] in proximity/allocation/direction -> None sentinel, normalized to [] in body (never mutated, behaviour preserved). E128 line 291 np.where continuation under-indent in _vectorized_calc_direction. isort: re-sorted xrspatial import block + blank line after inline import cupy as cp. flake8+isort clean after fix; 69 proximity tests pass + new parametrized regression test. Pre-existing E127 (test_proximity.py 726/752) + test-file isort drift left untouched (out of module scope)."
19
+ proximity,2026-06-18,,MEDIUM,4,"Re-audit 2026-06-18 (file last modified 2026-06-14). Cat 4 isort: from xrspatial.utils import (...) block wrapped one name early for line_length=100; pulled has_cuda_and_cupy onto first continuation line (both lines <=100, flake8 clean). isort+flake8 now clean. Fix applied + verified imports resolve. Cat 1/2/3 clean. Cat 5 grep: target_values: list = None on L1540/1700/1859 are type annotations not builtin-shadowing vars (false positive); mutable-default already None-sentinel from prior #2725. MEDIUM. /rockout issue+PR creation BLOCKED by auto-mode classifier (external publish denied); fix committed to deep-sweep branch only."
14
20
  rasterize,2026-05-27,2503,HIGH,1;3,F401 line 15 + F811 line 1193 (paired: local import warnings shadowed unused module-level import); E306 line 1775 (nested @cuda.jit). isort clean. Cat 5 grep clean. Fix in PR #2507.
15
21
  reproject,2026-06-09,3083,HIGH,3;4,"Re-sweep after 2026-06-08 cleanup (#3049): new findings confined to __init__.py. Cat 3 HIGH: F401 _merge_arrays_cupy imported from ._merge but never used (function kept in _merge.py, noted as dead code - no callers anywhere); F841 _use_native_cuda assigned L563/L602 never read (leftover from #2620 unconditional native-CUDA resampling). Cat 4: isort regrouped top-of-file import blocks + function-local _vertical import (~L1278). Cat 1/2 clean (flake8 reported only the two F-codes). Cat 5 grep clean (d: dict / entry: dict in _lite_crs.py are annotations, not shadows). flake8+isort clean after fix; 432 reproject tests pass incl. 43 GPU (CUDA available). PR open."
16
22
  resample,2026-05-27,2543,MEDIUM,4,isort drift only: 4 multi-line parenthesised imports collapsed to single/one-per-line under line_length=100 (top-of-file scipy.ndimage + xrspatial.utils; local cupyx imports in _nan_aware_interp_cupy and _interp_block_cupy); two blank-line nits after import math in _run_dask_numpy/_run_dask_cupy. flake8 clean. Cat 5 grep clean. 169 resample tests pass.
@@ -1,7 +1,10 @@
1
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."
2
+ aspect,2026-06-21,3439,MEDIUM,2,"#3439 (Cat 2): Inf/-Inf elevation input and all-NaN raster were untested. Added Inf-input finite-neighbors + 4-backend parity, and all-NaN -> all-NaN shape-preserved for aspect/northness/eastness on all 4 backends; GPU-validated (CUDA available). Both behaviors defined and consistent across backends -> coverage gap, not a bug (supersedes prior 'Inf possible source bug, not filed' note). Prior: #2742 degenerate shapes + geodesic boundary modes; #2829 northness/eastness geodesic branch -- all still covered."
3
+ classify,2026-06-20,,MEDIUM,3;4,"Deep-sweep 2026-06-20 test-coverage on a CUDA host. Backend matrix was already complete: all 10 public classifiers (binary/reclassify/quantile/natural_breaks/equal_interval/std_mean/head_tail_breaks/percentiles/maximum_breaks/box_plot) x 4 backends present and green (Cat 1 no gap). Cat 2 (NaN/Inf) covered: input_data() fixture embeds -inf/nan/+inf at corners on every numpy/dask/cupy test, plus all-nan/all-inf/all-same dedicated tests. Cat 5 covered via general_output_checks(verify_attrs=True) asserting attrs/dims/coords on every backend test. Found two MEDIUM gaps: Cat 3 (no 1x1 single-pixel, no Nx1/1xN strip tests for any classifier) and Cat 4 (the _validate_scalar k<min_val guards were never exercised: quantile/natural_breaks/maximum_breaks k>=2, equal_interval k>=1). Probed live: single-pixel and strip shapes all work correctly (no source bug -- lone finite pixel -> class 0; binary match -> 1; reclassify -> bin's new_value), so these are untested-but-passing paths. Added test-only (numpy, since the gaps are in shared CPU-side validation + bin-edge logic and the 4-backend dispatch is already fully covered): test_classify_single_pixel, test_binary_single_pixel, test_reclassify_single_pixel, test_classify_nx1_strip, test_classify_1xn_strip, and 4 k-below-min ValueError tests. All RAN and PASSED on the CUDA host; full file 98 passed. classify.py untouched. LOW (documented, not fixed): empty raster (0 rows) raises on numpy equal_interval (zero-size reduction) and differs across backends -- degenerate input, not pinned. Non-square cellsize not exercised (classifiers ignore cellsize, so out of scope)."
3
4
  contour,2026-06-08,2704;2710;3044,MEDIUM,1;2,"Pass 2 (2026-06-08, deep-sweep test-coverage): re-swept on a CUDA host. Verified issue #2704 is CLOSED (fixed 2026-06-01 via #2749, kernel now uses np.isfinite at contour.py:73-74); the two prior xfail(strict) #2704 pins were already flipped to plain passing assertions in TestInfHandling (test_inf_corner_no_nan_coords / test_neg_inf_corner_no_nan_coords) -- no stale xfail remained. Found one MEDIUM Cat 1+2 gap: no cross-backend parity test fed NaN input -- TestBackendEquivalence uses elevation_raster_no_nans and test_partial_nan is numpy-only, so numpy-interior NaN-skip vs dask NaN-halo (da.overlap) parity at chunk edges was unpinned. Filed #3044, added TestNaNBackendParity (3 tests: dask, cupy, dask+cupy each assert _segments_by_level equality vs numpy on a partial-NaN ramp with a NaN edge row + interior NaN cell inside a non-edge chunk). All 3 RAN and PASSED on a CUDA host; full file 89 passed, 0 skipped. Probed and verified now-resolved: the prior-pass LOW items are no longer real gaps -- the levels=None all-NaN early-return IS asserted (result==[]) on all 4 backends (numpy L148/dask L163/cupy L178/dask+cupy L194) plus geopandas (test_geopandas_all_nan_keeps_crs). LOW (documented, NOT fixed): non-square cellsize (res[0]!=res[1]) still never exercised -- all tests use create_test_raster res (0.5,0.5); probed live that anisotropic coords transform correctly (y scaled 2.0, x scaled 0.5 -> crossing x=1.25, y spans 0..8), works, so it is a LOW coverage gap not a bug. Cat 3 1x1/Nx1/1xN remain rejected by the >=2x2 guard (tested). Test-only PR for #3044; contour.py untouched. | 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
5
  cost_distance,2026-06-16,3367,MEDIUM,1;2,"Pass (2026-06-16 deep-sweep test-coverage, CUDA host). cost_distance is heavily tested: 1122 test lines for 1354 src lines, all 4 backends parametrized + regression tests for #1191/#880/#1252/#1262/#3340/#3341/#3343/#3344. Found one MEDIUM Cat 1+2 gap: _cost_distance_dask f_min<=0 early return (all-impassable friction, finite max_cost -> da.full NaN preserving chunks) was unreached -- numpy equiv covered by test_source_on_impassable_cell, iterative dask by test_iterative_narrow_corridor, but the bounded map_overlap wrapper shortcut was not. Filed #3367, added test_dask_all_impassable_friction_returns_nan (all-zero friction, dask+numpy chunks(3,3), max_cost=5; asserts all-NaN, dask-backed, npartitions>1). RAN + PASSED; -W error::UserWarning confirms early return taken (no iterative warning). Full file 85 passed on CUDA host. LOW (documented, not fixed): non-square cellsize numeric correctness untested (_make_meta_raster uses res=(2,3) but test_metadata_preserved checks metadata only)."
6
+ dasymetric,2026-06-20,3407;3406,HIGH,2;3;4;5,"deep-sweep test-coverage on a CUDA host (CUDA available, GPU tests ran). Module is well covered (813 test loc / 834 src): 4-backend equivalence for disaggregate weighted+binary, conservation, NaN/nodata/negative-weight, limiting_variable + cupy/dask NotImplemented guards, pycnophylactic numpy+cupy+dask-raises, validate_disaggregation all backends, memory guards (#1261). Filed #3407 (test-only) for real gaps and added 4 new test classes (11 passed, 2 xfailed). Cat5 HIGH: metadata (attrs res/crs + coords) never asserted -> TestMetadataPreservation (numpy/dask). Cat3 HIGH: true 1x1 raster untested (only 1x2 strip) -> TestSinglePixel for disaggregate weighted/binary + pycnophylactic (degenerate no-shift smoothing) + dask parity. Cat2 MEDIUM: Inf weight collapses zone total to 0 (silent conservation break) -> TestInfWeight pins current behaviour. Cat4 MEDIUM: 3-class limiting_variable (multi-break + per-class caps) untested despite docstring -> TestLimitingVariableThreeClass. SOURCE BUG found (filed #3406, NOT fixed - test-only sweep): pycnophylactic raises ValueError (np.nanmax on zero-size array) when no pixel is valid for smoothing (all-NaN zones or no zone id in values); disaggregate handles same input gracefully (all-NaN). Pinned with TestPycnophylacticEmptyValid xfail(strict, raises=ValueError) -> flips red when #3406 fixed. LOW (documented, not fixed): non-square cellsize never exercised (all tests use res 0.5/0.5); disaggregate cupy/dask+cupy 1x1 + metadata not separately added (eager numpy gap was the real one, GPU dispatch already covered by TestCrossBackend)."
7
+ diffusion,2026-06-20,3422,HIGH,1;2;3;4,"Pass 1 (2026-06-20, deep-sweep test-coverage, CUDA host). diffuse() dispatch table registers all 4 backends but test_diffusion.py only exercised numpy + dask+numpy. Cat 1 HIGH: cupy (_diffuse_cupy/_diffuse_step_gpu) and dask+cupy (_diffuse_dask_cupy/_diffuse_chunk_cupy) registered but never invoked -- no test ran them. Cat 4 HIGH: boundary accepts nan/nearest/reflect/wrap; only nearest+wrap tested, reflect had none. Cat 3 HIGH: 1x1 single-pixel and Nx1/1xN strip rasters never tested. Cat 2 MEDIUM: NaN tested numpy-only; Inf and all-NaN inputs untested. Filed #3422, added 14 tests (PR #3424, test-only, source untouched): cupy/dask+cupy parity vs numpy (incl. spatially-varying alpha + NaN propagation), reflect boundary across all 4 backends, 1x1 + Nx1 + 1xN (numpy + chunked dask strip), all-NaN stays NaN, Inf contamination smoke test. All 14 RAN+PASSED on a CUDA host; the 4 cupy/dask+cupy tests genuinely executed (not skipped); full file 39 passed. All paths verified correct before the tests were added -- coverage gap, not a bug. LOW (documented, not fixed): non-square cellsize (res[0]!=res[1]) never exercised -- diffuse uses res[0] as dx and assumes square cells; empty 0-row/0-col raster untested; asv benchmark absent; 'nan' boundary-mode edge=NaN behaviour not directly asserted on diffuse (covered indirectly via wrap/nearest)."
5
8
  focal,2026-06-10,3220;3219;3225,HIGH,1;2;3;4,"Deep-sweep 2026-06-10 on CUDA host, all 4 backends executed. Filed #3220 (coverage) and added 36 tests in PR branch: Inf inputs for mean/focal_stats (HIGH Cat2 - no Inf test existed anywhere), mean NaN input (HIGH Cat2 - default excludes=[nan] semantics never asserted), 1x1 + 1xN/Nx1 strips (HIGH Cat3), empty 0-row raster numpy-only (MEDIUM Cat3), mean passes=2 == mean(mean) and excludes sentinel -9999 behavioral tests (MEDIUM Cat4), dask+cupy non-default boundary modes for mean/apply/focal_stats (MEDIUM Cat1/4). Bugs surfaced, filed separately (NOT fixed here): #3219 hotspots silently returns all zeros on Inf input (nan global std passes the std==0 guard, all 4 backends); #3225 empty raster works on numpy but crashes cupy (raw CudaAPIError) and dask (map_overlap depth ValueError). hotspots+Inf and non-numpy empty behavior left unpinned until those are fixed. Backend matrix for the 4 public funcs was already solid (all 4 backends + parity); boundary modes covered except dask+cupy. Siblings filed #3214-3217 same day (dtype/docstring/apply-default-func) - no overlap."
6
9
  geotiff,2026-06-12,3266,MEDIUM,1,"Pass 22 (2026-06-12, deep-sweep test-coverage): delta audit of the ~20 commits since pass 21 (06-09..06-12, mostly pack/unpack fixes + #3241 GPU streaming writer + coregister #3254/#3248). Filed #3266 (tests). Cat 1 MEDIUM: pack=True gained working gpu/dask+gpu support in #3240, but three pack features were tested numpy+dask only: float32 width preservation (#3080, test_pack_float_width_3080.py), nodata kwarg fill (#3168, test_pack_nodata_kwarg_3168.py), band-subset per-band SCALE/OFFSET rewrite (#3161, test_pack_band_subset_3161.py). Live probe on this CUDA host: all six gpu/dask+gpu legs pass today (no source bug, pure coverage gap). Added one gpu/dask-gpu parametrized round-trip test per file (6 tests, requires_gpu, RUN+passing locally) and fixed two stale docstrings claiming unpack/pack is CPU-only (wrong since #3075/#3240). Verified NOT gaps this pass: #3128 int64 sentinel tests cover eager+dask+gpu; #3241 streaming writer landed with byte-identical band-first/band-last/BytesIO/small-buffer tests; #3104 scale-zero rejection has gpu legs; #3169 revived the dead compression-corpus oracle gate. Out of scope: coregister=True lives in accessor.py (excluded module); its multi-band + polar gaps are documented as experimental caveats in docs/source/reference/geotiff.rst (#3248). LOW (carried, documented not fixed): Inf as the declared nodata sentinel never tested. || PREVIOUS: Pass 21 (2026-06-09, deep-sweep test-coverage): filed #3114 (tests) + #3112 (source bug). Cat 1 HIGH: to_geotiff(pack=True) round-trip was tested only on numpy and dask+numpy (write/test_pack_3064.py); #3075 made unpack=True work on gpu and dask+gpu reads, but no test packed a GPU-read array back. Live probe on this CUDA host: BOTH GPU legs crash today -- eager gpu raises AttributeError (cupy has no astype, the known cupy 13.6/xarray 2025.12 where/astype incompat) and dask+gpu raises TypeError (numpy fill value inside cupy.where) -- both from _pack's out.fillna(nodata) in _attrs.py; _writers/gpu.py says the pre-dispatch re-pack is supposed to make every write path work. Source bug filed as #3112; test-only PR adds test_pack_round_trip_gpu (gpu + dask-gpu params, requires_gpu, xfail(strict=True) on #3112 so the fix flips them loudly) and fixes the stale module docstring claiming GPU rejects mask_and_scale. Ran on CUDA host: 13 passed, 2 xfailed. Verified NOT gaps this pass (probed before flagging): empty/zero-band writer guard is covered (test_basic.py 2075/2095 blocks incl. gpu + dask + streaming entry points); degenerate shapes covered on all 4 read backends (read/test_degenerate_shapes.py); overview_resampling all 7 modes parametrized; missing_sources raise/warn + invalid, band_nodata first/invalid, unpack on all 4 backends, masked/parse_coordinates/lock/cache/default_name/name-deprecation all exercised; attrs contract per-backend (attrs/test_contract.py). LOW (documented, not fixed): Inf as the declared nodata sentinel is never tested (only one nan+inf data round-trip in test_edge_cases.py). || PREVIOUS: Pass 20 (2026-06-06, deep-sweep test-coverage): filed #2984 and added test_writer.py degenerate-shape GPU write coverage (Cat 1 backend + Cat 3 geometric edge). Read side already covers 1x1/1xN/Nx1 on all 4 backends (read/test_degenerate_shapes.py) and the dask streaming writer covers them (integration/test_dask_pipeline.py); the GPU write path was the gap (smallest shape in gpu/test_writer.py was 2x2). Added test_write_geotiff_gpu_degenerate_round_trip (1x1/1xN/Nx1 x none/deflate) + test_to_geotiff_dask_gpu_degenerate_round_trip (dask+cupy via gpu=True). 9 new tests RUN+passing on a CUDA host. Verified paths work first (not a source bug); transform supplied explicitly via attrs. Wider tree audit (~92k test LOC vs ~33k source): rioxarray-compat (#2961), bbox NaN/Inf/rotated, 8-backend parity matrix, codec round-trips already covered -- no other real gaps. | Pass (2026-06-05 test-coverage sweep): mature module (~31k src / ~124k test LOC, 9 test dirs). Exhaustive existing coverage -- parity/test_backend_matrix.py runs all 4 backends + VRT + HTTP + fsspec; golden_corpus full-manifest parity; read_rioxarray_compat_2961 covers masked/mask_and_scale/parse_coordinates/default_name on eager+dask. Cat1+Cat3 gap found (MEDIUM): degenerate-shape READS (1x1/1xN/Nx1) were tested only on the eager numpy reader (test_edge_cases.py) and the dask streaming WRITE path (integration/test_dask_pipeline.py); the windowed dask READ (chunks=) and GPU READ (gpu=True) on a single-pixel dimension were never exercised (smallest dask-read source in read/test_tiling is 8x8/2x32, parity fixtures 32x32/64x64). Probed: paths work today, no source bug -- pure coverage gap. Added read/test_degenerate_shapes.py (18 tests): dask read x{chunks 1,3,4} x{1x1,1xN,Nx1} + coord/transform/crs parity + GPU read + dask+gpu read. GPU cells RAN and PASSED on this CUDA host (grid-size-1 launch validated). Fixture supplies explicit attrs['transform'] (writer cannot infer pixel size from a 1-element coord axis). Branch deep-sweep-test-coverage-geotiff-degenerate-read-01. NOTE: pre-existing union-merge CRLF/duplicate-record corruption in this CSV left untouched -- appended one clean record; DictReader last-write-wins picks this one."
7
10
  idw,2026-06-04,2919,HIGH,1;4,"cupy/dask+cupy backends untested (Cat1 HIGH); GPU k-reject error path untested (Cat4 MED). Added 6 GPU tests, validated on CUDA host. Inf-in-points (Cat2) and attrs-preservation (Cat5) are LOW, documented not fixed."
@@ -9,10 +12,12 @@ interpolate,2026-06-12,3290,MEDIUM,2;3;4;5,"Deep-sweep 2026-06-12 on CUDA host.
9
12
  interpolate-kriging,2026-06-04,2920;2921,HIGH,1;2;3;4;5,"Single public fn kriging(); all 4 backends already had cross-backend parity tests (numpy/cupy/dask+numpy/dask+cupy) incl. cupy & dask+cupy variance -- ran green on CUDA host. Gaps closed (test-only, #2921): Cat1 dask+numpy return_variance branch (_chunk_var) was untested -> added test_dask_return_variance_matches_numpy (atol=1e-12, var ~1e-14). Cat4 nlags only default(15) tested -> added non-default nlags=5 + invalid paths (nlags=0/-1 ValueError, nlags=2.5 TypeError). Cat2/3 two-point <3-lag-bins UserWarning branch -> test_two_point_warns_few_lag_bins. Cat2 all-NaN kriging input -> test_kriging_all_nan_points (only idw covered before). Cat5 output metadata (coords/dims/attrs/name) untested -> added test_output_metadata. Single-point kriging CRASHES (zero-size array reduction in _experimental_variogram, N=1) -- real source bug filed #2920; added xfail(strict, raises=ValueError) test_single_point documenting expected graceful behavior; source fix left to #2920 (test-only PR). LOW/not filed: singular-matrix K_inv-is-None all-NaN branch is defensive and unreachable via public API. GPU-validated."
10
13
  interpolate_spline,2026-06-04,,HIGH,1;3;5,scope=spline-only; cupy+dask_cupy spline backends untested (_tps_cuda_kernel) | n==2 affine branch + metadata untested | added 4 tests to TestSpline all pass on CUDA host | issue-create denied by classifier no GH issue
11
14
  mcda,2026-06-10,3149,HIGH,1;2;5,"Pass 1 (2026-06-10, deep-sweep test-coverage): test_mcda.py had 175 tests, all numpy or dask+numpy -- zero cupy/dask+cupy coverage despite explicit cupy branches in standardize._get_xp and combine._sort_descending (Cat 1 HIGH). Filed #3149, added ~70 tests: cross-backend parity for standardize (7 methods) x cupy/dask+numpy/dask+cupy, combine (wlc/wpm/fuzzy and-or-sum-product-gamma/owa) x 3 backends, constrain, boolean_overlay, sensitivity OAT+MC on GPU backends; metadata preservation (attrs/coords/dims/name) for every stage (Cat 5 MEDIUM); wpm all-NaN criterion + Inf propagation through wlc/fuzzy-and (Cat 2 MEDIUM). All RUN on a CUDA host: 233 passed, 11 xfailed. Probing surfaced real source bugs already filed by sibling sweeps as #3146 (owa raises on ALL dask backends -- _sort_descending calls nonexistent da.sort; owa cupy mixes numpy order weights into cupy stack; piecewise standardize broken on cupy + dask+cupy and categorical on dask+cupy via np.asarray on cupy chunks; monte_carlo sensitivity reads .values on cupy data) and #3147 (constrain drops attrs when masks applied) -- those paths pinned with strict xfail markers to flip on fix; constrain cupy/dask+cupy xfail(strict=False) on the known cupy 13.6 + xarray xr.where dependency incompat, not an mcda bug. Source untouched (test-only PR). LOW (documented, not fixed): name= output parameter untested across combine functions; empty (0-row) raster untested -- elementwise ops, judged low value. weights.py (ahp/rank) is pure-numpy metadata, backend matrix N/A, already well covered."
15
+ morphology,2026-06-20,3404,MEDIUM,2;3,"Added Inf/-Inf, all-NaN, Nx1/1xN strip, integer-dtype tests; source already correct, regression guards only; cupy + dask+cupy ran on GPU host"
16
+ multispectral,2026-06-20,3431,MEDIUM,2;3;4,true_color NaN/alpha + all-equal range_val==0 + nondefault nodata/c/th; evi & savi validation error paths; GPU tests ran (cupy+dask+cupy)
12
17
  polygon_clip,2026-06-10,3197,MEDIUM,1;2;3;5,"deep-sweep test-coverage 2026-06-10 on a CUDA host. Existing file covered numpy well + one parity test per dask/cupy/dask+cupy backend. Filed #3197 (test-only) and added 13 tests: Cat1 GPU param/NaN coverage (cupy + dask+cupy each get custom nodata, all_touched=True, and NaN-input preservation vs numpy; previously only crop=False inner polygon ran on GPU); Cat2 Inf/-Inf preserved, all-NaN input -> all-NaN, int32 + sentinel nodata=-1; Cat3 Nx1 + 1xN strip rasters; Cat5 coords preserved (crop=False) + crop coords are a contiguous subset of input coords (crop=True). All 13 RAN+PASSED on GPU (6 GPU tests not skipped); full file 36 passed 0 skipped. LOW (documented, NOT fixed): rasterize_kw forwarding never tested; non-square cellsize never tested. SOURCE NOTE (out of scope, not filed): clip_polygon docstring says 'named y and x dims' but rasterize() hard-requires literal y/x, so lat/lon-dim rasters raise -- dim-name preservation (Cat5) is therefore unsupported by contract, not a test gap. SOURCE NOTE 2: polygon_clip.py:216 still passes rasterize(use_cuda=True) on dask+cupy (renamed to gpu= in #3089); harmless deprecation alias today, candidate for an api-consistency follow-up."
13
18
  polygonize,2026-06-12,3299,MEDIUM,1,"Pass 4 (2026-06-12): added test_polygonize_mask_chunk_mismatch_3299.py (25 tests, all passing on a CUDA host incl. dask+cupy). Closes Cat 1 MEDIUM: the _polygonize_dask mask-rechunk branch (mask_data.chunks != dask_data.chunks -> rechunk) was never exercised; every prior dask masked test used mask chunks identical to the raster's. Mismatched layouts pinned against same-backend aligned-mask reference: (6,7) same-grid-shape misalignment (the silent-corruption layout for int rasters), single-chunk (15,18), more-blocks (4,5); int+float rasters, connectivity 4/8, dask+numpy and dask+cupy; plus exact-geometry single-masked-pixel hole anchor. Mutation (delete the rechunk guard) flips all 25 red; clean md5 restore. Full polygonize suite 486 passed / 16 skipped. Test-only; source untouched. Issue #3299. Audit re-confirmed Cat 2/3/4 closed by passes 1-3 and post-2026-05-29 changes (#2913 float-mask fix flipped prior xfails, #3041 has issue-2677 test file, #2673/#2817 covered by batch-invariance and heap tests); Cat 5 N/A (no DataArray output; CRS/transform propagation already tested). | 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."
14
- proximity,2026-06-09,2692;3139,MEDIUM,1;2;3,"Pass 3 (2026-06-09, deep-sweep test-coverage): module grew since Pass 2 (#2807 metric validation, #2812 GREAT_CIRCLE brute force, #2850/#2851 input validation, #2854/#2908 halo fixes, tie-break routing) and each landed with its own tests; Pass 2's stale LOW (invalid distance_metric fallback) is FIXED and tested (#2807). Found 3 MEDIUM gaps, filed #3139, added 40 tests (all RUN and PASS on a CUDA host; full file 450 passed): (1) Cat 2 integer-dtype raster untested on any backend -- bounded dask pads int arrays with boundary=np.nan which casts to INT_MIN phantom targets, only neutralized because the coordinate-grid pads are real NaNs; pinned int32 x 3 funcs x 4 backends x bounded/unbounded vs float64 numpy baseline + explicit target_values; (2) Cat 1 bounded dask+cupy (_process_dask_cupy) only ever ran EUCLIDEAN; pinned MANHATTAN+GREAT_CIRCLE x 3 funcs with a routing spy; mutation (pad=0) flips all 6 red, clean md5 restore; (3) Cat 3 empty 0-row/0-col raster unpinned; fails fast with IndexError, pinned raises. All behaviors verified correct before tests were added -- no source bug, source untouched. LOW (documented, not fixed): -inf pixel input never tested (+inf is; isfinite is symmetric). || 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."
15
- rasterize,2026-06-12,2614;3102;3105;3296,MEDIUM,1,"Pass 6 (2026-06-12, deep-sweep test-coverage): added test_rasterize_mixed_type_ordered_merge_3296.py (17 passed on a CUDA host after review follow-up added dask+numpy all_touched ordered-merge parity; full rasterize suite 757 passed + 1 xfail pre-follow-up). One Cat 1 MEDIUM gap: merge='first'/'last' cross-geometry-type input-order semantics (#2064) were pinned on numpy + dask+numpy only; the GPU implements ordering via a separate two-pass atomic scheme (pass 1 cuda.atomic.min/max on order across scanline/line/point kernels, pass 2 stamps the winner) and no cupy/dask+cupy test ever had two geometry types competing for one pixel under an ordered merge (gpu_race_2167 scenarios are single-type; GC parity tests are non-overlapping single-value). Probed first: behavior matches numpy, pure coverage gap. New tests: known-winner pins on all 4 backends, cupy + dask+cupy(chunks=2, 4 tiles) parity vs numpy for first/last, all_touched=True variant (supercover pass-2 kernel with cross-type competition), fixture non-degeneracy guard. Mutation (atomic.min->max in _apply_merge_gpu 'first' branch) flips 5 tests red; md5-clean restore. Issue #3296. LOW (documented, not fixed): _check_uniform_axis non-finite expected_step early-return untested; gpu=True-without-cupy ImportError guard only reachable via monkeypatch; _check_gpu_edge_cap tested at unit level only, no end-to-end >2048-edge row through rasterize(gpu=True). | PREVIOUS: Pass 5 (2026-06-09, deep-sweep test-coverage): added test_rasterize_coverage_2026_06_09.py (11 passed + 1 strict xfail on a CUDA host; full rasterize suite 652 passed). Two Cat 4 MEDIUM gaps. (1) all_touched x LineString interaction had zero coverage on any backend; probed and found all_touched=True is a NO-OP for lines (only polygon boundaries get the supercover burn in _run_numpy step 1b; lines always Bresenham via _burn_lines_cpu), so output matches rasterio's DEFAULT mode not its all_touched mode despite the docstring's pixel-for-pixel parity claim -- source bug filed #3102 (not fixed here, test-only sweep). Pinned the no-op on numpy/cupy/dask+numpy/dask+cupy (cupy+dask_cupy RAN on CUDA host), pinned equality with rasterio default mode, and added a strict xfail for rasterio all_touched parity that flips when #3102 lands. (2) _parse_input non-iterable geometries TypeError ('geometries must be a GeoDataFrame or iterable') had no test; pinned over int/float/None/object. Issue #3105. LOW (documented, not fixed): _like_crs rio.crs fallback (path 4) never exercised (attrs crs/crs_wkt/spatial_ref paths are covered in test_rasterize_crs_mismatch_3058.py); NaN-coordinate Point with explicit bounds silently dropped (probed, works, untested); non-numeric fill attrs try/except at rasterize.py:3729 untested. | PREVIOUS: 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."
19
+ proximity,2026-06-18,2692;3139,MEDIUM,1;4,"Pass 4 (2026-06-18, deep-sweep test-coverage): 1 MEDIUM, 1 LOW. MEDIUM (Cat 4/Cat 1): all three public funcs are @supports_dataset and document Dataset-in/Dataset-out, but no test ever passed a Dataset; the shared decorator is covered generically in test_dataset_support.py which never lists proximity/allocation/direction, so per-variable _process dispatch + attrs/coords round-trip + result.name=None reset were unpinned. Added test_dataset_input_processes_each_variable (3 funcs x 4 backends, 12 tests); numpy+dask+cupy+dask+cupy all RUN and PASS on this CUDA host, expected built from numpy baseline to avoid implicit cupy host conversion. Verified working first -- no source bug, source untouched. Full file 528 passed. LOW (documented, not fixed): public exports euclidean_distance / manhattan_distance have no direct unit test (only great_circle_distance does); both are trivial pure fns exercised indirectly through proximity. || Pass 3 (2026-06-09, deep-sweep test-coverage): module grew since Pass 2 (#2807 metric validation, #2812 GREAT_CIRCLE brute force, #2850/#2851 input validation, #2854/#2908 halo fixes, tie-break routing) and each landed with its own tests; Pass 2's stale LOW (invalid distance_metric fallback) is FIXED and tested (#2807). Found 3 MEDIUM gaps, filed #3139, added 40 tests (all RUN and PASS on a CUDA host; full file 450 passed): (1) Cat 2 integer-dtype raster untested on any backend -- bounded dask pads int arrays with boundary=np.nan which casts to INT_MIN phantom targets, only neutralized because the coordinate-grid pads are real NaNs; pinned int32 x 3 funcs x 4 backends x bounded/unbounded vs float64 numpy baseline + explicit target_values; (2) Cat 1 bounded dask+cupy (_process_dask_cupy) only ever ran EUCLIDEAN; pinned MANHATTAN+GREAT_CIRCLE x 3 funcs with a routing spy; mutation (pad=0) flips all 6 red, clean md5 restore; (3) Cat 3 empty 0-row/0-col raster unpinned; fails fast with IndexError, pinned raises. All behaviors verified correct before tests were added -- no source bug, source untouched. LOW (documented, not fixed): -inf pixel input never tested (+inf is; isfinite is symmetric). || 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."
20
+ rasterize,2026-06-18,2614;3102;3105;3296;3383,HIGH,4,"Pass 7 (2026-06-18, deep-sweep test-coverage): #3383 found a Cat 4 (error-path) gap -- rasterize() input-validation guards had no tests. Added 4 test classes (18 tests, all RAN+PASSED on a CUDA host; full module 233 passed/2 skipped): TestFillRepresentableGuard (NaN/out-of-range int + non-False bool fill rejected #2504/#3054, + valid int/bool/float-NaN negatives), TestBurnValueSafeIntegerGuard (|burn|>2**53-1 into int rejected #3056, + boundary 2**53-1 and float-dtype exemption), TestNonFiniteBurnValueGuard (NaN/inf burn into int/bool rejected #3085, + merge='count' exemption and float-dtype negatives), TestNonFiniteGeometryCoordsDropped (NaN/inf geometry coords dropped with UserWarning #3295, list+gdf paths). All guards run pre-dispatch on the CPU path so no GPU needed. Test-only PR, no source change."
16
21
  reproject,2026-06-09,2618;3050;3100;3101;3141,MEDIUM,1,"CI follow-up same day: first CI run of the threaded streaming branch hard-crashed macos-arm64 py3.14 (SIGABRT in numba call_cfunc, two ThreadPoolExecutor threads concurrently inside try_numba_transform/tmerc_inverse) -- the projection kernels are @njit(parallel=True) and numba's workqueue threading layer aborts on concurrent entry; filed source bug #3141. Test fix: threaded parity test now uses transform_precision=0 (per-thread pyproj Transformer, no numba), the NaN multi-tile test and 3-D xfail forced serial (max_memory=1) so the numba fast path stays covered without concurrent entry. windows-3.14 failure was fail-fast collateral (its suite fully passed). Pass 2026-06-09 (deep-sweep test-coverage): delta re-sweep one day after the 2026-06-08 pass; module modified today by #3077 (datum-probe warning silencing) and #3081 (merge output-size guard backend-aware) -- both landed WITH their own tests (TestDatumProbeNoProjWarning; TestSecurityGuards merge-guard trio incl. the monkeypatched in-memory raise), so the delta added no gap; the guard branching is is_dask-only, so cupy eager shares the tested numpy branch (no per-backend guard test needed). Found one MEDIUM Cat 1 gap every prior pass missed: the 5th dispatch branch of reproject() -- the streaming fallback (_reproject_streaming / _process_tile_batch / _parse_max_memory, taken when source >512MB and dask is not importable) -- had zero coverage anywhere; _parse_max_memory only runs on that branch so the existing max_memory kwarg tests never reached it. Filed #3101, added test_reproject_streaming_3101.py (15 tests: parity vs in-memory numpy for threaded / serial(max_memory=1) / single-tile / nearest+NaN, plus 10 _parse_max_memory unit cases). Probe surfaced source bug #3100: streaming assembly allocates a 2-D output buffer but 3-D sources yield (h,w,b) tiles -> ValueError broadcast in both assembly loops; pinned with strict xfail, source fix left to #3100 (test-only PR, source untouched). CPU-only path so no GPU tests needed (CUDA host; file ran 14 passed + 1 xfailed). LOW carried (documented, not fixed): reproject(name=) / merge(name=) override values untested (only merge name fallback covered); non-square-cellsize successful anisotropic run; dask.bag distributed branch of _reproject_streaming still unexercised (needs a live distributed client). || PREVIOUS: Pass 2026-06-08 (deep-sweep test-coverage): #3050 closes the one live gap found this pass. reproject()'s dask+cupy backend was parity-tested only with resampling='cubic' (TestCupyPyprojFallbackParity::test_projected_to_projected_dask_cupy_match); nearest/bilinear were covered on numpy (end-to-end) and eager cupy (parametrized test_projected_to_projected_numpy_cupy_match) but never on the dask+cupy chunk-assembly path. Parametrized that test over ['nearest','bilinear','cubic']; all 3 RUN+PASS on a CUDA host. Cat 4 MEDIUM (resampling-mode parameter coverage on the dask+cupy backend). Test-only, source untouched. Re-confirmed _merge.merge() has NO genuine cupy/dask+cupy backend (_merge_inmemory/_merge_dask use _merge_arrays_numpy + raster.values; _merge_arrays_cupy is imported but never dispatched = dead code, not a test gap) matching the prior pass's observation. reproject() otherwise saturated across all 4 backends, NaN/Inf/all-NaN, degenerate shapes, metadata, vertical, bounds_policy, integer nodata. LOW (documented, not filed): dask+cupy resampling-mode parity is the only per-mode-per-backend cell that had been missing. || PREVIOUS: 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."
17
22
  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."
18
23
  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."
@@ -2,6 +2,41 @@
2
2
  -----------
3
3
 
4
4
 
5
+ ### Version 0.10.11 - 2026-06-21
6
+
7
+ #### Bug fixes and improvements
8
+ - classify: align natural_breaks parameter order with sibling classifiers (#3418)
9
+ - multispectral: propagate input dims/coords through true_color() (#3429) (#3434)
10
+ - multispectral: fix nbr() docstring parameter name to match signature (swir2_agg) (#3435)
11
+ - multispectral: remove unused import and fix import ordering (#3428) (#3432)
12
+ - multispectral: add test coverage for true_color edges and evi/savi validation (#3436)
13
+ - diffusion: drop unused import, sort imports (#3421) (#3423)
14
+ - diffusion: add test coverage for cupy/dask+cupy, reflect boundary, and edge shapes (#3424)
15
+ - disaggregate: fix limiting_variable silently losing a zone's value (#3403) (#3414)
16
+ - disaggregate: vectorize the zone-membership loop (#3408) (#3420)
17
+ - dasymetric: add test coverage for metadata, 1x1, Inf weight, 3-class limiting_variable (#3419)
18
+ - dasymetric: clean up lint (F401, E128, isort) (#3411)
19
+ - classify: keep the large-array sampler O(num_sample) in host memory (#3416)
20
+ - classify: add edge-case and error-path test coverage (#3417)
21
+ - classify: remove unused import and fix isort ordering (#3405)
22
+ - morphology: remove unused imports and dead local, fix import order (#3413)
23
+ - morphology: skip the memory guard for lazy dask inputs (#3401) (#3410)
24
+ - morphology: add missing accessor methods (#3409)
25
+ - morphology: add test coverage for edge inputs (Inf, all-NaN, strip, integer) (#3415)
26
+ - viewshed: add tests for 1x1, all-NaN, and Inf-terrain edge cases (#3425) (#3426)
27
+ - proximity: match GPU max_distance precision to the CPU brute-force (#3389) (#3391)
28
+ - proximity: fix isort import-ordering drift (#3393)
29
+ - proximity: cover Dataset input for proximity/allocation/direction (#3390)
30
+ - fire: declare float32 dtype on the dask+numpy backend (#3394) (#3396)
31
+ - fire: drop dead code and fix import ordering (#3395) (#3397)
32
+ - aspect: sort imports per isort config (#3437) (#3438)
33
+ - aspect: test Inf and all-NaN elevation input across backends (#3439) (#3440)
34
+ - engine: carry the coregister/auto_reproject target on the parameter value (#3380)
35
+ - rasterize: match dask all_touched polygon boundary to eager on grid lines (#3384) (#3386)
36
+ - rasterize: add validation-guard test coverage (#3383) (#3385)
37
+ - zonal: vectorize _sort_and_stride 3D reindex, drop deepcopy (#3381) (#3382)
38
+
39
+
5
40
  ### Version 0.10.10 - 2026-06-17
6
41
 
7
42
  #### New features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xarray-spatial
3
- Version: 0.10.10
3
+ Version: 0.10.11
4
4
  Summary: xarray-based spatial analysis tools
5
5
  Home-page: https://github.com/xarray-contrib/xarray-spatial
6
6
  Author: Xarray-Spatial Developers
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xarray-spatial
3
- Version: 0.10.10
3
+ Version: 0.10.11
4
4
  Summary: xarray-based spatial analysis tools
5
5
  Home-page: https://github.com/xarray-contrib/xarray-spatial
6
6
  Author: Xarray-Spatial Developers
@@ -171,6 +171,7 @@ xrspatial/geotiff/tests/test_stable_only_bbox_ordering_2869.py
171
171
  xrspatial/geotiff/tests/test_stable_only_remote_2821.py
172
172
  xrspatial/geotiff/tests/test_xarray_backend_3365.py
173
173
  xrspatial/geotiff/tests/test_xarray_backend_coregister_3376.py
174
+ xrspatial/geotiff/tests/test_xarray_backend_coregister_target_3379.py
174
175
  xrspatial/geotiff/tests/_helpers/__init__.py
175
176
  xrspatial/geotiff/tests/_helpers/markers.py
176
177
  xrspatial/geotiff/tests/_helpers/tiff_builders.py
@@ -481,6 +482,7 @@ xrspatial/tests/test_preview.py
481
482
  xrspatial/tests/test_proximity.py
482
483
  xrspatial/tests/test_rasterize.py
483
484
  xrspatial/tests/test_rasterize_accuracy.py
485
+ xrspatial/tests/test_rasterize_all_touched_dask_grid_line_3384.py
484
486
  xrspatial/tests/test_rasterize_all_touched_supercover_2169.py
485
487
  xrspatial/tests/test_rasterize_alloc_3107.py
486
488
  xrspatial/tests/test_rasterize_coverage_2026_05_17.py
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.10.10'
22
- __version_tuple__ = version_tuple = (0, 10, 10)
21
+ __version__ = version = '0.10.11'
22
+ __version_tuple__ = version_tuple = (0, 10, 11)
23
23
 
24
- __commit_id__ = commit_id = 'g97043074c'
24
+ __commit_id__ = commit_id = 'g4108b324e'