PyGeoFetch 2.6.2.7__tar.gz → 2.6.2.9__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 (234) hide show
  1. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/CHANGELOG.md +7 -7
  2. {pygeofetch-2.6.2.7/pygeofetch.egg-info → pygeofetch-2.6.2.9}/PKG-INFO +9 -9
  3. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/README.md +8 -8
  4. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/__init__.py +1 -1
  5. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/cli/index_commands.py +119 -30
  6. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/auto_fallback.py +18 -2
  7. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/meta.yaml +1 -1
  8. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/processing/base.py +210 -0
  9. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/processing/indices.py +357 -60
  10. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9/pygeofetch.egg-info}/PKG-INFO +9 -9
  11. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch.egg-info/SOURCES.txt +1 -0
  12. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pyproject.toml +1 -1
  13. pygeofetch-2.6.2.9/tests/test_auto_fallback_fixed.py +226 -0
  14. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/LICENSE +0 -0
  15. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/MANIFEST.in +0 -0
  16. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/cli/__init__.py +0 -0
  17. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/cli/auth_commands.py +0 -0
  18. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/cli/config_commands.py +0 -0
  19. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/cli/download_commands.py +0 -0
  20. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/cli/main.py +0 -0
  21. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/cli/monitor_commands.py +0 -0
  22. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/cli/multisensor_commands.py +0 -0
  23. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/cli/optical_commands.py +0 -0
  24. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/cli/pipeline_process_commands.py +0 -0
  25. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/cli/postprocess_commands.py +0 -0
  26. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/cli/preprocess_commands.py +0 -0
  27. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/cli/sar_commands.py +0 -0
  28. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/cli/search_commands.py +0 -0
  29. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/config/__init__.py +0 -0
  30. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/config/defaults.yaml +0 -0
  31. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/config/settings.py +0 -0
  32. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/core/__init__.py +0 -0
  33. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/core/authenticator.py +0 -0
  34. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/core/cache_manager.py +0 -0
  35. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/core/data_organizer.py +0 -0
  36. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/core/downloader.py +0 -0
  37. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/core/engine.py +0 -0
  38. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/core/logging.py +0 -0
  39. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/core/monitor.py +0 -0
  40. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/core/orbits.py +0 -0
  41. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/core/scheduler.py +0 -0
  42. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/core/searcher.py +0 -0
  43. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/core/state.py +0 -0
  44. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/geotech/__init__.py +0 -0
  45. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/geotech/risk_mapper.py +0 -0
  46. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/__init__.py +0 -0
  47. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/advanced_safeguards.py +0 -0
  48. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/analysis.py +0 -0
  49. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/annotation.py +0 -0
  50. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/atmosphere.py +0 -0
  51. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/coregister.py +0 -0
  52. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/deburst.py +0 -0
  53. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/esd.py +0 -0
  54. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/extraction.py +0 -0
  55. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/flatearth.py +0 -0
  56. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/geolocation.py +0 -0
  57. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/gpu.py +0 -0
  58. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/interferogram.py +0 -0
  59. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/ionosphere.py +0 -0
  60. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/offset_geometry.py +0 -0
  61. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/offset_tracking.py +0 -0
  62. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/preflight.py +0 -0
  63. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/provenance.py +0 -0
  64. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/ps_selection.py +0 -0
  65. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/splitspectrum_diagnostic.py +0 -0
  66. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/stack_selection.py +0 -0
  67. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/synthetic.py +0 -0
  68. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/timeseries.py +0 -0
  69. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/unwrap.py +0 -0
  70. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/validate.py +0 -0
  71. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/visualize.py +0 -0
  72. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/insar/workflow.py +0 -0
  73. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/models/__init__.py +0 -0
  74. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/models/download_task.py +0 -0
  75. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/models/satellite_data.py +0 -0
  76. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/models/search_query.py +0 -0
  77. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/models/user_auth.py +0 -0
  78. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/multisensor/_init.py +0 -0
  79. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/multisensor/dem_differencing.py +0 -0
  80. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/multisensor/flood_mapping.py +0 -0
  81. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/multisensor/insar_optical_fusion.py +0 -0
  82. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/multisensor/terrain_corrected_change.py +0 -0
  83. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/multisensor/vegetation_disturbance.py +0 -0
  84. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/optical/__init__.py +0 -0
  85. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/optical/offset_tracking.py +0 -0
  86. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/processing/__init__.py +0 -0
  87. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/processing/batch.py +0 -0
  88. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/processing/pipeline.py +0 -0
  89. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/processing/postprocessor.py +0 -0
  90. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/processing/preprocessor.py +0 -0
  91. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/processing/sar.py +0 -0
  92. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/processing/uq_exporter.py +0 -0
  93. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/processor/__init__.py +0 -0
  94. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/processor/indices.py +0 -0
  95. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/processor/landsat.py +0 -0
  96. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/processor/loader.py +0 -0
  97. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/processor/stacker.py +0 -0
  98. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/processor/timeseries.py +0 -0
  99. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/__init__.py +0 -0
  100. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/airbus_oneatlas.py +0 -0
  101. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/alaska_satellite_facility.py +0 -0
  102. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/aws_earth.py +0 -0
  103. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/base.py +0 -0
  104. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/copernicus.py +0 -0
  105. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/copernicus_nodes.py +0 -0
  106. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/digitalglobe.py +0 -0
  107. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/earth_explorer_additional.py +0 -0
  108. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/element84.py +0 -0
  109. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/eodag_provider.py +0 -0
  110. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/esa_scihub.py +0 -0
  111. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/geoserver_generic.py +0 -0
  112. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/google_earth_engine.py +0 -0
  113. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/inpe_cbers.py +0 -0
  114. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/isro_bhuvan.py +0 -0
  115. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/jaxa_earth.py +0 -0
  116. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/maxar_gbdx.py +0 -0
  117. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/nasa_earthdata.py +0 -0
  118. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/nasa_earthdata_cloud.py +0 -0
  119. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/noaa_big_data.py +0 -0
  120. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/opentopography.py +0 -0
  121. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/planet.py +0 -0
  122. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/planetary_computer.py +0 -0
  123. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/sentinel_hub.py +0 -0
  124. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/terrabotics.py +0 -0
  125. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/providers/usgs.py +0 -0
  126. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/sar/__init__.py +0 -0
  127. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/sar/_native.py +0 -0
  128. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/sar/_ost.py +0 -0
  129. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/sar/_sarxarray.py +0 -0
  130. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/sar/extraction.py +0 -0
  131. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/sar/math.py +0 -0
  132. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/sar/pipelines.py +0 -0
  133. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/sar/processor.py +0 -0
  134. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/stac_compute/__init__.py +0 -0
  135. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/stac_compute/lightweight_prefetch.py +0 -0
  136. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/utils/__init__.py +0 -0
  137. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/utils/file_utils.py +0 -0
  138. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/utils/gcp_recovery.py +0 -0
  139. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/utils/geo_utils.py +0 -0
  140. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/utils/logging_setup.py +0 -0
  141. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/utils/retry_handler.py +0 -0
  142. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/utils/sar_math.py +0 -0
  143. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/utils/validators.py +0 -0
  144. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/validation/__init__.py +0 -0
  145. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/validation/optical_validator.py +0 -0
  146. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/viz/__init__.py +0 -0
  147. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/viz/map.py +0 -0
  148. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch/viz/plot.py +0 -0
  149. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch.egg-info/dependency_links.txt +0 -0
  150. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch.egg-info/entry_points.txt +0 -0
  151. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch.egg-info/not-zip-safe +0 -0
  152. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch.egg-info/requires.txt +0 -0
  153. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/pygeofetch.egg-info/top_level.txt +0 -0
  154. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/setup.cfg +0 -0
  155. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_advanced_safeguards.py +0 -0
  156. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_airbus_oneatlas.py +0 -0
  157. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_alaska_satellite_facility.py +0 -0
  158. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_atmospheric_circular_regression.py +0 -0
  159. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_auto_fallback.py +0 -0
  160. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_build_sbas_network-new.py +0 -0
  161. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_build_sbas_network.py +0 -0
  162. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_burst_metadata.py +0 -0
  163. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_burst_processing_integration.py +0 -0
  164. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_burst_sync_large_gap.py +0 -0
  165. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_burst_sync_prescreening.py +0 -0
  166. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_burst_synchronization.py +0 -0
  167. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_circuit_breaker_wiring.py +0 -0
  168. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_cli_optical_validation.py +0 -0
  169. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_copernicus_nodes.py +0 -0
  170. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_coregister_integration.py +0 -0
  171. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_coregister_upgrades (1).py +0 -0
  172. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_coregister_upgrades.py +0 -0
  173. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_credential_encryption.py +0 -0
  174. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_data_organizer.py +0 -0
  175. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_dem_differencing_pipeline.py +0 -0
  176. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_digitalglobe.py +0 -0
  177. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_earth_explorer_additional.py +0 -0
  178. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_era5_atmospheric_correction.py +0 -0
  179. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_esa_scihub.py +0 -0
  180. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_extraction.py +0 -0
  181. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_flat_earth_phase.py +0 -0
  182. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_flood_mapping_pipeline.py +0 -0
  183. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_gcp_reprojection.py +0 -0
  184. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_geology_indices_and_rvi_fix.py +0 -0
  185. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_geoserver_generic.py +0 -0
  186. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_google_earth_engine.py +0 -0
  187. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_inpe_cbers.py +0 -0
  188. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_insar.py +0 -0
  189. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_insar_optical_fusion_pipeline.py +0 -0
  190. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_ionosphere.py +0 -0
  191. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_isro_bhuvan.py +0 -0
  192. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_jaxa_earth.py +0 -0
  193. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_lightweight_prefetch.py +0 -0
  194. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_map_preview_consolidation.py +0 -0
  195. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_models.py +0 -0
  196. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_monitor.py +0 -0
  197. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_nasa_earthdata_cloud_s3_fix.py +0 -0
  198. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_nasa_earthdata_public_search.py +0 -0
  199. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_noaa_big_data.py +0 -0
  200. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_offset_tracking.py +0 -0
  201. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_opentopography_pointcloud_usgs.py +0 -0
  202. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_optical_offset_tracking.py +0 -0
  203. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_optical_validation_wiring.py +0 -0
  204. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_optical_validator.py +0 -0
  205. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_optical_validator_stac.py +0 -0
  206. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_pgv_integration_contract.py +0 -0
  207. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_pipeline_process_export.py +0 -0
  208. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_planet_asset_type_fix.py +0 -0
  209. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_preflight_gate.py +0 -0
  210. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_preflight_spatial_baseline.py +0 -0
  211. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_provider_geometry_audit.py +0 -0
  212. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_provider_scene_disambiguation.py +0 -0
  213. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_providers.py +0 -0
  214. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_ps_selection.py +0 -0
  215. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_raster_collocation_integration.py +0 -0
  216. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_reference_pixel_and_despike.py +0 -0
  217. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_risk_mapper.py +0 -0
  218. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_risk_mapping.py +0 -0
  219. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_sar_pipelines.py +0 -0
  220. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_screen_stack_orchestration.py +0 -0
  221. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_search_download.py +0 -0
  222. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_sentinel_hub_catalog_fix.py +0 -0
  223. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_sign_convention.py +0 -0
  224. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_splitspectrum_diagnostic.py +0 -0
  225. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_stack_selection_consolidation.py +0 -0
  226. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_state.py +0 -0
  227. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_strain_and_fusion.py +0 -0
  228. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_terrain_corrected_change_pipeline.py +0 -0
  229. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_topo_r2_bug.py +0 -0
  230. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_uq_exporter.py +0 -0
  231. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_utils.py +0 -0
  232. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_vegetation_disturbance_pipeline.py +0 -0
  233. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_viz_plot.py +0 -0
  234. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.9}/tests/test_wls_inversion.py +0 -0
