PyGeoFetch 1.7.0__tar.gz → 1.9.0__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 (116) hide show
  1. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/CHANGELOG.md +2 -2
  2. {pygeofetch-1.7.0/pygeofetch.egg-info → pygeofetch-1.9.0}/PKG-INFO +72 -3
  3. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/README.md +69 -0
  4. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/__init__.py +1 -1
  5. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/insar/__init__.py +46 -1
  6. pygeofetch-1.9.0/pygeofetch/insar/annotation.py +153 -0
  7. pygeofetch-1.9.0/pygeofetch/insar/coregister.py +339 -0
  8. pygeofetch-1.9.0/pygeofetch/insar/geolocation.py +478 -0
  9. pygeofetch-1.9.0/pygeofetch/insar/gpu.py +106 -0
  10. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/insar/interferogram.py +174 -19
  11. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/insar/timeseries.py +57 -5
  12. pygeofetch-1.9.0/pygeofetch/insar/validate.py +251 -0
  13. pygeofetch-1.9.0/pygeofetch/insar/visualize.py +226 -0
  14. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/meta.yaml +1 -1
  15. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/viz/plot.py +381 -1
  16. {pygeofetch-1.7.0 → pygeofetch-1.9.0/pygeofetch.egg-info}/PKG-INFO +72 -3
  17. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch.egg-info/SOURCES.txt +6 -0
  18. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch.egg-info/requires.txt +2 -2
  19. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pyproject.toml +3 -3
  20. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/LICENSE +0 -0
  21. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/MANIFEST.in +0 -0
  22. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/cli/__init__.py +0 -0
  23. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/cli/auth_commands.py +0 -0
  24. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/cli/config_commands.py +0 -0
  25. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/cli/download_commands.py +0 -0
  26. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/cli/index_commands.py +0 -0
  27. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/cli/main.py +0 -0
  28. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/cli/pipeline_process_commands.py +0 -0
  29. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/cli/postprocess_commands.py +0 -0
  30. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/cli/preprocess_commands.py +0 -0
  31. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/cli/sar_commands.py +0 -0
  32. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/cli/search_commands.py +0 -0
  33. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/config/__init__.py +0 -0
  34. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/config/defaults.yaml +0 -0
  35. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/config/settings.py +0 -0
  36. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/core/__init__.py +0 -0
  37. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/core/authenticator.py +0 -0
  38. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/core/cache_manager.py +0 -0
  39. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/core/downloader.py +0 -0
  40. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/core/engine.py +0 -0
  41. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/core/logging.py +0 -0
  42. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/core/orbits.py +0 -0
  43. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/core/scheduler.py +0 -0
  44. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/core/searcher.py +0 -0
  45. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/insar/atmosphere.py +0 -0
  46. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/insar/extraction.py +0 -0
  47. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/insar/unwrap.py +0 -0
  48. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/models/__init__.py +0 -0
  49. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/models/download_task.py +0 -0
  50. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/models/satellite_data.py +0 -0
  51. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/models/search_query.py +0 -0
  52. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/models/user_auth.py +0 -0
  53. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/processing/__init__.py +0 -0
  54. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/processing/base.py +0 -0
  55. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/processing/batch.py +0 -0
  56. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/processing/indices.py +0 -0
  57. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/processing/pipeline.py +0 -0
  58. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/processing/postprocessor.py +0 -0
  59. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/processing/preprocessor.py +0 -0
  60. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/processing/sar.py +0 -0
  61. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/processor/__init__.py +0 -0
  62. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/processor/indices.py +0 -0
  63. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/processor/landsat.py +0 -0
  64. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/processor/loader.py +0 -0
  65. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/processor/stacker.py +0 -0
  66. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/processor/timeseries.py +0 -0
  67. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/__init__.py +0 -0
  68. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/airbus_oneatlas.py +0 -0
  69. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/alaska_satellite_facility.py +0 -0
  70. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/aws_earth.py +0 -0
  71. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/base.py +0 -0
  72. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/copernicus.py +0 -0
  73. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/digitalglobe.py +0 -0
  74. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/earth_explorer_additional.py +0 -0
  75. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/element84.py +0 -0
  76. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/eodag_provider.py +0 -0
  77. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/esa_scihub.py +0 -0
  78. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/geoserver_generic.py +0 -0
  79. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/google_earth_engine.py +0 -0
  80. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/inpe_cbers.py +0 -0
  81. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/isro_bhuvan.py +0 -0
  82. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/jaxa_earth.py +0 -0
  83. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/maxar_gbdx.py +0 -0
  84. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/nasa_earthdata.py +0 -0
  85. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/nasa_earthdata_cloud.py +0 -0
  86. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/noaa_big_data.py +0 -0
  87. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/opentopography.py +0 -0
  88. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/planet.py +0 -0
  89. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/planetary_computer.py +0 -0
  90. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/sentinel_hub.py +0 -0
  91. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/terrabotics.py +0 -0
  92. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/providers/usgs.py +0 -0
  93. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/sar/__init__.py +0 -0
  94. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/sar/_native.py +0 -0
  95. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/sar/_ost.py +0 -0
  96. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/sar/_sarxarray.py +0 -0
  97. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/sar/extraction.py +0 -0
  98. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/sar/processor.py +0 -0
  99. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/utils/__init__.py +0 -0
  100. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/utils/file_utils.py +0 -0
  101. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/utils/gcp_recovery.py +0 -0
  102. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/utils/geo_utils.py +0 -0
  103. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/utils/logging_setup.py +0 -0
  104. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/utils/retry_handler.py +0 -0
  105. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/utils/validators.py +0 -0
  106. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/viz/__init__.py +0 -0
  107. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch/viz/map.py +0 -0
  108. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch.egg-info/dependency_links.txt +0 -0
  109. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch.egg-info/entry_points.txt +0 -0
  110. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch.egg-info/not-zip-safe +0 -0
  111. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/pygeofetch.egg-info/top_level.txt +0 -0
  112. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/setup.cfg +0 -0
  113. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/tests/test_extraction.py +0 -0
  114. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/tests/test_insar.py +0 -0
  115. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/tests/test_pgv_integration_contract.py +0 -0
  116. {pygeofetch-1.7.0 → pygeofetch-1.9.0}/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/1.7.0/).
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.9.0/).
5
5
 
