pygeofetch 2.0.0__tar.gz → 2.0.2__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 (145) hide show
  1. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/CHANGELOG.md +6 -6
  2. {pygeofetch-2.0.0/pygeofetch.egg-info → pygeofetch-2.0.2}/PKG-INFO +2 -2
  3. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/__init__.py +2 -2
  4. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/cli/main.py +2 -2
  5. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/__init__.py +1 -0
  6. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/coregister.py +140 -52
  7. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/deburst.py +38 -56
  8. pygeofetch-2.0.2/pygeofetch/insar/esd.py +361 -0
  9. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/interferogram.py +665 -674
  10. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/meta.yaml +1 -1
  11. {pygeofetch-2.0.0 → pygeofetch-2.0.2/pygeofetch.egg-info}/PKG-INFO +2 -2
  12. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch.egg-info/requires.txt +1 -1
  13. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pyproject.toml +2 -2
  14. pygeofetch-2.0.0/pygeofetch/insar/esd.py +0 -333
  15. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/LICENSE +0 -0
  16. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/MANIFEST.in +0 -0
  17. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/README.md +0 -0
  18. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/cli/__init__.py +0 -0
  19. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/cli/auth_commands.py +0 -0
  20. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/cli/config_commands.py +0 -0
  21. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/cli/download_commands.py +0 -0
  22. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/cli/index_commands.py +0 -0
  23. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/cli/pipeline_process_commands.py +0 -0
  24. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/cli/postprocess_commands.py +0 -0
  25. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/cli/preprocess_commands.py +0 -0
  26. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/cli/sar_commands.py +0 -0
  27. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/cli/search_commands.py +0 -0
  28. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/config/__init__.py +0 -0
  29. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/config/defaults.yaml +0 -0
  30. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/config/settings.py +0 -0
  31. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/core/__init__.py +0 -0
  32. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/core/authenticator.py +0 -0
  33. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/core/cache_manager.py +0 -0
  34. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/core/downloader.py +0 -0
  35. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/core/engine.py +0 -0
  36. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/core/logging.py +0 -0
  37. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/core/orbits.py +0 -0
  38. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/core/scheduler.py +0 -0
  39. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/core/searcher.py +0 -0
  40. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/annotation.py +0 -0
  41. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/atmosphere.py +0 -0
  42. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/extraction.py +0 -0
  43. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/flatearth.py +0 -0
  44. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/geolocation.py +0 -0
  45. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/gpu.py +0 -0
  46. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/ionosphere.py +0 -0
  47. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/splitspectrum_diagnostic.py +0 -0
  48. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/stack_selection.py +0 -0
  49. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/synthetic.py +0 -0
  50. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/timeseries.py +0 -0
  51. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/unwrap.py +0 -0
  52. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/validate.py +0 -0
  53. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/visualize.py +0 -0
  54. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/insar/workflow.py +0 -0
  55. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/models/__init__.py +0 -0
  56. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/models/download_task.py +0 -0
  57. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/models/satellite_data.py +0 -0
  58. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/models/search_query.py +0 -0
  59. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/models/user_auth.py +0 -0
  60. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/processing/__init__.py +0 -0
  61. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/processing/base.py +0 -0
  62. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/processing/batch.py +0 -0
  63. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/processing/indices.py +0 -0
  64. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/processing/pipeline.py +0 -0
  65. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/processing/postprocessor.py +0 -0
  66. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/processing/preprocessor.py +0 -0
  67. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/processing/sar.py +0 -0
  68. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/processor/__init__.py +0 -0
  69. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/processor/indices.py +0 -0
  70. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/processor/landsat.py +0 -0
  71. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/processor/loader.py +0 -0
  72. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/processor/stacker.py +0 -0
  73. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/processor/timeseries.py +0 -0
  74. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/__init__.py +0 -0
  75. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/airbus_oneatlas.py +0 -0
  76. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/alaska_satellite_facility.py +0 -0
  77. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/aws_earth.py +0 -0
  78. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/base.py +0 -0
  79. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/copernicus.py +0 -0
  80. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/digitalglobe.py +0 -0
  81. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/earth_explorer_additional.py +0 -0
  82. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/element84.py +0 -0
  83. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/eodag_provider.py +0 -0
  84. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/esa_scihub.py +0 -0
  85. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/geoserver_generic.py +0 -0
  86. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/google_earth_engine.py +0 -0
  87. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/inpe_cbers.py +0 -0
  88. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/isro_bhuvan.py +0 -0
  89. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/jaxa_earth.py +0 -0
  90. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/maxar_gbdx.py +0 -0
  91. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/nasa_earthdata.py +0 -0
  92. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/nasa_earthdata_cloud.py +0 -0
  93. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/noaa_big_data.py +0 -0
  94. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/opentopography.py +0 -0
  95. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/planet.py +0 -0
  96. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/planetary_computer.py +0 -0
  97. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/sentinel_hub.py +0 -0
  98. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/terrabotics.py +0 -0
  99. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/providers/usgs.py +0 -0
  100. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/sar/__init__.py +0 -0
  101. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/sar/_native.py +0 -0
  102. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/sar/_ost.py +0 -0
  103. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/sar/_sarxarray.py +0 -0
  104. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/sar/extraction.py +0 -0
  105. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/sar/processor.py +0 -0
  106. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/utils/__init__.py +0 -0
  107. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/utils/file_utils.py +0 -0
  108. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/utils/gcp_recovery.py +0 -0
  109. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/utils/geo_utils.py +0 -0
  110. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/utils/logging_setup.py +0 -0
  111. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/utils/retry_handler.py +0 -0
  112. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/utils/validators.py +0 -0
  113. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/viz/__init__.py +0 -0
  114. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/viz/map.py +0 -0
  115. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch/viz/plot.py +0 -0
  116. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch.egg-info/SOURCES.txt +0 -0
  117. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch.egg-info/dependency_links.txt +0 -0
  118. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch.egg-info/entry_points.txt +0 -0
  119. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch.egg-info/not-zip-safe +0 -0
  120. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/pygeofetch.egg-info/top_level.txt +0 -0
  121. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/setup.cfg +0 -0
  122. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_atmospheric_circular_regression (1).py +0 -0
  123. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_atmospheric_circular_regression.py +0 -0
  124. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_build_sbas_network.py +0 -0
  125. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_burst_metadata.py +0 -0
  126. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_burst_processing_integration.py +0 -0
  127. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_coregister_integration.py +0 -0
  128. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_coregister_upgrades.py +0 -0
  129. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_era5_atmospheric_correction.py +0 -0
  130. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_extraction.py +0 -0
  131. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_flat_earth_phase.py +0 -0
  132. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_gcp_reprojection.py +0 -0
  133. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_insar.py +0 -0
  134. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_ionosphere.py +0 -0
  135. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_models.py +0 -0
  136. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_pgv_integration_contract.py +0 -0
  137. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_provider_geometry_audit (1).py +0 -0
  138. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_provider_geometry_audit.py +0 -0
  139. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_provider_scene_disambiguation.py +0 -0
  140. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_providers.py +0 -0
  141. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_raster_collocation_integration.py +0 -0
  142. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_search_download.py +0 -0
  143. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_splitspectrum_diagnostic.py +0 -0
  144. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_utils.py +0 -0
  145. {pygeofetch-2.0.0 → pygeofetch-2.0.2}/tests/test_viz_plot.py +0 -0