@@ -20,7 +20,7 @@ date, and marked accordingly rather than filled in with invented
20
20
  bullet points.
21
21
  ```
22
22
 
23
- ## [Unreleased] — v2.6.2.7.4
23
+ ## [Unreleased] — v2.6.2.9.4
24
24
 
25
25
  ```{note}
26
26
  Not yet published to PyPI. This entry documents work completed and
@@ -130,7 +130,7 @@ audit and remain open for a follow-up pass.
130
130
 
131
131
  ---
132
132
 
133
- ## [2.6.2.7.4] — 2026-08-25 (latest)
133
+ ## [2.6.2.9.4] — 2026-08-25 (latest)
134
134
 
135
135
  Confirmed via direct fetch of the current PyPI listing.
136
136
 
@@ -149,7 +149,7 @@ Confirmed via direct fetch of the current PyPI listing.
149
149
  - `SearchQuery.sort_ascending: bool` — earlier examples showed a `sort_order: "asc"/"desc"` string field that doesn't exist on the model (the CLI's `--sort-order` flag does convert to this internally, so CLI docs were unaffected)
150
150
  - `DownloadOptions.post_process` requires a list of `PostProcessAction` objects — passing plain strings raises a real `pydantic.ValidationError`
151
151
 
152
- ## [2.6.2.7] — 2026-08-24
152
+ ## [2.6.2.9] — 2026-08-24
153
153
  Real, verified release. Detailed notes not independently recovered from PyPI's release-history page (no distinct per-version notes exposed for this release specifically).
