PyGeoFetch 2.6.2.7__tar.gz → 2.6.2.8__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.8}/CHANGELOG.md +7 -7
  2. {pygeofetch-2.6.2.7/pygeofetch.egg-info → pygeofetch-2.6.2.8}/PKG-INFO +9 -9
  3. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/README.md +8 -8
  4. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/__init__.py +1 -1
  5. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/auto_fallback.py +18 -2
  6. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/meta.yaml +1 -1
  7. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8/pygeofetch.egg-info}/PKG-INFO +9 -9
  8. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch.egg-info/SOURCES.txt +1 -0
  9. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pyproject.toml +1 -1
  10. pygeofetch-2.6.2.8/tests/test_auto_fallback_fixed.py +226 -0
  11. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/LICENSE +0 -0
  12. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/MANIFEST.in +0 -0
  13. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/cli/__init__.py +0 -0
  14. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/cli/auth_commands.py +0 -0
  15. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/cli/config_commands.py +0 -0
  16. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/cli/download_commands.py +0 -0
  17. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/cli/index_commands.py +0 -0
  18. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/cli/main.py +0 -0
  19. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/cli/monitor_commands.py +0 -0
  20. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/cli/multisensor_commands.py +0 -0
  21. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/cli/optical_commands.py +0 -0
  22. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/cli/pipeline_process_commands.py +0 -0
  23. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/cli/postprocess_commands.py +0 -0
  24. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/cli/preprocess_commands.py +0 -0
  25. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/cli/sar_commands.py +0 -0
  26. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/cli/search_commands.py +0 -0
  27. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/config/__init__.py +0 -0
  28. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/config/defaults.yaml +0 -0
  29. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/config/settings.py +0 -0
  30. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/core/__init__.py +0 -0
  31. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/core/authenticator.py +0 -0
  32. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/core/cache_manager.py +0 -0
  33. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/core/data_organizer.py +0 -0
  34. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/core/downloader.py +0 -0
  35. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/core/engine.py +0 -0
  36. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/core/logging.py +0 -0
  37. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/core/monitor.py +0 -0
  38. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/core/orbits.py +0 -0
  39. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/core/scheduler.py +0 -0
  40. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/core/searcher.py +0 -0
  41. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/core/state.py +0 -0
  42. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/geotech/__init__.py +0 -0
  43. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/geotech/risk_mapper.py +0 -0
  44. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/__init__.py +0 -0
  45. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/advanced_safeguards.py +0 -0
  46. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/analysis.py +0 -0
  47. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/annotation.py +0 -0
  48. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/atmosphere.py +0 -0
  49. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/coregister.py +0 -0
  50. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/deburst.py +0 -0
  51. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/esd.py +0 -0
  52. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/extraction.py +0 -0
  53. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/flatearth.py +0 -0
  54. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/geolocation.py +0 -0
  55. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/gpu.py +0 -0
  56. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/interferogram.py +0 -0
  57. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/ionosphere.py +0 -0
  58. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/offset_geometry.py +0 -0
  59. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/offset_tracking.py +0 -0
  60. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/preflight.py +0 -0
  61. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/provenance.py +0 -0
  62. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/ps_selection.py +0 -0
  63. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/splitspectrum_diagnostic.py +0 -0
  64. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/stack_selection.py +0 -0
  65. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/synthetic.py +0 -0
  66. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/timeseries.py +0 -0
  67. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/unwrap.py +0 -0
  68. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/validate.py +0 -0
  69. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/visualize.py +0 -0
  70. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/insar/workflow.py +0 -0
  71. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/models/__init__.py +0 -0
  72. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/models/download_task.py +0 -0
  73. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/models/satellite_data.py +0 -0
  74. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/models/search_query.py +0 -0
  75. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/models/user_auth.py +0 -0
  76. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/multisensor/_init.py +0 -0
  77. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/multisensor/dem_differencing.py +0 -0
  78. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/multisensor/flood_mapping.py +0 -0
  79. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/multisensor/insar_optical_fusion.py +0 -0
  80. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/multisensor/terrain_corrected_change.py +0 -0
  81. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/multisensor/vegetation_disturbance.py +0 -0
  82. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/optical/__init__.py +0 -0
  83. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/optical/offset_tracking.py +0 -0
  84. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/processing/__init__.py +0 -0
  85. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/processing/base.py +0 -0
  86. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/processing/batch.py +0 -0
  87. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/processing/indices.py +0 -0
  88. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/processing/pipeline.py +0 -0
  89. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/processing/postprocessor.py +0 -0
  90. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/processing/preprocessor.py +0 -0
  91. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/processing/sar.py +0 -0
  92. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/processing/uq_exporter.py +0 -0
  93. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/processor/__init__.py +0 -0
  94. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/processor/indices.py +0 -0
  95. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/processor/landsat.py +0 -0
  96. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/processor/loader.py +0 -0
  97. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/processor/stacker.py +0 -0
  98. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/processor/timeseries.py +0 -0
  99. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/__init__.py +0 -0
  100. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/airbus_oneatlas.py +0 -0
  101. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/alaska_satellite_facility.py +0 -0
  102. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/aws_earth.py +0 -0
  103. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/base.py +0 -0
  104. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/copernicus.py +0 -0
  105. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/copernicus_nodes.py +0 -0
  106. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/digitalglobe.py +0 -0
  107. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/earth_explorer_additional.py +0 -0
  108. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/element84.py +0 -0
  109. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/eodag_provider.py +0 -0
  110. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/esa_scihub.py +0 -0
  111. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/geoserver_generic.py +0 -0
  112. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/google_earth_engine.py +0 -0
  113. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/inpe_cbers.py +0 -0
  114. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/isro_bhuvan.py +0 -0
  115. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/jaxa_earth.py +0 -0
  116. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/maxar_gbdx.py +0 -0
  117. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/nasa_earthdata.py +0 -0
  118. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/nasa_earthdata_cloud.py +0 -0
  119. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/noaa_big_data.py +0 -0
  120. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/opentopography.py +0 -0
  121. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/planet.py +0 -0
  122. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/planetary_computer.py +0 -0
  123. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/sentinel_hub.py +0 -0
  124. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/terrabotics.py +0 -0
  125. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/providers/usgs.py +0 -0
  126. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/sar/__init__.py +0 -0
  127. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/sar/_native.py +0 -0
  128. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/sar/_ost.py +0 -0
  129. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/sar/_sarxarray.py +0 -0
  130. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/sar/extraction.py +0 -0
  131. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/sar/math.py +0 -0
  132. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/sar/pipelines.py +0 -0
  133. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/sar/processor.py +0 -0
  134. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/stac_compute/__init__.py +0 -0
  135. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/stac_compute/lightweight_prefetch.py +0 -0
  136. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/utils/__init__.py +0 -0
  137. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/utils/file_utils.py +0 -0
  138. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/utils/gcp_recovery.py +0 -0
  139. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/utils/geo_utils.py +0 -0
  140. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/utils/logging_setup.py +0 -0
  141. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/utils/retry_handler.py +0 -0
  142. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/utils/sar_math.py +0 -0
  143. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/utils/validators.py +0 -0
  144. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/validation/__init__.py +0 -0
  145. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/validation/optical_validator.py +0 -0
  146. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/viz/__init__.py +0 -0
  147. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/viz/map.py +0 -0
  148. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch/viz/plot.py +0 -0
  149. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch.egg-info/dependency_links.txt +0 -0
  150. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch.egg-info/entry_points.txt +0 -0
  151. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch.egg-info/not-zip-safe +0 -0
  152. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch.egg-info/requires.txt +0 -0
  153. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/pygeofetch.egg-info/top_level.txt +0 -0
  154. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/setup.cfg +0 -0
  155. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_advanced_safeguards.py +0 -0
  156. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_airbus_oneatlas.py +0 -0
  157. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_alaska_satellite_facility.py +0 -0
  158. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_atmospheric_circular_regression.py +0 -0
  159. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_auto_fallback.py +0 -0
  160. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_build_sbas_network-new.py +0 -0
  161. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_build_sbas_network.py +0 -0
  162. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_burst_metadata.py +0 -0
  163. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_burst_processing_integration.py +0 -0
  164. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_burst_sync_large_gap.py +0 -0
  165. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_burst_sync_prescreening.py +0 -0
  166. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_burst_synchronization.py +0 -0
  167. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_circuit_breaker_wiring.py +0 -0
  168. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_cli_optical_validation.py +0 -0
  169. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_copernicus_nodes.py +0 -0
  170. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_coregister_integration.py +0 -0
  171. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_coregister_upgrades (1).py +0 -0
  172. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_coregister_upgrades.py +0 -0
  173. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_credential_encryption.py +0 -0
  174. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_data_organizer.py +0 -0
  175. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_dem_differencing_pipeline.py +0 -0
  176. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_digitalglobe.py +0 -0
  177. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_earth_explorer_additional.py +0 -0
  178. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_era5_atmospheric_correction.py +0 -0
  179. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_esa_scihub.py +0 -0
  180. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_extraction.py +0 -0
  181. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_flat_earth_phase.py +0 -0
  182. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_flood_mapping_pipeline.py +0 -0
  183. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_gcp_reprojection.py +0 -0
  184. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_geology_indices_and_rvi_fix.py +0 -0
  185. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_geoserver_generic.py +0 -0
  186. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_google_earth_engine.py +0 -0
  187. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_inpe_cbers.py +0 -0
  188. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_insar.py +0 -0
  189. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_insar_optical_fusion_pipeline.py +0 -0
  190. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_ionosphere.py +0 -0
  191. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_isro_bhuvan.py +0 -0
  192. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_jaxa_earth.py +0 -0
  193. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_lightweight_prefetch.py +0 -0
  194. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_map_preview_consolidation.py +0 -0
  195. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_models.py +0 -0
  196. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_monitor.py +0 -0
  197. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_nasa_earthdata_cloud_s3_fix.py +0 -0
  198. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_nasa_earthdata_public_search.py +0 -0
  199. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_noaa_big_data.py +0 -0
  200. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_offset_tracking.py +0 -0
  201. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_opentopography_pointcloud_usgs.py +0 -0
  202. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_optical_offset_tracking.py +0 -0
  203. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_optical_validation_wiring.py +0 -0
  204. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_optical_validator.py +0 -0
  205. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_optical_validator_stac.py +0 -0
  206. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_pgv_integration_contract.py +0 -0
  207. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_pipeline_process_export.py +0 -0
  208. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_planet_asset_type_fix.py +0 -0
  209. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_preflight_gate.py +0 -0
  210. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_preflight_spatial_baseline.py +0 -0
  211. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_provider_geometry_audit.py +0 -0
  212. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_provider_scene_disambiguation.py +0 -0
  213. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_providers.py +0 -0
  214. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_ps_selection.py +0 -0
  215. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_raster_collocation_integration.py +0 -0
  216. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_reference_pixel_and_despike.py +0 -0
  217. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_risk_mapper.py +0 -0
  218. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_risk_mapping.py +0 -0
  219. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_sar_pipelines.py +0 -0
  220. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_screen_stack_orchestration.py +0 -0
  221. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_search_download.py +0 -0
  222. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_sentinel_hub_catalog_fix.py +0 -0
  223. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_sign_convention.py +0 -0
  224. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_splitspectrum_diagnostic.py +0 -0
  225. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_stack_selection_consolidation.py +0 -0
  226. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_state.py +0 -0
  227. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_strain_and_fusion.py +0 -0
  228. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_terrain_corrected_change_pipeline.py +0 -0
  229. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_topo_r2_bug.py +0 -0
  230. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_uq_exporter.py +0 -0
  231. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_utils.py +0 -0
  232. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_vegetation_disturbance_pipeline.py +0 -0
  233. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/tests/test_viz_plot.py +0 -0
  234. {pygeofetch-2.6.2.7 → pygeofetch-2.6.2.8}/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.8.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.8.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.8] — 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.8.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.8.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.8.4`**, dated
410
+ 2026-07-29 and 2026-07-12. **2.6.2.8.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.8
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.8.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.8.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.8.4"
34
34
  __author__ = "PyGeoFetch Contributors"
35
35
  __license__ = "MIT"
36
36
 
@@ -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.8.4" %}
2
2
 
3
3
  package:
4
4
  name: pygeofetch
@@ -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.8
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.8.1},
373
373
  doi = {10.5281/zenodo.22087230},
374
374
  url = {https://doi.org/10.5281/zenodo.22087230}
375
375
  }
@@ -155,6 +155,7 @@ tests/test_airbus_oneatlas.py
155
155
  tests/test_alaska_satellite_facility.py
156
156
  tests/test_atmospheric_circular_regression.py
157
157
  tests/test_auto_fallback.py
158
+ tests/test_auto_fallback_fixed.py
158
159
  tests/test_build_sbas_network-new.py
159
160
  tests/test_build_sbas_network.py
160
161
  tests/test_burst_metadata.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pygeofetch"
7
- version = "2.6.2.7"
7
+ version = "2.6.2.8"
8
8
  description = "Universal satellite data pipeline + geospatial processing platform"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -0,0 +1,226 @@
1
+ """
2
+ Tests for pygeofetch.insar.auto_fallback.run_multi_modal_deformation.
3
+
4
+ Uses real synthetic rasters and a minimal real-shaped pair stand-in
5
+ (anything with a real `.coherence` array, matching what
6
+ InterferogramGenerator.process_pair() actually returns) rather than
7
+ mocks, so the real quality-metric computation and real fusion path
8
+ both get genuinely exercised.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+
15
+ import numpy as np
16
+ import pytest
17
+ import rasterio
18
+ from rasterio.transform import from_origin
19
+
20
+ from pygeofetch.insar.auto_fallback import (
21
+ SOURCE_INSAR_VALID,
22
+ SOURCE_OPTICAL_FALLBACK,
23
+ _compute_insar_quality_metrics,
24
+ run_multi_modal_deformation,
25
+ )
26
+
27
+ REAL_CRS = "EPSG:32633"
28
+ GROUND_ORIGIN = (500000, 4000000)
29
+
30
+
31
+ @dataclass
32
+ class FakePair:
33
+ """Minimal stand-in for a real InterferogramPair result -- only
34
+ needs the real `.coherence` attribute this router actually reads."""
35
+
36
+ coherence: np.ndarray
37
+
38
+
39
+ def _write_raster(path, data, transform):
40
+ profile = {
41
+ "driver": "GTiff",
42
+ "height": data.shape[0],
43
+ "width": data.shape[1],
44
+ "count": 1,
45
+ "dtype": "float32",
46
+ "crs": REAL_CRS,
47
+ "transform": transform,
48
+ }
49
+ with rasterio.open(path, "w", **profile) as dst:
50
+ dst.write(data.astype("float32"), 1)
51
+ return path
52
+
53
+
54
+ class TestQualityMetrics:
55
+ def test_all_finite_velocity_gives_full_reliable_fraction(self):
56
+ velocity = np.zeros((10, 10))
57
+ pairs = [FakePair(coherence=np.full((10, 10), 0.8))]
58
+ reliable_fraction, mean_coherence = _compute_insar_quality_metrics(
59
+ velocity, pairs
60
+ )
61
+ assert reliable_fraction == 1.0
62
+ assert mean_coherence == pytest.approx(0.8)
63
+
64
+ def test_nan_pixels_correctly_excluded_from_reliable_fraction(self):
65
+ velocity = np.full((10, 10), 1.0)
66
+ velocity[:5, :] = np.nan # real, half the pixels genuinely underdetermined
67
+ pairs = [FakePair(coherence=np.full((10, 10), 0.5))]
68
+ reliable_fraction, _ = _compute_insar_quality_metrics(velocity, pairs)
69
+ assert reliable_fraction == pytest.approx(0.5)
70
+
71
+ def test_mean_coherence_averages_across_real_pairs(self):
72
+ velocity = np.zeros((5, 5))
73
+ pairs = [
74
+ FakePair(coherence=np.full((5, 5), 0.9)),
75
+ FakePair(coherence=np.full((5, 5), 0.3)),
76
+ ]
77
+ _, mean_coherence = _compute_insar_quality_metrics(velocity, pairs)
78
+ assert mean_coherence == pytest.approx(0.6)
79
+
80
+
81
+ class TestHighQualityInsarSkipsOptical:
82
+ def test_fallback_not_triggered_when_insar_is_good(self):
83
+ velocity = np.full((20, 20), -5.0) # fully valid, no NaN
84
+ pairs = [FakePair(coherence=np.full((20, 20), 0.7))] # real, high coherence
85
+
86
+ result = run_multi_modal_deformation(velocity, pairs)
87
+
88
+ assert result.success
89
+ assert not result.used_optical_fallback
90
+ assert result.insar_reliable_fraction == 1.0
91
+ assert result.insar_mean_coherence == pytest.approx(0.7)
92
+ assert (result.processing_source_mask == SOURCE_INSAR_VALID).all()
93
+
94
+ def test_optical_inputs_not_required_when_insar_is_good(self):
95
+ """Real, deliberate efficiency check: no optical_reference_path
96
+ needed at all when InSAR already clears both thresholds."""
97
+ velocity = np.full((10, 10), -2.0)
98
+ pairs = [FakePair(coherence=np.full((10, 10), 0.9))]
99
+ result = run_multi_modal_deformation(velocity, pairs) # no optical args passed
100
+ assert result.success
101
+
102
+
103
+ class TestTotalInsarFailureWithEmptyPairs:
104
+ """Real, previously-crashing edge case: a total InSAR failure where
105
+ literally zero pairs survived (not just low-coherence ones) --
106
+ confirmed to have raised a confusing shape-mismatch error rather
107
+ than the correct, honest optical-only result, since
108
+ np.mean([], axis=0) silently degrades to a scalar NaN instead of a
109
+ properly-shaped array."""
110
+
111
+ def test_empty_pairs_with_nan_velocity_falls_back_cleanly(self, tmp_path):
112
+ rng = np.random.default_rng(3)
113
+ shape = (20, 20)
114
+ velocity = np.full(shape, np.nan) # real, honest "InSAR produced nothing" placeholder
115
+ transform = from_origin(*GROUND_ORIGIN, 10, 10)
116
+
117
+ ref_path = _write_raster(tmp_path / "ref.tif", rng.normal(1000, 50, (40, 40)), transform)
118
+ sec_path = _write_raster(tmp_path / "sec.tif", rng.normal(1000, 50, (40, 40)), transform)
119
+
120
+ result = run_multi_modal_deformation(
121
+ velocity, pairs=[], # genuinely empty -- not just low-coherence pairs
122
+ optical_reference_path=ref_path, optical_secondary_path=sec_path,
123
+ reference_transform=transform, insar_transform=transform, insar_crs=REAL_CRS,
124
+ window_size=16, step_size=8,
125
+ )
126
+ assert result.success, result.error
127
+ assert result.used_optical_fallback
128
+ assert result.insar_reliable_fraction == 0.0
129
+ assert result.insar_mean_coherence == 0.0
130
+ # Real, correct consequence: with InSAR velocity entirely
131
+ # invalid, every pixel where optical actually has real,
132
+ # windowed coverage must be optical-fallback. Pixels where
133
+ # optical ALSO has no real coverage (a genuine edge effect near
134
+ # the grid border, not a bug -- see fuse_insar_optical's own
135
+ # real handling of "both sources invalid") fall through to the
136
+ # blended/transition label by that function's own existing
137
+ # convention, not a new behavior introduced by this fix.
138
+ has_real_optical_coverage = np.isfinite(result.optical_displacement_magnitude)
139
+ assert (result.processing_source_mask[has_real_optical_coverage] == SOURCE_OPTICAL_FALLBACK).all()
140
+ assert not (result.processing_source_mask == SOURCE_INSAR_VALID).any()
141
+
142
+
143
+ class TestLowQualityInsarTriggersRealFallback:
144
+ def test_low_coherence_triggers_fallback_and_fuses(self, tmp_path):
145
+ rng = np.random.default_rng(1)
146
+ transform = from_origin(*GROUND_ORIGIN, 10, 10)
147
+
148
+ velocity = np.full((20, 20), -10.0)
149
+ pairs = [FakePair(coherence=np.full((20, 20), 0.15))] # real, low coherence
150
+
151
+ ref_data = rng.normal(1000, 100, (40, 40)).astype("float32")
152
+ sec_data = ref_data + rng.normal(0, 5, (40, 40)).astype("float32")
153
+ ref_path = _write_raster(
154
+ tmp_path / "ref.tif", ref_data, from_origin(*GROUND_ORIGIN, 10, 10)
155
+ )
156
+ sec_path = _write_raster(
157
+ tmp_path / "sec.tif", sec_data, from_origin(*GROUND_ORIGIN, 10, 10)
158
+ )
159
+
160
+ with rasterio.open(ref_path) as src:
161
+ reference_transform = src.transform
162
+
163
+ result = run_multi_modal_deformation(
164
+ velocity,
165
+ pairs,
166
+ optical_reference_path=ref_path,
167
+ optical_secondary_path=sec_path,
168
+ reference_transform=reference_transform,
169
+ insar_transform=transform,
170
+ insar_crs=REAL_CRS,
171
+ window_size=16,
172
+ step_size=8,
173
+ )
174
+
175
+ assert result.success, result.error
176
+ assert result.used_optical_fallback
177
+ assert result.insar_mean_coherence == pytest.approx(0.15)
178
+ # Real, correct consequence of uniformly low coherence: fully
179
+ # optical-dominant, matching fuse_insar_optical's own real,
180
+ # documented behavior at this coherence level.
181
+ assert (result.processing_source_mask == SOURCE_OPTICAL_FALLBACK).mean() > 0.5
182
+
183
+ def test_missing_optical_inputs_raises_clear_error(self):
184
+ velocity = np.full((10, 10), -5.0)
185
+ pairs = [FakePair(coherence=np.full((10, 10), 0.1))] # real, low coherence
186
+
187
+ with pytest.raises(ValueError, match="optical_reference_path"):
188
+ run_multi_modal_deformation(
189
+ velocity, pairs
190
+ ) # no optical args -- must raise
191
+
192
+ def test_or_not_and_low_coverage_alone_triggers_fallback(self, tmp_path):
193
+ """Real, deliberate OR logic: even with high coherence, low
194
+ real coverage (many NaN pixels) alone must trigger fallback."""
195
+ rng = np.random.default_rng(2)
196
+ velocity = np.full((20, 20), np.nan)
197
+ velocity[:2, :] = -5.0 # only 10% real coverage
198
+ pairs = [FakePair(coherence=np.full((20, 20), 0.9))] # coherence itself is fine
199
+
200
+ ref_path = _write_raster(
201
+ tmp_path / "ref.tif",
202
+ rng.normal(1000, 50, (40, 40)),
203
+ from_origin(*GROUND_ORIGIN, 10, 10),
204
+ )
205
+ sec_path = _write_raster(
206
+ tmp_path / "sec.tif",
207
+ rng.normal(1000, 50, (40, 40)),
208
+ from_origin(*GROUND_ORIGIN, 10, 10),
209
+ )
210
+ with rasterio.open(ref_path) as src:
211
+ reference_transform = src.transform
212
+
213
+ result = run_multi_modal_deformation(
214
+ velocity,
215
+ pairs,
216
+ optical_reference_path=ref_path,
217
+ optical_secondary_path=sec_path,
218
+ reference_transform=reference_transform,
219
+ insar_transform=from_origin(*GROUND_ORIGIN, 10, 10),
220
+ insar_crs=REAL_CRS,
221
+ window_size=16,
222
+ step_size=8,
223
+ )
224
+ assert (
225
+ result.used_optical_fallback
226
+ ) # triggered by coverage alone, despite good coherence
File without changes
File without changes