6
- ## [1.7.0] — 2026-07-12
6
+ ## [1.9.0] — 2026-07-12
7
7
 
8
8
  ### Added
9
9
  - Federated search across 22+ satellite data providers
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygeofetch
3
- Version: 1.7.0
3
+ Version: 1.9.0
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
@@ -94,8 +94,8 @@ Requires-Dist: plotly>=5.14.0; extra == "viz"
94
94
  Requires-Dist: whitebox>=2.3.0; extra == "viz"
95
95
  Requires-Dist: whiteboxgui>=2.3.0; extra == "viz"
96
96
  Requires-Dist: geemap>=0.28.0; extra == "viz"
97
- Requires-Dist: osmnx>=1.7.0; extra == "viz"
98
- Requires-Dist: contextily>=1.7.0; extra == "viz"
97
+ Requires-Dist: osmnx>=1.9.0; extra == "viz"
98
+ Requires-Dist: contextily>=1.9.0; extra == "viz"
99
99
  Requires-Dist: xyzservices>=2023.10.0; extra == "viz"
100
100
  Requires-Dist: mapclassify>=2.5.0; extra == "viz"
101
101
  Requires-Dist: geoplot>=0.5.0; extra == "viz"
@@ -467,6 +467,70 @@ pygeofetch proc-pipeline template change_detection
467
467
 
468
468
  ---
469
469
 