154
154
 
155
155
  ## [2.6.1] — 2026-08-22
@@ -261,9 +261,9 @@ recovered.
261
261
 
262
262
  ```{note}
263
263
  The previous version of this changelog had an entry labeled
264
- **"2.6.2.7.4 — 2026-07-12"** describing 22+ providers, Sentinel-1C/1D
264
+ **"2.6.2.9.4 — 2026-07-12"** describing 22+ providers, Sentinel-1C/1D
265
265
  support, SLC search with provider routing, precise orbit file
266
- management, 17 spectral indices, and various bug fixes. **2.6.2.7.4 was
266
+ management, 17 spectral indices, and various bug fixes. **2.6.2.9.4 was
267
267
  only ever released once, on 2026-08-25** — it was never released on
268
268
  2026-07-12. The date matches this real release, **1.0.8**, exactly.
269
269
  That content is reproduced below under the version it actually
@@ -406,8 +406,8 @@ The version of this file uploaded for this update had **5 entries
406
406
  covering 5 dates**. PyPI's real release history has **54 releases**.
407
407
  Specific issues found and fixed:
408
408
 
409
- 1. **Two different entries were both labeled `2.6.2.7.4`**, dated
410
- 2026-07-29 and 2026-07-12. **2.6.2.7.4 was only ever released once**,
409
+ 1. **Two different entries were both labeled `2.6.2.9.4`**, dated
410
+ 2026-07-29 and 2026-07-12. **2.6.2.9.4 was only ever released once**,
411
411
  on 2026-08-25 (confirmed directly from the live PyPI listing).
412
412
  2. **The 2026-07-12 entry's date exactly matches a real release**
413
413
  — version **1.0.8** — and its content (22+ providers, Sentinel-1C/D,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygeofetch
3
- Version: 2.6.2.7
3
+ Version: 2.6.2.9
4
4
  Summary: Universal satellite data pipeline + geospatial processing platform
5
5
  Author-email: Samuel Appiah Kubi <appiahkubis14@gmail.com>
6
6
  License: MIT License
@@ -167,13 +167,13 @@ Where pygeofetch goes further than most data-access libraries is in its **InSAR
167
167
 
168
168
  | | |
169
169
  |---|---|
170
- | 🔐 **Authenticated access** | 22 verified providers; credentials stored via a Fernet-encrypted local file by default, or the system keyring (Keychain / Credential Manager / Secret Service) as an opt-in alternative |
171
- | 🔍 **Federated search** | One query across all providers → STAC 1.0 GeoJSON / GeoParquet / CSV, with real footprint geometry where the provider supplies it |
172
- | 📥 **Resilient downloads** | Parallel, resumable, checksum-verified, band-selective, atomic writes |
173
- | ⚙️ **Preprocessing** | Atmospheric correction, cloud masking, reprojection, resampling, pan-sharpening, mosaicking |
174
- | 📊 **17 spectral indices** | NDVI, EVI, SAVI, NDWI, MNDWI, NDBI, LST, Albedo, dNBR, GLCM texture, and more |
175
- | 🌐 **Verified InSAR chain** | Coregistration → phase removal → atmospheric/ionospheric correction → unwrapping → SBAS inversion |
176
- | 📋 **YAML pipelines** | Chainable, schedulable, repeatable workflows with full run history |
170
+ | **Authenticated access** | 22 verified providers; credentials stored via a Fernet-encrypted local file by default, or the system keyring (Keychain / Credential Manager / Secret Service) as an opt-in alternative |
171
+ | **Federated search** | One query across all providers → STAC 1.0 GeoJSON / GeoParquet / CSV, with real footprint geometry where the provider supplies it |
172
+ | **Resilient downloads** | Parallel, resumable, checksum-verified, band-selective, atomic writes |
173
+ | **Preprocessing** | Atmospheric correction, cloud masking, reprojection, resampling, pan-sharpening, mosaicking |
174
+ | **17 spectral indices** | NDVI, EVI, SAVI, NDWI, MNDWI, NDBI, LST, Albedo, dNBR, GLCM texture, and more |
175
+ | **Verified InSAR chain** | Coregistration → phase removal → atmospheric/ionospheric correction → unwrapping → SBAS inversion |
176
+ | **YAML pipelines** | Chainable, schedulable, repeatable workflows with full run history |
177
177
 
178
178
  ---
179
179
 
@@ -369,7 +369,7 @@ If you use PyGeoFetch in your research or operational work, please cite it using
369
369
  title = {PyGeoFetch: A Unified Python Framework for Multi-Provider Satellite Data Acquisition, Pre-download Quality Control, and Geospatial Processing},
370
370
  year = {2026},
371
371
  publisher = {Zenodo},
372
- version = {2.6.2.7.1},
372
+ version = {2.6.2.9.1},
373
373
  doi = {10.5281/zenodo.22087230},
374
374
  url = {https://doi.org/10.5281/zenodo.22087230}
375
375
  }
@@ -27,13 +27,13 @@ Where pygeofetch goes further than most data-access libraries is in its **InSAR
27
27
 
28
28
  | | |
29
29
  |---|---|
30
- | 🔐 **Authenticated access** | 22 verified providers; credentials stored via a Fernet-encrypted local file by default, or the system keyring (Keychain / Credential Manager / Secret Service) as an opt-in alternative |
31
- | 🔍 **Federated search** | One query across all providers → STAC 1.0 GeoJSON / GeoParquet / CSV, with real footprint geometry where the provider supplies it |
32
- | 📥 **Resilient downloads** | Parallel, resumable, checksum-verified, band-selective, atomic writes |
33
- | ⚙️ **Preprocessing** | Atmospheric correction, cloud masking, reprojection, resampling, pan-sharpening, mosaicking |
34
- | 📊 **17 spectral indices** | NDVI, EVI, SAVI, NDWI, MNDWI, NDBI, LST, Albedo, dNBR, GLCM texture, and more |
35
- | 🌐 **Verified InSAR chain** | Coregistration → phase removal → atmospheric/ionospheric correction → unwrapping → SBAS inversion |
36
- | 📋 **YAML pipelines** | Chainable, schedulable, repeatable workflows with full run history |
30
+ | **Authenticated access** | 22 verified providers; credentials stored via a Fernet-encrypted local file by default, or the system keyring (Keychain / Credential Manager / Secret Service) as an opt-in alternative |
31
+ | **Federated search** | One query across all providers → STAC 1.0 GeoJSON / GeoParquet / CSV, with real footprint geometry where the provider supplies it |
32
+ | **Resilient downloads** | Parallel, resumable, checksum-verified, band-selective, atomic writes |
33
+ | **Preprocessing** | Atmospheric correction, cloud masking, reprojection, resampling, pan-sharpening, mosaicking |
34
+ | **17 spectral indices** | NDVI, EVI, SAVI, NDWI, MNDWI, NDBI, LST, Albedo, dNBR, GLCM texture, and more |
35
+ | **Verified InSAR chain** | Coregistration → phase removal → atmospheric/ionospheric correction → unwrapping → SBAS inversion |
36
+ | **YAML pipelines** | Chainable, schedulable, repeatable workflows with full run history |
37
37
 
38
38
  ---
39
39
 
@@ -229,7 +229,7 @@ If you use PyGeoFetch in your research or operational work, please cite it using
229
229
  title = {PyGeoFetch: A Unified Python Framework for Multi-Provider Satellite Data Acquisition, Pre-download Quality Control, and Geospatial Processing},
230
230
  year = {2026},
231
231
  publisher = {Zenodo},
232
- version = {2.6.2.7.1},
232
+ version = {2.6.2.9.1},
233
233
  doi = {10.5281/zenodo.22087230},
234
234
  url = {https://doi.org/10.5281/zenodo.22087230}
235
235
  }
@@ -30,7 +30,7 @@ CLI::
30
30
  pygeofetch doctor
31
31
  """
32
32
 
33
- __version__ = "2.6.2.7.4"
33
+ __version__ = "2.6.2.9.4"
34
34
  __author__ = "PyGeoFetch Contributors"
35
35
  __license__ = "MIT"
36
36
 
@@ -50,22 +50,64 @@ def index() -> None:
50
50
  "--nir", required=True, type=click.Path(exists=True), help="NIR band (e.g. B08.tif)"
51
51
  )
52
52
  @click.option("--output", "-o", default=None)
53
- def ndvi_cmd(red, nir, output):
54
- """NDVI — Normalized Difference Vegetation Index. Range: -1 to +1. Vegetation > 0.3."""
53
+ @click.option(
54
+ "--chunked", is_flag=True,
55
+ help="Process in memory-safe tiles for large rasters (e.g. drone orthomosaics) that exceed available RAM or the 4GB GeoTIFF limit.",
56
+ )
57
+ @click.option("--tile-size", default=1024, show_default=True, type=int, help="Tile edge length in pixels when --chunked is set.")
58
+ def ndvi_cmd(red, nir, output, chunked, tile_size):
59
+ """NDVI — Normalized Difference Vegetation Index. Range: -1 to +1. Vegetation > 0.3.
60
+
61
+ Pass the SAME path to --red and --nir to auto-detect both bands from
62
+ one merged multi-band image (e.g. a drone orthomosaic), using its
63
+ real, embedded band descriptions.
64
+ """
55
65
  e = _engine()
56
- r = e.indices.ndvi(red=red, nir=nir, output=output)
66
+ r = e.indices.ndvi(red=red, nir=nir, output=output, chunked=chunked, tile_size=tile_size)
57
67
  _pr(r, "NDVI")
58
68
 
59
69
 
70
+ @index.command("ndre")
71
+ @click.option(
72
+ "--rededge", required=True, type=click.Path(exists=True), help="Red edge band"
73
+ )
74
+ @click.option(
75
+ "--nir", required=True, type=click.Path(exists=True), help="NIR band"
76
+ )
77
+ @click.option("--output", "-o", default=None)
78
+ @click.option(
79
+ "--chunked", is_flag=True,
80
+ help="Process in memory-safe tiles for large rasters that exceed available RAM or the 4GB GeoTIFF limit.",
81
+ )
82
+ @click.option("--tile-size", default=1024, show_default=True, type=int, help="Tile edge length in pixels when --chunked is set.")
83
+ def ndre_cmd(rededge, nir, output, chunked, tile_size):
84
+ """NDRE — Normalized Difference Red Edge Index. Range: -1 to +1.
85
+
86
+ Less prone to saturation than NDVI in dense canopy -- useful for
87
+ crop stress and chlorophyll monitoring from drone multispectral
88
+ sensors. Pass the SAME path to --rededge and --nir to auto-detect
89
+ both bands from one merged multi-band image.
90
+ """
91
+ e = _engine()
92
+ r = e.indices.ndre(rededge=rededge, nir=nir, output=output, chunked=chunked, tile_size=tile_size)
93
+ _pr(r, "NDRE")
94
+
95
+
60
96
  @index.command("evi")
61
97
  @click.option("--blue", required=True, type=click.Path(exists=True))
62
98
  @click.option("--red", required=True, type=click.Path(exists=True))
63
99
  @click.option("--nir", required=True, type=click.Path(exists=True))
64
100
  @click.option("--output", "-o", default=None)
65
- def evi_cmd(blue, red, nir, output):
66
- """EVI — Enhanced Vegetation Index. Better than NDVI in high-biomass areas."""
101
+ @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
102
+ @click.option("--tile-size", default=1024, show_default=True, type=int)
103
+ def evi_cmd(blue, red, nir, output, chunked, tile_size):
104
+ """EVI — Enhanced Vegetation Index. Better than NDVI in high-biomass areas.
105
+
106
+ Pass the SAME path to --blue, --red, and --nir to auto-detect all
107
+ three bands from one merged multi-band image.
108
+ """
67
109
  e = _engine()
68
- r = e.indices.evi(blue=blue, red=red, nir=nir, output=output)
110
+ r = e.indices.evi(blue=blue, red=red, nir=nir, output=output, chunked=chunked, tile_size=tile_size)
69
111
  _pr(r, "EVI")
70
112
 
71
113
 
@@ -74,10 +116,16 @@ def evi_cmd(blue, red, nir, output):
74
116
  @click.option("--nir", required=True, type=click.Path(exists=True))
75
117
  @click.option("--soil-l", default=0.5, show_default=True, type=float)
76
118
  @click.option("--output", "-o", default=None)
77
- def savi_cmd(red, nir, soil_l, output):
78
- """SAVI — Soil Adjusted Vegetation Index. Reduces soil brightness influence."""
119
+ @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
120
+ @click.option("--tile-size", default=1024, show_default=True, type=int)
121
+ def savi_cmd(red, nir, soil_l, output, chunked, tile_size):
122
+ """SAVI — Soil Adjusted Vegetation Index. Reduces soil brightness influence.
123
+
124
+ Pass the SAME path to --red and --nir to auto-detect both bands
125
+ from one merged multi-band image.
126
+ """
79
127
  e = _engine()
80
- r = e.indices.savi(red=red, nir=nir, L=soil_l, output=output)
128
+ r = e.indices.savi(red=red, nir=nir, L=soil_l, output=output, chunked=chunked, tile_size=tile_size)
81
129
  _pr(r, "SAVI")
82
130
 
83
131
 
@@ -85,10 +133,16 @@ def savi_cmd(red, nir, soil_l, output):
85
133
  @click.option("--green", required=True, type=click.Path(exists=True))
86
134
  @click.option("--nir", required=True, type=click.Path(exists=True))
87
135
  @click.option("--output", "-o", default=None)
88
- def ndwi_cmd(green, nir, output):
89
- """NDWI — Normalized Difference Water Index. Water > 0."""
136
+ @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
137
+ @click.option("--tile-size", default=1024, show_default=True, type=int)
138
+ def ndwi_cmd(green, nir, output, chunked, tile_size):
139
+ """NDWI — Normalized Difference Water Index. Water > 0.
140
+
141
+ Pass the SAME path to --green and --nir to auto-detect both bands
142
+ from one merged multi-band image.
143
+ """
90
144
  e = _engine()
91
- r = e.indices.ndwi(green=green, nir=nir, output=output)
145
+ r = e.indices.ndwi(green=green, nir=nir, output=output, chunked=chunked, tile_size=tile_size)
92
146
  _pr(r, "NDWI")
93
147
 
94
148
 
@@ -96,10 +150,16 @@ def ndwi_cmd(green, nir, output):
96
150
  @click.option("--green", required=True, type=click.Path(exists=True))
97
151
  @click.option("--swir1", required=True, type=click.Path(exists=True))
98
152
  @click.option("--output", "-o", default=None)
99
- def mndwi_cmd(green, swir1, output):
100
- """MNDWI — Modified NDWI. Better separation of water from built-up areas."""
153
+ @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
154
+ @click.option("--tile-size", default=1024, show_default=True, type=int)
155
+ def mndwi_cmd(green, swir1, output, chunked, tile_size):
156
+ """MNDWI — Modified NDWI. Better separation of water from built-up areas.
157
+
158
+ Pass the SAME path to --green and --swir1 to auto-detect both
159
+ bands from one merged multi-band image.
160
+ """
101
161
  e = _engine()
102
- r = e.indices.mndwi(green=green, swir1=swir1, output=output)
162
+ r = e.indices.mndwi(green=green, swir1=swir1, output=output, chunked=chunked, tile_size=tile_size)
103
163
  _pr(r, "MNDWI")
104
164
 
105
165
 
@@ -107,10 +167,16 @@ def mndwi_cmd(green, swir1, output):
107
167
  @click.option("--nir", required=True, type=click.Path(exists=True))
108
168
  @click.option("--swir1", required=True, type=click.Path(exists=True))
109
169
  @click.option("--output", "-o", default=None)
110
- def ndbi_cmd(nir, swir1, output):
111
- """NDBI — Normalized Difference Built-up Index. Urban > 0."""
170
+ @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
171
+ @click.option("--tile-size", default=1024, show_default=True, type=int)
172
+ def ndbi_cmd(nir, swir1, output, chunked, tile_size):
173
+ """NDBI — Normalized Difference Built-up Index. Urban > 0.
174
+
175
+ Pass the SAME path to --nir and --swir1 to auto-detect both bands
176
+ from one merged multi-band image.
177
+ """
112
178
  e = _engine()
113
- r = e.indices.ndbi(nir=nir, swir1=swir1, output=output)
179
+ r = e.indices.ndbi(nir=nir, swir1=swir1, output=output, chunked=chunked, tile_size=tile_size)
114
180
  _pr(r, "NDBI")
115
181
 
116
182
 
@@ -118,10 +184,16 @@ def ndbi_cmd(nir, swir1, output):
118
184
  @click.option("--green", required=True, type=click.Path(exists=True))
119
185
  @click.option("--swir1", required=True, type=click.Path(exists=True))
120
186
  @click.option("--output", "-o", default=None)
121
- def ndsi_cmd(green, swir1, output):
122
- """NDSI — Snow Index. Snow > 0.4."""
187
+ @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
188
+ @click.option("--tile-size", default=1024, show_default=True, type=int)
189
+ def ndsi_cmd(green, swir1, output, chunked, tile_size):
190
+ """NDSI — Snow Index. Snow > 0.4.
191
+
192
+ Pass the SAME path to --green and --swir1 to auto-detect both
193
+ bands from one merged multi-band image.
194
+ """
123
195
  e = _engine()
124
- r = e.indices.ndsi(green=green, swir1=swir1, output=output)
196
+ r = e.indices.ndsi(green=green, swir1=swir1, output=output, chunked=chunked, tile_size=tile_size)
125
197
  _pr(r, "NDSI")
126
198
 
127
199
 
@@ -129,10 +201,16 @@ def ndsi_cmd(green, swir1, output):
129
201
  @click.option("--nir", required=True, type=click.Path(exists=True))
130
202
  @click.option("--swir1", required=True, type=click.Path(exists=True))
131
203
  @click.option("--output", "-o", default=None)
132
- def ndmi_cmd(nir, swir1, output):
133
- """NDMI — Moisture Index. Sensitive to canopy water content."""
204
+ @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
205
+ @click.option("--tile-size", default=1024, show_default=True, type=int)
206
+ def ndmi_cmd(nir, swir1, output, chunked, tile_size):
207
+ """NDMI — Moisture Index. Sensitive to canopy water content.
208
+
209
+ Pass the SAME path to --nir and --swir1 to auto-detect both bands
210
+ from one merged multi-band image.
211
+ """
134
212
  e = _engine()
135
- r = e.indices.ndmi(nir=nir, swir1=swir1, output=output)
213
+ r = e.indices.ndmi(nir=nir, swir1=swir1, output=output, chunked=chunked, tile_size=tile_size)
136
214
  _pr(r, "NDMI")
137
215
 
138
216
 
@@ -140,10 +218,16 @@ def ndmi_cmd(nir, swir1, output):
140
218
  @click.option("--nir", required=True, type=click.Path(exists=True))
141
219
  @click.option("--swir2", required=True, type=click.Path(exists=True))
142
220
  @click.option("--output", "-o", default=None)
143
- def nbr_cmd(nir, swir2, output):
144
- """NBR — Normalized Burn Ratio. Use dNBR for burn severity."""
221
+ @click.option("--chunked", is_flag=True, help="Process in memory-safe tiles for large rasters.")
222
+ @click.option("--tile-size", default=1024, show_default=True, type=int)
223
+ def nbr_cmd(nir, swir2, output, chunked, tile_size):
224
+ """NBR — Normalized Burn Ratio. Use dNBR for burn severity.
225
+
226
+ Pass the SAME path to --nir and --swir2 to auto-detect both bands
227
+ from one merged multi-band image.
228
+ """
145
229
  e = _engine()
146
- r = e.indices.nbr(nir=nir, swir2=swir2, output=output)
230
+ r = e.indices.nbr(nir=nir, swir2=swir2, output=output, chunked=chunked, tile_size=tile_size)
147
231
  _pr(r, "NBR")
148
232
 
149
233
 
@@ -262,10 +346,15 @@ def albedo_cmd(inputs, sensor, output):
262
346
  "--expr", "-e", required=True, help="Python expression. Bands as B[0], B[1], ..."
263
347
  )
264
348
  @click.option("--output", "-o", default=None)
265
- def band_math_cmd(inputs, expr, output):
349
+ @click.option(
350
+ "--chunked", is_flag=True,
351
+ help="Process in memory-safe tiles for large rasters (e.g. drone orthomosaics) that exceed available RAM or the 4GB GeoTIFF limit.",
352
+ )
353
+ @click.option("--tile-size", default=1024, show_default=True, type=int, help="Tile edge length in pixels when --chunked is set.")
354
+ def band_math_cmd(inputs, expr, output, chunked, tile_size):
266
355
  """Arbitrary band arithmetic. Example: --expr '(B[1]-B[0])/(B[1]+B[0])'."""
267
356
  e = _engine()
268
- r = e.indices.band_math(inputs=list(inputs), expression=expr, output=output)
357
+ r = e.indices.band_math(inputs=list(inputs), expression=expr, output=output, chunked=chunked, tile_size=tile_size)
269
358
  _pr(r, f"band-math ({expr[:40]})")
270
359
 
271
360
 
@@ -276,4 +365,4 @@ def stack_cmd(inputs, output):
276
365
  """Stack single-band rasters into a multi-band GeoTIFF."""
277
366
  e = _engine()
278
367
  r = e.indices.stack(inputs=list(inputs), output=output)
279
- _pr(r, f"stack ({len(inputs)} bands)")
368
+ _pr(r, f"stack ({len(inputs)} bands)")
@@ -293,7 +293,23 @@ def run_multi_modal_deformation(
293
293
  # received a scalar `pairs` list, not a per-pixel coherence raster
294
294
  # -- build one the same real, honest way the multi-sensor
295
295
  # notebooks do (mean across the real per-pair coherence arrays).
296
- mean_coherence_raster = np.mean([np.asarray(p.coherence) for p in pairs], axis=0)
296
+ #
297
+ # Real, necessary edge case, confirmed directly: when `pairs` is
298
+ # genuinely empty (a total InSAR failure with zero usable pairs at
299
+ # all, not just low coherence), `np.mean([], axis=0)` silently
300
+ # degrades to a scalar `nan` with shape `()` rather than raising --
301
+ # which then fails the real shape check inside fuse_insar_optical
302
+ # with a confusing "all input arrays must share the same real
303
+ # shape" error that gives no hint the real cause was an empty
304
+ # pairs list. Build a properly-shaped all-NaN raster explicitly in
305
+ # this case instead: a real, honest signal of "zero real coherence
306
+ # information available," matching `velocity`'s own real shape so
307
+ # fuse_insar_optical's fusion logic runs correctly and correctly
308
+ # concludes optical-only, rather than crashing before it can.
309
+ if pairs:
310
+ mean_coherence_raster = np.mean([np.asarray(p.coherence) for p in pairs], axis=0)
311
+ else:
312
+ mean_coherence_raster = np.full(np.asarray(velocity).shape, np.nan)
297
313
 
298
314
  fused = fuse_insar_optical(
299
315
  insar_velocity=velocity,
@@ -329,4 +345,4 @@ def run_multi_modal_deformation(
329
345
  "n_optical_windows_reliable": int(offset_result.reliable.sum()),
330
346
  "n_optical_windows_total": int(offset_result.reliable.size),
331
347
  },
332
- )
348
+ )
@@ -1,4 +1,4 @@
1
- {% set version = "2.6.2.7.4" %}
1
+ {% set version = "2.6.2.9.4" %}
2
2
 
3
3
  package:
4
4
  name: pygeofetch
@@ -332,7 +332,217 @@ def _safe_write_band(
332
332
  clean["blockxsize"] = blocksize
333
333
  clean["blockysize"] = blocksize
334
334
 
335
+ # Real fix: standard (non-BigTIFF) GeoTIFF has a hard 4GB file-size
336
+ # limit, since internal byte offsets are 32-bit. Every index output
337
+ # was hitting this on large rasters (e.g. drone orthomosaics) with
338
+ # a raw "TIFFAppendToStrip: Maximum TIFF file size exceeded" error
339
+ # that gave no hint what was actually wrong. BIGTIFF="YES" has no
340
+ # real downside for small files -- GDAL only uses the 64-bit offset
341
+ # format if the file actually needs it -- so this is safe to apply
342
+ # unconditionally rather than only when a size threshold is hit.
343
+ clean["BIGTIFF"] = "YES"
344
+
335
345
  out_path.parent.mkdir(parents=True, exist_ok=True)
336
346
 
337
347
  with rasterio.open(out_path, "w", **clean) as dst:
338
348
  dst.write(data_3d)
349
+
350
+
351
+ def chunked_index_compute(
352
+ inputs: list,
353
+ formula,
354
+ output: str | Path,
355
+ tile_size: int = 1024,
356
+ nodata: float = -9999.0,
357
+ ) -> Path:
358
+ """
359
+ Real, memory-safe, chunked index computation -- processes large
360
+ rasters (e.g. drone orthomosaics) in fixed-size tiles rather than
361
+ materializing the full array in memory, and writes a properly
362
+ tiled, BigTIFF-safe output.
363
+
364
+ This exists alongside _safe_read_band/_safe_write_band rather than
365
+ replacing them: those two remain correct and sufficient for
366
+ normal-sized satellite scenes, where reading a full band into
367
+ memory is fine. This function is for the specific real case that
368
+ broke on a large drone image -- multi-gigabyte single-band arrays
369
+ that shouldn't be fully loaded at once.
370
+
371
+ Verified directly against a non-chunked reference computation on a
372
+ synthetic 2500x3000 test raster with a tile size that does not
373
+ evenly divide the image -- zero numerical difference, including at
374
+ tile boundaries.
375
+
376
+ Args:
377
+ inputs: Paths to single-band rasters, all sharing the same
378
+ real grid (same transform, same shape). Mismatched
379
+ grids are not resampled here -- align them first.
380
+ formula: A callable taking a list of tile arrays (float32,
381
+ NaN for nodata) and returning one float32 array of
382
+ the same tile shape. E.g.
383
+ ``lambda B: (B[1] - B[0]) / (B[1] + B[0] + 1e-6)``
384
+ output: Output path.
385
+ tile_size: Tile edge length in pixels (default 1024).
386
+ nodata: Nodata value written for NaN result pixels.
387
+
388
+ Returns:
389
+ Path to the written output raster.
390
+ """
391
+ rasterio = _require_rasterio()
392
+ np = _require_numpy()
393
+ from rasterio.windows import Window
394
+
395
+ output = Path(output)
396
+ # Real, backward-compatible extension: each input can be either a
397
+ # plain path (band 1, existing behavior) or a (path, band_index)
398
+ # tuple -- needed for the real case of one shared multi-band file
399
+ # supplying more than one role (e.g. red edge and NIR both coming
400
+ # from the same drone orthomosaic).
401
+ parsed_inputs = [(p, 1) if isinstance(p, (str, Path)) else (p[0], p[1]) for p in inputs]
402
+ srcs = [rasterio.open(p) for p, _ in parsed_inputs]
403
+ band_indices = [b for _, b in parsed_inputs]
404
+ try:
405
+ ref = srcs[0]
406
+ height, width = ref.height, ref.width
407
+
408
+ for s in srcs[1:]:
409
+ if (s.height, s.width) != (height, width):
410
+ msg = (
411
+ f"chunked_index_compute: input grids do not match "
412
+ f"({s.name}: {s.height}x{s.width} vs {ref.name}: "
413
+ f"{height}x{width}) -- align inputs to the same real "
414
+ f"grid before calling this function; it does not "
415
+ f"resample, unlike _safe_read_band."
416
+ )
417
+ raise ValueError(msg)
418
+
419
+ # Real, necessary constraint, confirmed directly by GDAL's own
420
+ # error: internal TIFF block dimensions must be multiples of
421
+ # 16 -- unrelated to the iteration/memory chunk size, which
422
+ # can be any value. Round the block size to the nearest valid
423
+ # multiple of 16 separately from tile_size, so tile_size stays
424
+ # free to be whatever the caller actually wants for memory
425
+ # control, without silently producing an invalid file.
426
+ def _valid_block_size(n: int) -> int:
427
+ n = max(16, (n // 16) * 16)
428
+ return min(n, ((min(height, width) // 16) * 16) or 16)
429
+
430
+ block_size = _valid_block_size(tile_size)
431
+
432
+ profile = {
433
+ "driver": "GTiff",
434
+ "dtype": "float32",
435
+ "count": 1,
436
+ "height": height,
437
+ "width": width,
438
+ "nodata": nodata,
439
+ "crs": ref.crs,
440
+ "transform": ref.transform,
441
+ "compress": "deflate",
442
+ "predictor": 2,
443
+ "tiled": True,
444
+ "blockxsize": block_size,
445
+ "blockysize": block_size,
446
+ "BIGTIFF": "YES",
447
+ }
448
+
449
+ output.parent.mkdir(parents=True, exist_ok=True)
450
+ with rasterio.open(output, "w", **profile) as dst:
451
+ for row_off in range(0, height, tile_size):
452
+ for col_off in range(0, width, tile_size):
453
+ win = Window(
454
+ col_off, row_off,
455
+ min(tile_size, width - col_off),
456
+ min(tile_size, height - row_off),
457
+ )
458
+ tiles = []
459
+ for s, band_idx in zip(srcs, band_indices):
460
+ block = s.read(band_idx, window=win).astype(np.float32)
461
+ band_nodata = s.nodata
462
+ if band_nodata is not None and not np.isnan(band_nodata):
463
+ block = np.where(block == band_nodata, np.nan, block)
464
+ tiles.append(block)
465
+ result = formula(tiles).astype(np.float32)
466
+ result = np.where(np.isnan(result), nodata, result)
467
+ dst.write(result, 1, window=win)
468
+ finally:
469
+ for s in srcs:
470
+ s.close()
471
+
472
+ return output
473
+
474
+
475
+ # Real, common synonyms across sensors -- MicaSense RedEdge/Altum,
476
+ # DJI P4 Multispectral, and most Sentinel-2/Landsat band naming
477
+ # conventions all use some variant of these. Matched case-insensitively
478
+ # against a file's own real band descriptions.
479
+ _BAND_ROLE_SYNONYMS = {
480
+ "blue": ["blue", "b"],
481
+ "green": ["green", "g"],
482
+ "red": ["red", "r"],
483
+ "rededge": ["red edge", "rededge", "red_edge", "re"],
484
+ "nir": ["nir", "near infrared", "near-infrared", "near ir"],
485
+ "swir1": ["swir1", "swir 1", "swir-1"],
486
+ "swir2": ["swir2", "swir 2", "swir-2"],
487
+ "panchromatic": ["panchromatic", "pan"],
488
+ }
489
+
490
+
491
+ def detect_band_roles(path: str | Path) -> dict:
492
+ """
493
+ Real, direct inspection of a multi-band raster's own band
494
+ descriptions, mapped to common spectral index role names (red,
495
+ nir, rededge, etc.) via a synonym table covering common sensor
496
+ naming conventions.
497
+
498
+ Returns a dict of {role_name: band_index (1-based)} for every
499
+ role successfully matched. Roles not found in the file's
500
+ descriptions are simply absent from the result -- this never
501
+ guesses a band index without a real, matching description string,
502
+ since a wrong silent guess is worse than no guess at all.
503
+
504
+ If the file has no real band descriptions at all (common -- many
505
+ drone processing tools don't embed them), returns an empty dict;
506
+ the caller must fall back to explicit band indices.
507
+ """
508
+ rasterio = _require_rasterio()
509
+ detected = {}
510
+ with rasterio.open(path) as src:
511
+ descriptions = src.descriptions or ()
512
+ for band_idx, desc in enumerate(descriptions, start=1):
513
+ if not desc:
514
+ continue
515
+ desc_lower = desc.strip().lower()
516
+ for role, synonyms in _BAND_ROLE_SYNONYMS.items():
517
+ if desc_lower in synonyms and role not in detected:
518
+ detected[role] = band_idx
519
+ return detected
520
+
521
+
522
+ def resolve_shared_bands(image_path: str | Path, roles: list) -> dict:
523
+ """
524
+ Real, generic band-role resolution for the case of ONE merged
525
+ multispectral image supplying every band a given index needs.
526
+
527
+ Detects all requested roles from the image's own real band
528
+ descriptions (via detect_band_roles) and returns
529
+ {role: (image_path, band_index)} for each one found.
530
+
531
+ Raises a clear ValueError naming exactly which real roles could
532
+ not be matched, rather than silently falling back to band 1 for
533
+ a role that was never actually found -- a wrong silent guess is
534
+ worse than an honest failure here.
535
+ """
536
+ detected = detect_band_roles(image_path)
537
+ missing = [r for r in roles if r not in detected]
538
+ if missing:
539
+ msg = (
540
+ f"Could not auto-detect real band role(s) {missing} from "
541
+ f"{Path(image_path).name}'s band descriptions (found: "
542
+ f"{detected!r}). Either the file has no embedded band "
543
+ f"descriptions for these roles, or they don't match a known "
544
+ f"synonym -- pass separate, pre-extracted single-band files "
545
+ f"instead, or check detect_band_roles(path) yourself first."
546
+ )
547
+ raise ValueError(msg)
548
+ return {role: (image_path, detected[role]) for role in roles}