@@ -1,9 +1,9 @@
1
1
  # Changelog
2
2
 
3
3
  All notable changes to PyGeoFetch are documented here.
4
- Format follows [Keep a Changelog](https://keepachangelog.com/en/2.0.0/).
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/2.0.2/).
5
5
 
6
- ## [2.0.0] — 2026-07-29
6
+ ## [2.0.2] — 2026-07-29
7
7
 
8
8
  ### Added
9
9
  - Major SNAPHU unwrapping improvements for urban InSAR applications
@@ -51,7 +51,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/2.0.0/).
51
51
 
52
52
  ### Migration Guide
53
53
  ```python
54
- # Before (v2.0.0)
54
+ # Before (v2.0.2)
55
55
  unwrapper = PhaseUnwrapper(cost_mode="defo", init_method="mcf")
56
56
  unwrapped = unwrapper.unwrap(phase, coherence, nlooks=2.0)
57
57
 
@@ -72,7 +72,7 @@ unwrapped = unwrapper.unwrap(
72
72
 
73
73
  ---
74
74
 
75
- ## [2.0.0] — 2026-07-12
75
+ ## [2.0.2] — 2026-07-12
76
76
 
77
77
  ### Added
78
78
  - Federated search across 22+ satellite data providers
@@ -188,8 +188,8 @@ unwrapped = unwrapper.unwrap(
188
188
 
189
189
  | Version | Date | Key Changes |
190
190
  |---------|------------|-------------|
191
- | **2.0.0** | 2026-07-29 | SNAPHU unwrapping fixes, 87.1% reliable pixels |
192
- | **2.0.0** | 2026-07-12 | 22 providers, 17 indices, Sentinel-1C/D |
191
+ | **2.0.2** | 2026-07-29 | SNAPHU unwrapping fixes, 87.1% reliable pixels |
192
+ | **2.0.2** | 2026-07-12 | 22 providers, 17 indices, Sentinel-1C/D |
193
193
  | **1.9.2** | 2026-06-15 | Batch processing, parallel downloads |
194
194
  | **1.9.1** | 2026-05-20 | 3D terrain, SNAPHU integration, time series |
195
195
  | **1.9.0** | 2026-04-25 | Initial public release |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygeofetch
3
- Version: 2.0.0
3
+ Version: 2.0.2
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
@@ -85,7 +85,7 @@ Requires-Dist: localtileserver>=1.0.0; extra == "viz"
85
85
  Requires-Dist: ipysheet>=0.6.0; extra == "viz"
86
86
  Requires-Dist: ipyleaflet>=0.17.0; extra == "viz"
87
87
  Requires-Dist: ipywidgets>=8.0.0; extra == "viz"
88
- Requires-Dist: ipyevents>=2.0.0; extra == "viz"
88
+ Requires-Dist: ipyevents>=2.0.2; extra == "viz"
89
89
  Requires-Dist: ipyfilechooser>=0.6.0; extra == "viz"
90
90
  Requires-Dist: sidecar>=0.6.0; extra == "viz"
91
91
  Requires-Dist: bokeh>=3.0.0; extra == "viz"
@@ -1,5 +1,5 @@
1
1
  """
2
- PyGeoFetch v2.0.0 — Universal Satellite Data Pipeline.
2
+ PyGeoFetch v2.0.2 — Universal Satellite Data Pipeline.
3
3
 
4
4
  Unified access to 22+ satellite data providers with a consistent
5
5
  Python API and CLI. Supports authentication management, federated search,
@@ -30,7 +30,7 @@ CLI::
30
30
  pygeofetch doctor
31
31
  """
32
32
 
33
- __version__ = "2.0.0"
33
+ __version__ = "2.0.2"
34
34
  __author__ = "PyGeoFetch Contributors"
35
35
  __license__ = "MIT"
36
36
 
@@ -1,5 +1,5 @@
1
1
  """
2
- PyGeoFetch CLI v2.0.0 main entry point.
2
+ PyGeoFetch CLI v2.0.2 main entry point.
3
3
 
4
4
  Full command reference:
5
5
  pygeofetch auth — credential management
@@ -71,7 +71,7 @@ def cli(
71
71
  ) -> None:
72
72
  """
73
73
  \b
74
- PyGeoFetch v2.0.0 — Universal Satellite Data Pipeline
74
+ PyGeoFetch v2.0.2 — Universal Satellite Data Pipeline
75
75
  ===========================================================
76
76
  Unified access to 22+ satellite data providers.
77
77
 
@@ -158,4 +158,5 @@ __all__ = [
158
158
  "collocate_by_geocoding",
159
159
  "CoregistrationQuality",
160
160
  "select_consistent_geometry",
161
+
161
162
  ]
@@ -665,23 +665,67 @@ def _sample_complex_imagette(data, center_row: float, center_col: float, size: i
665
665
  return (real + 1j * imag).astype(np.complex64)
666
666
 
667
667
 
668
- def _imagette_coherence(ref_imagette, sec_imagette) -> float:
668
+ # def _imagette_coherence(ref_imagette, sec_imagette) -> float:
669
+ # """
670
+ # Single scalar complex coherence between two same-shape imagettes —
671
+ # SNAP CrossCorrelationOp's "Method 1" (no internal sliding window;
672
+ # the whole imagette is treated as one coherence estimation window):
673
+ # https://step.esa.int/main/wp-content/help/versions/13.0.0/snap-toolboxes/eu.esa.microwavetbx.sar.op.insar.ui/operators/CrossCorrelationOp.html
674
+ # """
675
+ # import numpy as np
676
+
677
+ # num = abs(np.mean(ref_imagette * np.conj(sec_imagette)))
678
+ # denom = np.sqrt(
679
+ # np.mean(np.abs(ref_imagette) ** 2) * np.mean(np.abs(sec_imagette) ** 2)
680
+ # )
681
+ # if denom < 1e-12:
682
+ # return 0.0
683
+ # return float(np.clip(num / denom, 0.0, 1.0))
684
+
685
+
686
+ # def _imagette_coherence(ref_imagette, sec_imagette) -> float:
687
+
688
+ # import numpy as np
689
+
690
+ # num = abs(np.mean(ref_imagette * np.conj(sec_imagette)))
691
+ # denom = np.sqrt(np.mean(np.abs(ref_imagette) ** 2) * np.mean(np.abs(sec_imagette) ** 2))
692
+ # if denom < 1e-12:
693
+ # return 0.0
694
+ # return float(np.clip(num / denom, 0.0, 1.0))
695
+
696
+ def _imagette_coherence(ref_imagette, sec_imagette, window_size: int = 5) -> float:
669
697
  """
670
- Single scalar complex coherence between two same-shape imagettes —
671
- SNAP CrossCorrelationOp's "Method 1" (no internal sliding window;
672
- the whole imagette is treated as one coherence estimation window):
673
- https://step.esa.int/main/wp-content/help/versions/13.0.0/snap-toolboxes/eu.esa.microwavetbx.sar.op.insar.ui/operators/CrossCorrelationOp.html
698
+ Calculate coherence using windowed averaging (standard ESD approach).
699
+
700
+ Args:
701
+ ref_imagette: Reference imagette (complex array)
702
+ sec_imagette: Secondary imagette (complex array)
703
+ window_size: Window size for coherence calculation (default: 5)
704
+
705
+ Returns:
706
+ Average coherence value
674
707
  """
675
708
  import numpy as np
676
-
677
- num = abs(np.mean(ref_imagette * np.conj(sec_imagette)))
678
- denom = np.sqrt(
679
- np.mean(np.abs(ref_imagette) ** 2) * np.mean(np.abs(sec_imagette) ** 2)
680
- )
681
- if denom < 1e-12:
682
- return 0.0
683
- return float(np.clip(num / denom, 0.0, 1.0))
684
-
709
+ from scipy.ndimage import uniform_filter
710
+
711
+ # Calculate complex cross-correlation
712
+ cross_corr = ref_imagette * np.conj(sec_imagette)
713
+
714
+ # Calculate windowed averages
715
+ num_real = uniform_filter(cross_corr.real, size=window_size)
716
+ num_imag = uniform_filter(cross_corr.imag, size=window_size)
717
+ num = np.sqrt(num_real**2 + num_imag**2)
718
+
719
+ denom_ref = uniform_filter(np.abs(ref_imagette)**2, size=window_size)
720
+ denom_sec = uniform_filter(np.abs(sec_imagette)**2, size=window_size)
721
+ denom = np.sqrt(denom_ref * denom_sec)
722
+
723
+ # Calculate coherence
724
+ coherence = np.where(denom > 1e-12, num / denom, 0.0)
725
+ coherence = np.clip(coherence, 0.0, 1.0)
726
+
727
+ # Return average coherence
728
+ return float(np.mean(coherence))
685
729
 
686
730
  def refine_offsets_by_coherence(
687
731
  ref_complex,
@@ -1124,11 +1168,11 @@ def resample_with_offset_field(
1124
1168
  import numpy as np
1125
1169
  from scipy.ndimage import map_coordinates
1126
1170
 
1127
- h, w = data.shape
1128
- out_real = np.empty((h, w), dtype=np.float32)
1129
- out_imag = np.empty((h, w), dtype=np.float32)
1130
- data_real = data.real
1131
- data_imag = data.imag
1171
+ # h, w = data.shape
1172
+ # out_real = np.empty((h, w), dtype=np.float32)
1173
+ # out_imag = np.empty((h, w), dtype=np.float32)
1174
+ # data_real = data.real
1175
+ # data_imag = data.imag
1132
1176
 
1133
1177
  # Process in row chunks rather than building ~10 full-resolution
1134
1178
  # float64 intermediate arrays (row_idx, col_idx, ref_global_row/col,
@@ -1142,41 +1186,85 @@ def resample_with_offset_field(
1142
1186
  # more than sufficient precision for sub-pixel offset resampling and
1143
1187
  # halves the footprint on its own; chunking bounds peak memory to
1144
1188
  # one row-block regardless of total crop size.
1145
- chunk_rows = max(1, min(h, 2000))
1146
- for row_start in range(0, h, chunk_rows):
1147
- row_end = min(row_start + chunk_rows, h)
1148
- row_idx, col_idx = np.mgrid[row_start:row_end, 0:w].astype(np.float32)
1149
-
1150
- # row_idx/col_idx are the reference grid's local (0-based)
1151
- # coordinates. Convert to full-scene coordinates to correctly
1152
- # evaluate the fitted offset functions, which were built on real
1153
- # annotation-derived full-scene coordinates, not any particular
1154
- # crop's local ones.
1155
- ref_global_row = row_idx + ref_row_offset
1156
- ref_global_col = col_idx + ref_col_offset
1157
-
1158
- offset_row = row_offset_fn(ref_global_row, ref_global_col)
1159
- offset_col = col_offset_fn(ref_global_row, ref_global_col)
1160
-
1161
- # Where this pixel's secondary counterpart is, in full-scene coordinates
1162
- sec_global_row = ref_global_row + offset_row
1163
- sec_global_col = ref_global_col + offset_col
1164
-
1165
- # Convert to the SECONDARY array's own local coordinates for
1166
- # actually indexing into `data` -- the secondary crop can have a
1167
- # different offset than the reference crop, so this is not the
1168
- # same subtraction as ref_row_offset/ref_col_offset above.
1169
- sample_rows = sec_global_row - sec_row_offset
1170
- sample_cols = sec_global_col - sec_col_offset
1171
-
1172
- out_real[row_start:row_end] = map_coordinates(
1173
- data_real, [sample_rows, sample_cols], order=1, mode="constant", cval=0.0
1174
- )
1175
- out_imag[row_start:row_end] = map_coordinates(
1176
- data_imag, [sample_rows, sample_cols], order=1, mode="constant", cval=0.0
1177
- )
1189
+
1190
+
1191
+ h, w = data.shape
1192
+ out_real = np.empty((h, w), dtype=np.float32)
1193
+ out_imag = np.empty((h, w), dtype=np.float32)
1194
+ data_real = data.real
1195
+ data_imag = data.imag
1196
+
1197
+ # Process in 2D tiles for cache locality and strict memory bounding
1198
+ tile_h, tile_w = 1024, 1024
1199
+
1200
+ for r_start in range(0, h, tile_h):
1201
+ r_end = min(r_start + tile_h, h)
1202
+ for c_start in range(0, w, tile_w):
1203
+ c_end = min(c_start + tile_w, w)
1204
+
1205
+ # Use ogrid to create broadcastable 1D arrays instead of full 2D mgrid matrices
1206
+ row_idx = np.arange(r_start, r_end, dtype=np.float32)[:, None]
1207
+ col_idx = np.arange(c_start, c_end, dtype=np.float32)[None, :]
1208
+
1209
+ ref_global_row = row_idx + ref_row_offset
1210
+ ref_global_col = col_idx + ref_col_offset
1211
+
1212
+ # Evaluate polynomial (broadcasts automatically, minimal memory)
1213
+ offset_row = row_offset_fn(ref_global_row, ref_global_col)
1214
+ offset_col = col_offset_fn(ref_global_row, ref_global_col)
1215
+
1216
+ sample_rows = ref_global_row + offset_row - sec_row_offset
1217
+ sample_cols = ref_global_col + offset_col - sec_col_offset
1218
+
1219
+ # Broadcast to 2D for map_coordinates
1220
+ sample_rows_2d = np.broadcast_to(sample_rows, (r_end - r_start, c_end - c_start))
1221
+ sample_cols_2d = np.broadcast_to(sample_cols, (r_end - r_start, c_end - c_start))
1222
+
1223
+ out_real[r_start:r_end, c_start:c_end] = map_coordinates(
1224
+ data_real, [sample_rows_2d, sample_cols_2d], order=1, mode="constant", cval=0.0
1225
+ )
1226
+ out_imag[r_start:r_end, c_start:c_end] = map_coordinates(
1227
+ data_imag, [sample_rows_2d, sample_cols_2d], order=1, mode="constant", cval=0.0
1228
+ )
1229
+
1230
+ del sample_rows_2d, sample_cols_2d, offset_row, offset_col
1178
1231
 
1179
1232
  return (out_real + 1j * out_imag).astype(np.complex64)
1233
+ # chunk_rows = max(1, min(h, 2000))
1234
+ # for row_start in range(0, h, chunk_rows):
1235
+ # row_end = min(row_start + chunk_rows, h)
1236
+ # row_idx, col_idx = np.mgrid[row_start:row_end, 0:w].astype(np.float32)
1237
+
1238
+ # # row_idx/col_idx are the reference grid's local (0-based)
1239
+ # # coordinates. Convert to full-scene coordinates to correctly
1240
+ # # evaluate the fitted offset functions, which were built on real
1241
+ # # annotation-derived full-scene coordinates, not any particular
1242
+ # # crop's local ones.
1243
+ # ref_global_row = row_idx + ref_row_offset
1244
+ # ref_global_col = col_idx + ref_col_offset
1245
+
1246
+ # offset_row = row_offset_fn(ref_global_row, ref_global_col)
1247
+ # offset_col = col_offset_fn(ref_global_row, ref_global_col)
1248
+
1249
+ # # Where this pixel's secondary counterpart is, in full-scene coordinates
1250
+ # sec_global_row = ref_global_row + offset_row
1251
+ # sec_global_col = ref_global_col + offset_col
1252
+
1253
+ # # Convert to the SECONDARY array's own local coordinates for
1254
+ # # actually indexing into `data` -- the secondary crop can have a
1255
+ # # different offset than the reference crop, so this is not the
1256
+ # # same subtraction as ref_row_offset/ref_col_offset above.
1257
+ # sample_rows = sec_global_row - sec_row_offset
1258
+ # sample_cols = sec_global_col - sec_col_offset
1259
+
1260
+ # out_real[row_start:row_end] = map_coordinates(
1261
+ # data_real, [sample_rows, sample_cols], order=1, mode="constant", cval=0.0
1262
+ # )
1263
+ # out_imag[row_start:row_end] = map_coordinates(
1264
+ # data_imag, [sample_rows, sample_cols], order=1, mode="constant", cval=0.0
1265
+ # )
1266
+
1267
+ # return (out_real + 1j * out_imag).astype(np.complex64)
1180
1268
 
1181
1269
 
1182
1270
  def _interpolate(orbit, t):
@@ -3,32 +3,27 @@ TOPS deburst — remove real burst-boundary overlap/redundancy, matching
3
3
  SNAP's own S-1 TOPS Deburst operator.
4
4
 
5
5
  Algorithm confirmed directly against ESA's own STEP documentation for
6
- TOPSARDeburstOp (step.esa.int/main/wp-content/help/.../TOPSARDeburstOp.html),
7
- not inferred or guessed:
8
-
9
- "In the azimuth direction, bursts are merged according to their
10
- zero Doppler time... The merge time is determined by the average
11
- of the last line of the first burst and the first line of the next
12
- burst. For each range cell, the merging time is quantised to the
13
- nearest output azimuth cell."
6
+ TOPSARDeburstOp:
7
+ "In the azimuth direction, bursts are merged according to their
8
+ zero Doppler time... The merge time is determined by the average
9
+ of the last line of the first burst and the first line of the next
10
+ burst. For each range cell, the merging time is quantised to the
11
+ nearest output azimuth cell."
14
12
 
15
13
  Why this matters: without deburst, adjacent bursts' real overlap
16
14
  regions both remain in the data, and the SAME ground area gets imaged
17
15
  twice by two different (radiometrically and phase-wise inconsistent)
18
- parts of the antenna beam pattern -- this is a real, confirmed source
19
- of visible striping and degraded coherence at burst boundaries, and it
20
- was never being corrected anywhere in this pipeline before this module
21
- (pygeofetch's own existing ESD refinement docstring already admits this
22
- directly: "has no effect on already-deburst/stripmap data" -- data
23
- passed through it was never actually deburst).
16
+ parts of the antenna beam pattern — this is a real, confirmed source
17
+ of visible striping and degraded coherence at burst boundaries.
24
18
  """
25
-
26
19
  from __future__ import annotations
27
20
 
28
21
  import logging
29
22
  from datetime import timedelta
30
23
  from typing import List, Tuple
31
24
 
25
+ import numpy as np
26
+
32
27
  from pygeofetch.insar.annotation import SwathTiming
33
28
 
34
29
  logger = logging.getLogger("pygeofetch.insar.deburst")
@@ -41,23 +36,9 @@ def compute_burst_row_ranges(
41
36
  Compute each burst's real, kept (non-overlapping) row range, in
42
37
  that burst's own local 0-indexed row coordinates.
43
38
 
44
- Verified before use: on a small, controlled synthetic case with
45
- known burst timing, the computed ranges left no gaps and no
46
- duplicate coverage, and their total length matched an
47
- independently-computed expected total (span from the first burst's
48
- start to the last burst's last line, at one line per
49
- azimuth_time_interval_s) exactly.
50
-
51
39
  Args:
52
- swath_timing: Real burst metadata from
53
- annotation.parse_burst_info().
54
- azimuth_time_interval_s: Real per-line time spacing, from the
55
- same annotation XML's SLCGeometry
56
- (imageAnnotation/imageInformation/azimuthTimeInterval)
57
- -- must be the SAME value used for both, since
58
- the cut-time calculation converts between the
59
- two bursts' local row coordinates via this
60
- shared spacing.
40
+ swath_timing: Real burst metadata from annotation.parse_burst_info().
41
+ azimuth_time_interval_s: Real per-line time spacing.
61
42
 
62
43
  Returns:
63
44
  List of (keep_start_row, keep_end_row) inclusive, local to
@@ -72,23 +53,26 @@ def compute_burst_row_ranges(
72
53
  if n == 1:
73
54
  return [(0, lines_per_burst - 1)]
74
55
 
75
- # Real cut row, in each side's own local coordinates, for every
76
- # adjacent burst pair
77
- cut_in_prev = [None] * n # cut row (local to burst i) ending burst i's kept range
78
- cut_in_next = [None] * n # cut row (local to burst i) starting burst i's kept range
56
+ # Real cut row, in each side's own local coordinates
57
+ cut_in_prev = [None] * n
58
+ cut_in_next = [None] * n
79
59
 
80
60
  for i in range(n - 1):
81
61
  burst_i_last_line_time = bursts[i].azimuth_time + timedelta(
82
62
  seconds=(lines_per_burst - 1) * azimuth_time_interval_s
83
63
  )
84
64
  burst_next_first_line_time = bursts[i + 1].azimuth_time
85
- cut_time = burst_i_last_line_time + (burst_next_first_line_time - burst_i_last_line_time) / 2
65
+ cut_time = burst_i_last_line_time + (
66
+ burst_next_first_line_time - burst_i_last_line_time
67
+ ) / 2
86
68
 
87
69
  cut_row_in_i = round(
88
- (cut_time - bursts[i].azimuth_time).total_seconds() / azimuth_time_interval_s
70
+ (cut_time - bursts[i].azimuth_time).total_seconds()
71
+ / azimuth_time_interval_s
89
72
  )
90
73
  cut_row_in_next = round(
91
- (cut_time - bursts[i + 1].azimuth_time).total_seconds() / azimuth_time_interval_s
74
+ (cut_time - bursts[i + 1].azimuth_time).total_seconds()
75
+ / azimuth_time_interval_s
92
76
  )
93
77
 
94
78
  cut_in_prev[i] = cut_row_in_i
@@ -100,11 +84,10 @@ def compute_burst_row_ranges(
100
84
  keep_end = (lines_per_burst - 1) if i == n - 1 else cut_in_prev[i]
101
85
  keep_start = max(0, min(keep_start, lines_per_burst - 1))
102
86
  keep_end = max(0, min(keep_end, lines_per_burst - 1))
87
+
103
88
  if keep_start > keep_end:
104
89
  logger.warning(
105
- "Burst %d: computed keep range is empty (start=%d > end=%d) "
106
- "— unusually large real overlap or unusual burst timing; "
107
- "this burst will contribute no rows to the debursted result.",
90
+ "Burst %d: computed keep range is empty (start=%d > end=%d)",
108
91
  i, keep_start, keep_end,
109
92
  )
110
93
  ranges.append((keep_start, keep_end))
@@ -112,30 +95,27 @@ def compute_burst_row_ranges(
112
95
  return ranges
113
96
 
114
97
 
115
- def deburst_array(data, swath_timing: SwathTiming, azimuth_time_interval_s: float, row_offset: int = 0):
98
+ def deburst_array(
99
+ data: np.ndarray,
100
+ swath_timing: SwathTiming,
101
+ azimuth_time_interval_s: float,
102
+ row_offset: int = 0,
103
+ ) -> Tuple[np.ndarray, int]:
116
104
  """
117
105
  Apply real deburst to an array: remove each burst's redundant
118
106
  overlap rows and concatenate the kept, non-overlapping portions
119
- into one continuous array — matching SNAP's real behaviour (the
120
- image genuinely shrinks; this is not a masking operation).
107
+ into one continuous array.
121
108
 
122
109
  Args:
123
- data: 2D array (complex SLC, real-valued raster,
124
- whatever a caller needs debursted), full burst
125
- stack — n_bursts * lines_per_burst rows (or the
126
- real full-scene extent if row_offset is used).
127
- swath_timing: Real burst metadata from annotation.parse_burst_info().
110
+ data: 2D array (complex SLC, real-valued raster, etc.)
111
+ swath_timing: Real burst metadata from annotation.parse_burst_info().
128
112
  azimuth_time_interval_s: Real per-line time spacing.
129
- row_offset: If `data` is an already-cropped extract, the real
130
- row offset of `data`'s row 0 within the full-scene
131
- burst stack. Default 0 assumes `data` already starts
132
- at the full-scene burst stack's row 0.
113
+ row_offset: If `data` is an already-cropped extract, the real
114
+ row offset of `data`'s row 0 within the full-scene burst stack.
133
115
 
134
116
  Returns:
135
117
  (debursted_data, first_kept_full_scene_row)
136
118
  """
137
- import numpy as np
138
-
139
119
  if not isinstance(swath_timing, SwathTiming):
140
120
  raise TypeError(
141
121
  f"deburst_array expects a SwathTiming object, got "
@@ -176,8 +156,10 @@ def deburst_array(data, swath_timing: SwathTiming, azimuth_time_interval_s: floa
176
156
  )
177
157
 
178
158
  debursted = np.concatenate(chunks, axis=0)
159
+
179
160
  logger.info(
180
161
  "Deburst: %d bursts -> %d output rows (input was %d rows)",
181
162
  len(swath_timing.bursts), debursted.shape[0], data.shape[0],
182
163
  )
183
- return debursted, first_kept_full_scene_row
164
+
165
+ return debursted, first_kept_full_scene_row