470
+
471
+ ### 🌐 InSAR Processing (`pygeofetch.insar`)
472
+
473
+ A full Interferometric SAR chain for Sentinel-1 — search through SBAS time series inversion, pure Python, no external InSAR software required beyond `snaphu-py`'s bundled unwrapper.
474
+
475
+ ```bash
476
+ pip install "pygeofetch[insar]" # native SBAS inversion
477
+ pip install "pygeofetch[insar-full]" # + MintPy passthrough for advanced corrections
478
+ ```
479
+
480
+ | Component | Description |
481
+ |---|---|
482
+ | `InterferogramGenerator` | Coregistration (real orbit-based, or shape-based fallback) + Enhanced Spectral Diversity (ESD) refinement for TOPS burst continuity + topographic phase removal |
483
+ | `PhaseUnwrapper` | SNAPHU (Chen & Zebker 2001) via the official `snaphu-py` bindings — the same algorithm used by ASF, ISCE2/3, GAMMA, and SNAP |
484
+ | `AtmosphericCorrector` | Elevation-correlated regression or ERA5 reanalysis-based tropospheric delay correction |
485
+ | `SBASTimeSeries` | Small BAseline Subset displacement/velocity inversion (Berardino et al. 2002) |
486
+ | `DataValidator` | SLC, coherence, and SBAS-network sanity checks, wired in at every real pipeline entry point |
487
+
488
+ Real orbit-based coregistration computes genuine per-pixel offsets from actual satellite orbit state vectors and acquisition timing (not a shape-matching guess) — supply a DEM, both SAFE archives, and both `.EOF` orbit files, and it's used automatically; otherwise falls back cleanly to shape-based resampling.
489
+
490
+ Every stage supports automatic visualization (`auto_visualize=True`) and optional GPU acceleration (`use_gpu=True`, CuPy) for coherence estimation and SBAS inversion on large scenes.
491
+
492
+ See [`pygeofetch/insar/README.md`](pygeofetch/insar/README.md) for the full processing chain, verification methodology, and current limitations.
493
+
494
+
495
+ ```python
496
+ # InSAR — search to SBAS time series
497
+ from pygeofetch.insar import (
498
+ InterferogramGenerator, PhaseUnwrapper, AtmosphericCorrector,
499
+ SBASTimeSeries, DataValidator,
500
+ )
501
+ from pygeofetch.insar.timeseries import InterferogramPair
502
+ from pygeofetch.core.orbits import fetch_orbit_file
503
+
504
+ # Coregistration + interferogram formation (real orbit-based when a DEM,
505
+ # both SAFE archives, and both orbit files are supplied; falls back to
506
+ # shape-based resampling otherwise)
507
+ gen = InterferogramGenerator(coherence_window=5, esd_enabled=True, use_gpu=False)
508
+ result = gen.process_pair(
509
+ reference="slc_ref.tif", secondary="slc_sec.tif", dem="dem.tif",
510
+ reference_safe_zip="ref.SAFE.zip", secondary_safe_zip="sec.SAFE.zip",
511
+ reference_orbit_file=fetch_orbit_file("S1A_..._ref"),
512
+ secondary_orbit_file=fetch_orbit_file("S1A_..._sec"),
513
+ )
514
+ result.save("./output", auto_visualize=True)
515
+
516
+ # Atmospheric correction + phase unwrapping
517
+ atm = AtmosphericCorrector(method="elevation")
518
+ corrected_phase = atm.correct(result.interferogram, dem="dem.tif")
519
+
520
+ unwrapper = PhaseUnwrapper(cost_mode="defo", init_method="mcf")
521
+ unwrapped, conncomp = unwrapper.unwrap(corrected_phase, result.coherence)
522
+
523
+ # SBAS time series inversion across a network of interferograms
524
+ pairs = [
525
+ InterferogramPair("2026-01-01", "2026-01-13", unwrapped, result.coherence),
526
+ # ... additional pairs forming a connected network
527
+ ]
528
+ sbas = SBASTimeSeries(wavelength_m=0.05546576, reference_date="2026-01-01")
529
+ ts_result = sbas.invert(pairs, reference_pixel=(10, 15)) # known-stable pixel
530
+ ts_result.save("./timeseries", auto_visualize=True)
531
+ print(f"Mean velocity: {ts_result.velocity.mean()*1000:.1f} mm/year")
532
+ ```
533
+
470
534
  ## 🖥️ Complete CLI Reference
471
535
 
472
536
  ```
@@ -653,3 +717,8 @@ pytest tests/unit/ -v
653
717
  pygeofetch is free and open source software, licensed under the [MIT License](LICENSE).
654
718
 
655
719
  © 2026 Samuel Appiah Kubi. Part of the **PyGeoVision** platform — [pygeofetch](https://github.com/appiahkubis14/pygeofetch) (data + processing) complete Earth observation pipeline.
720
+
721
+
722
+
723
+
724
+
@@ -331,6 +331,70 @@ pygeofetch proc-pipeline template change_detection
331
331
 
332
332
  ---
333
333
 
334
+
335
+ ### 🌐 InSAR Processing (`pygeofetch.insar`)
336
+
337
+ A full Interferometric SAR chain for Sentinel-1 — search through SBAS time series inversion, pure Python, no external InSAR software required beyond `snaphu-py`'s bundled unwrapper.
338
+
339
+ ```bash
340
+ pip install "pygeofetch[insar]" # native SBAS inversion
341
+ pip install "pygeofetch[insar-full]" # + MintPy passthrough for advanced corrections
342
+ ```
343
+
344
+ | Component | Description |
345
+ |---|---|
346
+ | `InterferogramGenerator` | Coregistration (real orbit-based, or shape-based fallback) + Enhanced Spectral Diversity (ESD) refinement for TOPS burst continuity + topographic phase removal |
347
+ | `PhaseUnwrapper` | SNAPHU (Chen & Zebker 2001) via the official `snaphu-py` bindings — the same algorithm used by ASF, ISCE2/3, GAMMA, and SNAP |
348
+ | `AtmosphericCorrector` | Elevation-correlated regression or ERA5 reanalysis-based tropospheric delay correction |
349
+ | `SBASTimeSeries` | Small BAseline Subset displacement/velocity inversion (Berardino et al. 2002) |
350
+ | `DataValidator` | SLC, coherence, and SBAS-network sanity checks, wired in at every real pipeline entry point |
351
+
352
+ Real orbit-based coregistration computes genuine per-pixel offsets from actual satellite orbit state vectors and acquisition timing (not a shape-matching guess) — supply a DEM, both SAFE archives, and both `.EOF` orbit files, and it's used automatically; otherwise falls back cleanly to shape-based resampling.
353
+
354
+ Every stage supports automatic visualization (`auto_visualize=True`) and optional GPU acceleration (`use_gpu=True`, CuPy) for coherence estimation and SBAS inversion on large scenes.
355
+
356
+ See [`pygeofetch/insar/README.md`](pygeofetch/insar/README.md) for the full processing chain, verification methodology, and current limitations.
357
+
358
+
359
+ ```python
360
+ # InSAR — search to SBAS time series
361
+ from pygeofetch.insar import (
362
+ InterferogramGenerator, PhaseUnwrapper, AtmosphericCorrector,
363
+ SBASTimeSeries, DataValidator,
364
+ )
365
+ from pygeofetch.insar.timeseries import InterferogramPair
366
+ from pygeofetch.core.orbits import fetch_orbit_file
367
+
368
+ # Coregistration + interferogram formation (real orbit-based when a DEM,
369
+ # both SAFE archives, and both orbit files are supplied; falls back to
370
+ # shape-based resampling otherwise)
371
+ gen = InterferogramGenerator(coherence_window=5, esd_enabled=True, use_gpu=False)
372
+ result = gen.process_pair(
373
+ reference="slc_ref.tif", secondary="slc_sec.tif", dem="dem.tif",
374
+ reference_safe_zip="ref.SAFE.zip", secondary_safe_zip="sec.SAFE.zip",
375
+ reference_orbit_file=fetch_orbit_file("S1A_..._ref"),
376
+ secondary_orbit_file=fetch_orbit_file("S1A_..._sec"),
377
+ )
378
+ result.save("./output", auto_visualize=True)
379
+
380
+ # Atmospheric correction + phase unwrapping
381
+ atm = AtmosphericCorrector(method="elevation")
382
+ corrected_phase = atm.correct(result.interferogram, dem="dem.tif")
383
+
384
+ unwrapper = PhaseUnwrapper(cost_mode="defo", init_method="mcf")
385
+ unwrapped, conncomp = unwrapper.unwrap(corrected_phase, result.coherence)
386
+
387
+ # SBAS time series inversion across a network of interferograms
388
+ pairs = [
389
+ InterferogramPair("2026-01-01", "2026-01-13", unwrapped, result.coherence),
390
+ # ... additional pairs forming a connected network
391
+ ]
392
+ sbas = SBASTimeSeries(wavelength_m=0.05546576, reference_date="2026-01-01")
393
+ ts_result = sbas.invert(pairs, reference_pixel=(10, 15)) # known-stable pixel
394
+ ts_result.save("./timeseries", auto_visualize=True)
395
+ print(f"Mean velocity: {ts_result.velocity.mean()*1000:.1f} mm/year")
396
+ ```
397
+
334
398
  ## 🖥️ Complete CLI Reference
335
399
 
336
400
  ```
@@ -517,3 +581,8 @@ pytest tests/unit/ -v
517
581
  pygeofetch is free and open source software, licensed under the [MIT License](LICENSE).
518
582
 
519
583
  © 2026 Samuel Appiah Kubi. Part of the **PyGeoVision** platform — [pygeofetch](https://github.com/appiahkubis14/pygeofetch) (data + processing) complete Earth observation pipeline.
584
+
585
+
586
+
587
+
588
+
@@ -30,7 +30,7 @@ CLI::
30
30
  pygeofetch doctor
31
31
  """
32
32
 
33
- __version__ = "1.7.0"
33
+ __version__ = "1.9.0"
34
34
  __author__ = "PyGeoFetch Contributors"
35
35
  __license__ = "MIT"
36
36
 
@@ -52,9 +52,39 @@ Usage::
52
52
 
53
53
  from pygeofetch.insar.atmosphere import AtmosphericCorrector
54
54
  from pygeofetch.insar.extraction import SLCExtractor
55
+ from pygeofetch.insar.gpu import gpu_available
55
56
  from pygeofetch.insar.interferogram import InterferogramGenerator, InterferogramResult
56
57
  from pygeofetch.insar.timeseries import SBASTimeSeries
57
58
  from pygeofetch.insar.unwrap import PhaseUnwrapper
59
+ from pygeofetch.insar.validate import DataValidator, ValidationResult
60
+ from pygeofetch.insar.visualize import (
61
+ visualize_interferogram,
62
+ visualize_timeseries,
63
+ visualize_unwrapped,
64
+ )
65
+
66
+ # Real, orbit-based coregistration components. All exported here are
67
+ # individually verified against known ground truth (see each module's
68
+ # docstring for specifics): annotation.py against ESA's own field-path
69
+ # spec; orbit parsing/interpolation against exact reference values;
70
+ # find_zero_doppler_time against known times from starting guesses up
71
+ # to 30 seconds off; geodetic_to_ecef via round-trip verification.
72
+ # solve_ground_point (in geolocation.py) is deliberately NOT exported
73
+ # here — it has a known, unresolved reliability gap and is not
74
+ # recommended as a primary coregistration path; use
75
+ # compute_offset_field_from_dem() instead, which avoids it entirely.
76
+ from pygeofetch.insar.annotation import SLCGeometry, parse_slc_geometry
77
+ from pygeofetch.insar.geolocation import (
78
+ geodetic_to_ecef,
79
+ find_zero_doppler_time,
80
+ parse_orbit_file,
81
+ interpolate_orbit_state,
82
+ )
83
+ from pygeofetch.insar.coregister import (
84
+ compute_offset_field_from_dem,
85
+ fit_offset_polynomial,
86
+ resample_with_offset_field,
87
+ )
58
88
 
59
89
  __all__ = [
60
90
  "InterferogramGenerator",
@@ -63,4 +93,19 @@ __all__ = [
63
93
  "SBASTimeSeries",
64
94
  "AtmosphericCorrector",
65
95
  "SLCExtractor",
66
- ]
96
+ "DataValidator",
97
+ "ValidationResult",
98
+ "gpu_available",
99
+ "visualize_interferogram",
100
+ "visualize_unwrapped",
101
+ "visualize_timeseries",
102
+ "SLCGeometry",
103
+ "parse_slc_geometry",
104
+ "geodetic_to_ecef",
105
+ "find_zero_doppler_time",
106
+ "parse_orbit_file",
107
+ "interpolate_orbit_state",
108
+ "compute_offset_field_from_dem",
109
+ "fit_offset_polynomial",
110
+ "resample_with_offset_field",
111
+ ]
@@ -0,0 +1,153 @@
1
+ """
2
+ Sentinel-1 annotation XML parsing — real per-pixel acquisition timing.
3
+
4
+ This is the missing link between "a pixel at (row, col) in an extracted
5
+ SLC GeoTIFF" and "the exact acquisition time and slant range needed to
6
+ feed geolocation.py's orbit-based solver" — that information does not
7
+ exist in the GeoTIFF itself (a general raster format); it lives in a
8
+ separate XML file inside the .SAFE archive.
9
+
10
+ Field paths confirmed against ESA's own Mission Performance Centre
11
+ documentation (Thermal Denoising of Products Generated by the S-1 IPF,
12
+ MPC-0392):
13
+
14
+ /product/imageAnnotation/imageInformation/productFirstLineUtcTime
15
+ /product/imageAnnotation/imageInformation/azimuthTimeInterval
16
+ /product/imageAnnotation/imageInformation/slantRangeTime
17
+ /product/generalAnnotation/productInformation/rangeSamplingRate
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import logging
23
+ import zipfile
24
+ from dataclasses import dataclass
25
+ from datetime import datetime, timedelta
26
+ from pathlib import Path
27
+ from typing import Optional, Union
28
+
29
+ logger = logging.getLogger("pygeofetch.insar.annotation")
30
+
31
+ SPEED_OF_LIGHT = 299792458.0
32
+
33
+
34
+ @dataclass
35
+ class SLCGeometry:
36
+ """
37
+ Real per-pixel acquisition geometry for one Sentinel-1 SLC measurement,
38
+ parsed from its annotation XML — everything needed to convert a pixel
39
+ (row, col) into an exact acquisition time and slant range.
40
+ """
41
+
42
+ first_line_time: datetime
43
+ azimuth_time_interval_s: float
44
+ near_range_time_s: float
45
+ range_sampling_rate_hz: float
46
+ n_lines: int
47
+ n_columns: int
48
+
49
+ def azimuth_time(self, row: float) -> datetime:
50
+ """Exact acquisition time of a given (possibly fractional) row."""
51
+ return self.first_line_time + timedelta(
52
+ seconds=row * self.azimuth_time_interval_s
53
+ )
54
+
55
+ def range_time(self, col: float) -> float:
56
+ """Two-way slant range time (seconds) of a given (possibly
57
+ fractional) column."""
58
+ return self.near_range_time_s + col / self.range_sampling_rate_hz
59
+
60
+ def slant_range_m(self, col: float) -> float:
61
+ """One-way slant range (metres) of a given column."""
62
+ return self.range_time(col) * SPEED_OF_LIGHT / 2.0
63
+
64
+ def row_for_azimuth_time(self, t: datetime) -> float:
65
+ """Inverse of azimuth_time(): which (fractional) row corresponds
66
+ to a given acquisition time."""
67
+ return (t - self.first_line_time).total_seconds() / self.azimuth_time_interval_s
68
+
69
+ def col_for_range_time(self, range_time_s: float) -> float:
70
+ """Inverse of range_time(): which (fractional) column corresponds
71
+ to a given two-way range time."""
72
+ return (range_time_s - self.near_range_time_s) * self.range_sampling_rate_hz
73
+
74
+
75
+ def parse_slc_geometry(
76
+ safe_zip_path: Union[str, Path], member_hint: Optional[str] = None
77
+ ) -> SLCGeometry:
78
+ """
79
+ Parse real per-pixel acquisition geometry from a Sentinel-1 SAFE
80
+ archive's annotation XML.
81
+
82
+ Args:
83
+ safe_zip_path: Path to the downloaded .SAFE.zip archive.
84
+ member_hint: Optional substring to disambiguate which
85
+ annotation file to use when multiple sub-swaths/
86
+ polarisations are present (e.g. "iw2-vv" or the
87
+ measurement filename this geometry corresponds
88
+ to). If None, uses the first annotation XML found.
89
+
90
+ Returns:
91
+ A real SLCGeometry with the exact timing needed to feed
92
+ geolocation.py's solve_ground_point().
93
+
94
+ Raises:
95
+ ValueError if no annotation XML is found, or a required field
96
+ is missing — surfaced clearly rather than returning a
97
+ geometry built from guessed/default values.
98
+ """
99
+ import xml.etree.ElementTree as ET
100
+
101
+ with zipfile.ZipFile(safe_zip_path) as zf:
102
+ candidates = [
103
+ n for n in zf.namelist()
104
+ if "/annotation/" in n and n.lower().endswith(".xml")
105
+ and "/calibration/" not in n.lower()
106
+ ]
107
+ if member_hint:
108
+ filtered = [n for n in candidates if member_hint.lower() in n.lower()]
109
+ if filtered:
110
+ candidates = filtered
111
+
112
+ if not candidates:
113
+ raise ValueError(
114
+ f"{safe_zip_path}: no annotation XML found — this doesn't "
115
+ f"look like a real Sentinel-1 SAFE archive, or the "
116
+ f"expected internal structure has changed."
117
+ )
118
+
119
+ with zf.open(candidates[0]) as f:
120
+ root = ET.parse(f).getroot()
121
+
122
+ def get_text(path: str) -> str:
123
+ elem = root.find(path)
124
+ if elem is None or elem.text is None:
125
+ raise ValueError(
126
+ f"{safe_zip_path}: required annotation field missing: {path}"
127
+ )
128
+ return elem.text
129
+
130
+ first_line_str = get_text(".//imageAnnotation/imageInformation/productFirstLineUtcTime")
131
+ first_line_time = datetime.fromisoformat(first_line_str)
132
+
133
+ geometry = SLCGeometry(
134
+ first_line_time=first_line_time,
135
+ azimuth_time_interval_s=float(
136
+ get_text(".//imageAnnotation/imageInformation/azimuthTimeInterval")
137
+ ),
138
+ near_range_time_s=float(
139
+ get_text(".//imageAnnotation/imageInformation/slantRangeTime")
140
+ ),
141
+ range_sampling_rate_hz=float(
142
+ get_text(".//generalAnnotation/productInformation/rangeSamplingRate")
143
+ ),
144
+ n_lines=int(get_text(".//imageAnnotation/imageInformation/numberOfLines")),
145
+ n_columns=int(get_text(".//imageAnnotation/imageInformation/numberOfColumns")),
146
+ )
147
+
148
+ logger.info(
149
+ "Parsed real SLC geometry from %s: %d x %d, starting %s",
150
+ Path(safe_zip_path).name, geometry.n_lines, geometry.n_columns,
151
+ geometry.first_line_time,
152
+ )
153
+ return geometry