PyGeoFetch 2.6.2.3__tar.gz → 2.6.2.5__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.
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/CHANGELOG.md +7 -7
- {pygeofetch-2.6.2.3/pygeofetch.egg-info → pygeofetch-2.6.2.5}/PKG-INFO +5 -10
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/README.md +1 -4
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/__init__.py +1 -1
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/cli/main.py +4 -0
- pygeofetch-2.6.2.5/pygeofetch/cli/multisensor_commands.py +243 -0
- pygeofetch-2.6.2.5/pygeofetch/cli/optical_commands.py +105 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/cli/search_commands.py +121 -8
- pygeofetch-2.6.2.5/pygeofetch/core/data_organizer.py +364 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/core/engine.py +8 -4
- pygeofetch-2.6.2.5/pygeofetch/insar/advanced_safeguards.py +370 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/preflight.py +136 -1
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/meta.yaml +1 -1
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/models/satellite_data.py +49 -0
- pygeofetch-2.6.2.5/pygeofetch/multisensor/dem_differencing.py +201 -0
- pygeofetch-2.6.2.5/pygeofetch/multisensor/flood_mapping.py +215 -0
- pygeofetch-2.6.2.5/pygeofetch/multisensor/insar_optical_fusion.py +269 -0
- pygeofetch-2.6.2.5/pygeofetch/multisensor/multisensor_init.py +55 -0
- pygeofetch-2.6.2.5/pygeofetch/multisensor/terrain_corrected_change.py +285 -0
- pygeofetch-2.6.2.5/pygeofetch/multisensor/vegetation_disturbance.py +220 -0
- pygeofetch-2.6.2.5/pygeofetch/optical/__init__.py +35 -0
- pygeofetch-2.6.2.5/pygeofetch/optical/offset_tracking.py +789 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/processor/indices.py +143 -11
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/__init__.py +2 -4
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/airbus_oneatlas.py +1 -1
- pygeofetch-2.6.2.5/pygeofetch/providers/alaska_satellite_facility.py +361 -0
- pygeofetch-2.6.2.5/pygeofetch/providers/digitalglobe.py +423 -0
- pygeofetch-2.6.2.5/pygeofetch/providers/earth_explorer_additional.py +104 -0
- pygeofetch-2.6.2.5/pygeofetch/providers/geoserver_generic.py +377 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/inpe_cbers.py +114 -96
- pygeofetch-2.6.2.5/pygeofetch/providers/isro_bhuvan.py +357 -0
- pygeofetch-2.6.2.5/pygeofetch/providers/jaxa_earth.py +276 -0
- pygeofetch-2.6.2.5/pygeofetch/providers/maxar_gbdx.py +298 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/nasa_earthdata.py +26 -6
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/nasa_earthdata_cloud.py +114 -38
- pygeofetch-2.6.2.5/pygeofetch/providers/opentopography.py +435 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/planet.py +23 -2
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/sentinel_hub.py +50 -25
- pygeofetch-2.6.2.5/pygeofetch/sar/__init__.py +49 -0
- pygeofetch-2.6.2.5/pygeofetch/sar/math.py +100 -0
- pygeofetch-2.6.2.5/pygeofetch/sar/pipelines.py +573 -0
- pygeofetch-2.6.2.5/pygeofetch/sar/processor.py +419 -0
- pygeofetch-2.6.2.5/pygeofetch/utils/sar_math.py +100 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/validation/optical_validator.py +69 -33
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/viz/plot.py +24 -5
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5/pygeofetch.egg-info}/PKG-INFO +5 -10
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch.egg-info/SOURCES.txt +43 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch.egg-info/requires.txt +2 -5
- pygeofetch-2.6.2.5/pygeofetch.egg-info/top_level.txt +2 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pyproject.toml +16 -16
- pygeofetch-2.6.2.5/tests/test_advanced_safeguards.py +167 -0
- pygeofetch-2.6.2.5/tests/test_alaska_satellite_facility.py +217 -0
- pygeofetch-2.6.2.5/tests/test_build_sbas_network.py +191 -0
- pygeofetch-2.6.2.5/tests/test_coregister_upgrades.py +295 -0
- pygeofetch-2.6.2.5/tests/test_data_organizer.py +170 -0
- pygeofetch-2.6.2.5/tests/test_dem_differencing_pipeline.py +168 -0
- pygeofetch-2.6.2.5/tests/test_digitalglobe.py +260 -0
- pygeofetch-2.6.2.5/tests/test_earth_explorer_additional.py +94 -0
- pygeofetch-2.6.2.5/tests/test_flood_mapping_pipeline.py +229 -0
- pygeofetch-2.6.2.5/tests/test_geology_indices_and_rvi_fix.py +159 -0
- pygeofetch-2.6.2.5/tests/test_geoserver_generic.py +207 -0
- pygeofetch-2.6.2.5/tests/test_inpe_cbers.py +170 -0
- pygeofetch-2.6.2.5/tests/test_insar_optical_fusion_pipeline.py +183 -0
- pygeofetch-2.6.2.5/tests/test_isro_bhuvan.py +227 -0
- pygeofetch-2.6.2.5/tests/test_jaxa_earth.py +173 -0
- pygeofetch-2.6.2.5/tests/test_nasa_earthdata_cloud_s3_fix.py +173 -0
- pygeofetch-2.6.2.5/tests/test_nasa_earthdata_public_search.py +81 -0
- pygeofetch-2.6.2.5/tests/test_opentopography_pointcloud_usgs.py +233 -0
- pygeofetch-2.6.2.5/tests/test_optical_offset_tracking.py +183 -0
- pygeofetch-2.6.2.5/tests/test_optical_validator_stac.py +223 -0
- pygeofetch-2.6.2.5/tests/test_planet_asset_type_fix.py +109 -0
- pygeofetch-2.6.2.5/tests/test_preflight_spatial_baseline.py +152 -0
- pygeofetch-2.6.2.5/tests/test_provider_geometry_audit.py +186 -0
- pygeofetch-2.6.2.5/tests/test_sar_pipelines.py +317 -0
- pygeofetch-2.6.2.5/tests/test_sentinel_hub_catalog_fix.py +124 -0
- pygeofetch-2.6.2.5/tests/test_strain_and_fusion.py +252 -0
- pygeofetch-2.6.2.5/tests/test_terrain_corrected_change_pipeline.py +210 -0
- pygeofetch-2.6.2.5/tests/test_vegetation_disturbance_pipeline.py +242 -0
- pygeofetch-2.6.2.3/pygeofetch/providers/alaska_satellite_facility.py +0 -256
- pygeofetch-2.6.2.3/pygeofetch/providers/digitalglobe.py +0 -254
- pygeofetch-2.6.2.3/pygeofetch/providers/earth_explorer_additional.py +0 -254
- pygeofetch-2.6.2.3/pygeofetch/providers/geoserver_generic.py +0 -256
- pygeofetch-2.6.2.3/pygeofetch/providers/isro_bhuvan.py +0 -254
- pygeofetch-2.6.2.3/pygeofetch/providers/jaxa_earth.py +0 -256
- pygeofetch-2.6.2.3/pygeofetch/providers/maxar_gbdx.py +0 -266
- pygeofetch-2.6.2.3/pygeofetch/providers/opentopography.py +0 -239
- pygeofetch-2.6.2.3/pygeofetch/sar/__init__.py +0 -22
- pygeofetch-2.6.2.3/pygeofetch/sar/processor.py +0 -145
- pygeofetch-2.6.2.3/pygeofetch.egg-info/top_level.txt +0 -1
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/LICENSE +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/MANIFEST.in +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/cli/__init__.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/cli/auth_commands.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/cli/config_commands.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/cli/download_commands.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/cli/index_commands.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/cli/monitor_commands.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/cli/pipeline_process_commands.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/cli/postprocess_commands.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/cli/preprocess_commands.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/cli/sar_commands.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/config/__init__.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/config/defaults.yaml +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/config/settings.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/core/__init__.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/core/authenticator.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/core/cache_manager.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/core/downloader.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/core/logging.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/core/monitor.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/core/orbits.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/core/scheduler.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/core/searcher.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/core/state.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/__init__.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/analysis.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/annotation.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/atmosphere.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/coregister.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/deburst.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/esd.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/extraction.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/flatearth.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/geolocation.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/gpu.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/interferogram.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/ionosphere.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/offset_geometry.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/offset_tracking.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/provenance.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/ps_selection.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/splitspectrum_diagnostic.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/stack_selection.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/synthetic.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/timeseries.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/unwrap.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/validate.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/visualize.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/insar/workflow.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/models/__init__.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/models/download_task.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/models/search_query.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/models/user_auth.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/processing/__init__.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/processing/base.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/processing/batch.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/processing/indices.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/processing/pipeline.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/processing/postprocessor.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/processing/preprocessor.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/processing/sar.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/processor/__init__.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/processor/landsat.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/processor/loader.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/processor/stacker.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/processor/timeseries.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/aws_earth.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/base.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/copernicus.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/copernicus_nodes.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/element84.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/eodag_provider.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/esa_scihub.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/google_earth_engine.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/noaa_big_data.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/planetary_computer.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/terrabotics.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/providers/usgs.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/sar/_native.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/sar/_ost.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/sar/_sarxarray.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/sar/extraction.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/utils/__init__.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/utils/file_utils.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/utils/gcp_recovery.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/utils/geo_utils.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/utils/logging_setup.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/utils/retry_handler.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/utils/validators.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/validation/__init__.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/viz/__init__.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch/viz/map.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch.egg-info/dependency_links.txt +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch.egg-info/entry_points.txt +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/pygeofetch.egg-info/not-zip-safe +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/setup.cfg +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_airbus_oneatlas.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_atmospheric_circular_regression.py +0 -0
- /pygeofetch-2.6.2.3/tests/test_build_sbas_network.py → /pygeofetch-2.6.2.5/tests/test_build_sbas_network-new.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_burst_metadata.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_burst_processing_integration.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_burst_sync_large_gap.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_burst_sync_prescreening.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_burst_synchronization.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_circuit_breaker_wiring.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_cli_optical_validation.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_copernicus_nodes.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_coregister_integration.py +0 -0
- /pygeofetch-2.6.2.3/tests/test_coregister_upgrades.py → /pygeofetch-2.6.2.5/tests/test_coregister_upgrades (1).py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_credential_encryption.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_era5_atmospheric_correction.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_esa_scihub.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_extraction.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_flat_earth_phase.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_gcp_reprojection.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_google_earth_engine.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_insar.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_ionosphere.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_map_preview_consolidation.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_models.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_monitor.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_noaa_big_data.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_offset_tracking.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_optical_validation_wiring.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_optical_validator.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_pgv_integration_contract.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_pipeline_process_export.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_preflight_gate.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_provider_geometry_audit (1).py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_provider_scene_disambiguation.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_providers.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_ps_selection.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_raster_collocation_integration.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_reference_pixel_and_despike.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_risk_mapping.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_screen_stack_orchestration.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_search_download.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_sign_convention.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_splitspectrum_diagnostic.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_stack_selection_consolidation.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_state.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_topo_r2_bug.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_utils.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/tests/test_viz_plot.py +0 -0
- {pygeofetch-2.6.2.3 → pygeofetch-2.6.2.5}/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.4
|
|
23
|
+
## [Unreleased] — v2.6.2.5.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.
|
|
133
|
+
## [2.6.2.5.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] — 2026-08-24
|
|
152
|
+
## [2.6.2.5] — 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.
|
|
264
|
+
**"2.6.2.5.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.
|
|
266
|
+
management, 17 spectral indices, and various bug fixes. **2.6.2.5.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.
|
|
410
|
-
2026-07-29 and 2026-07-12. **2.6.2.
|
|
409
|
+
1. **Two different entries were both labeled `2.6.2.5.4`**, dated
|
|
410
|
+
2026-07-29 and 2026-07-12. **2.6.2.5.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.
|
|
3
|
+
Version: 2.6.2.5
|
|
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
|
|
@@ -26,7 +26,7 @@ License: MIT License
|
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
|
|
28
28
|
Project-URL: Homepage, https://www.eocoreint.com/
|
|
29
|
-
Project-URL: Documentation, https://
|
|
29
|
+
Project-URL: Documentation, https://appiahkubis14.github.io/pygeofetch-docs/
|
|
30
30
|
Project-URL: Repository, https://github.com/EOCoreINT/pygeofetch
|
|
31
31
|
Project-URL: Bug Tracker, https://github.com/EOCoreINT/pygeofetch/issues
|
|
32
32
|
Keywords: satellite,remote-sensing,GIS,earth-observation,geospatial,SAR,Sentinel,Landsat
|
|
@@ -71,8 +71,6 @@ Requires-Dist: spyndex>=0.5; extra == "processor"
|
|
|
71
71
|
Requires-Dist: scipy>=1.12; extra == "processor"
|
|
72
72
|
Requires-Dist: scikit-image>=0.23; extra == "processor"
|
|
73
73
|
Requires-Dist: geopandas>=0.14; extra == "processor"
|
|
74
|
-
Provides-Extra: providers
|
|
75
|
-
Requires-Dist: eodag>=3.0; extra == "providers"
|
|
76
74
|
Provides-Extra: sar
|
|
77
75
|
Requires-Dist: sarxarray>=0.1; extra == "sar"
|
|
78
76
|
Provides-Extra: insar
|
|
@@ -130,12 +128,12 @@ Requires-Dist: mypy>=1.8; extra == "dev"
|
|
|
130
128
|
Requires-Dist: build>=1.0; extra == "dev"
|
|
131
129
|
Requires-Dist: twine>=5.0; extra == "dev"
|
|
132
130
|
Provides-Extra: test
|
|
133
|
-
Requires-Dist: pygeofetch[dev,geo,insar]; extra == "test"
|
|
131
|
+
Requires-Dist: pygeofetch[cloud,dev,geo,insar]; extra == "test"
|
|
134
132
|
Provides-Extra: insar-full
|
|
135
133
|
Requires-Dist: snaphu>=0.1; extra == "insar-full"
|
|
136
134
|
Requires-Dist: scipy>=1.12; extra == "insar-full"
|
|
137
135
|
Provides-Extra: full
|
|
138
|
-
Requires-Dist: pygeofetch[cloud,geo,insar,notebook,processor,
|
|
136
|
+
Requires-Dist: pygeofetch[cloud,geo,insar,notebook,processor,schedule,viz]; extra == "full"
|
|
139
137
|
Provides-Extra: all
|
|
140
138
|
Requires-Dist: pygeofetch[full]; extra == "all"
|
|
141
139
|
Dynamic: license-file
|
|
@@ -153,9 +151,6 @@ Dynamic: license-file
|
|
|
153
151
|
|
|
154
152
|
[Quick Start](#-quick-start) · [Mexico City Case Study](#-case-study-mapping-mexico-city-land-subsidence-with-insar) · [Case Study Repo ↗](https://github.com/EOCoreINT/mexico-subsidence-project) · [Documentation](https://pygeofetch.readthedocs.io/en/latest/) · [Notebooks](#-notebooks)
|
|
155
153
|
|
|
156
|
-
<br>
|
|
157
|
-
<img src="https://raw.githubusercontent.com/EOCoreINT/pygeofetch/refs/heads/main/icon/concept_a.png" alt="PyGeoFetch Logo" width="350">
|
|
158
|
-
<br>
|
|
159
154
|
|
|
160
155
|
</div>
|
|
161
156
|
|
|
@@ -386,7 +381,7 @@ If you use PyGeoFetch in your research or operational work, please cite it using
|
|
|
386
381
|
title = {PyGeoFetch: A Unified Python Framework for Multi-Provider Satellite Data Acquisition, Pre-download Quality Control, and Geospatial Processing},
|
|
387
382
|
year = {2026},
|
|
388
383
|
publisher = {Zenodo},
|
|
389
|
-
version = {2.6.2.
|
|
384
|
+
version = {2.6.2.5.1},
|
|
390
385
|
doi = {10.5281/zenodo.22087230},
|
|
391
386
|
url = {https://doi.org/10.5281/zenodo.22087230}
|
|
392
387
|
}
|
|
@@ -11,9 +11,6 @@
|
|
|
11
11
|
|
|
12
12
|
[Quick Start](#-quick-start) · [Mexico City Case Study](#-case-study-mapping-mexico-city-land-subsidence-with-insar) · [Case Study Repo ↗](https://github.com/EOCoreINT/mexico-subsidence-project) · [Documentation](https://pygeofetch.readthedocs.io/en/latest/) · [Notebooks](#-notebooks)
|
|
13
13
|
|
|
14
|
-
<br>
|
|
15
|
-
<img src="https://raw.githubusercontent.com/EOCoreINT/pygeofetch/refs/heads/main/icon/concept_a.png" alt="PyGeoFetch Logo" width="350">
|
|
16
|
-
<br>
|
|
17
14
|
|
|
18
15
|
</div>
|
|
19
16
|
|
|
@@ -244,7 +241,7 @@ If you use PyGeoFetch in your research or operational work, please cite it using
|
|
|
244
241
|
title = {PyGeoFetch: A Unified Python Framework for Multi-Provider Satellite Data Acquisition, Pre-download Quality Control, and Geospatial Processing},
|
|
245
242
|
year = {2026},
|
|
246
243
|
publisher = {Zenodo},
|
|
247
|
-
version = {2.6.2.
|
|
244
|
+
version = {2.6.2.5.1},
|
|
248
245
|
doi = {10.5281/zenodo.22087230},
|
|
249
246
|
url = {https://doi.org/10.5281/zenodo.22087230}
|
|
250
247
|
}
|
|
@@ -26,6 +26,8 @@ from pygeofetch.cli.auth_commands import auth
|
|
|
26
26
|
from pygeofetch.cli.config_commands import config
|
|
27
27
|
from pygeofetch.cli.download_commands import download
|
|
28
28
|
from pygeofetch.cli.index_commands import index
|
|
29
|
+
from pygeofetch.cli.multisensor_commands import multisensor
|
|
30
|
+
from pygeofetch.cli.optical_commands import optical
|
|
29
31
|
from pygeofetch.cli.pipeline_process_commands import proc_pipeline
|
|
30
32
|
from pygeofetch.cli.postprocess_commands import post
|
|
31
33
|
from pygeofetch.cli.preprocess_commands import preprocess
|
|
@@ -103,6 +105,8 @@ cli.add_command(index)
|
|
|
103
105
|
cli.add_command(post)
|
|
104
106
|
cli.add_command(sar)
|
|
105
107
|
cli.add_command(proc_pipeline)
|
|
108
|
+
cli.add_command(optical)
|
|
109
|
+
cli.add_command(multisensor)
|
|
106
110
|
|
|
107
111
|
|
|
108
112
|
# ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""CLI access to pygeofetch's 5 real multi-sensor pipelines."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group()
|
|
11
|
+
def multisensor() -> None:
|
|
12
|
+
"""Multi-sensor fusion pipelines: InSAR+optical, SAR+optical flood
|
|
13
|
+
mapping, terrain-corrected change, DEM differencing, vegetation
|
|
14
|
+
disturbance. See docs/processing/multi-sensor-pipelines.md."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _print_result(result) -> None:
|
|
18
|
+
if result.success:
|
|
19
|
+
click.echo(f"✓ Success -> {result.final_output}")
|
|
20
|
+
if result.metadata:
|
|
21
|
+
click.echo(
|
|
22
|
+
json.dumps(
|
|
23
|
+
{k: str(v) for k, v in result.metadata.items()},
|
|
24
|
+
indent=2,
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
else:
|
|
28
|
+
click.echo(f"✗ Failed: {result.error}", err=True)
|
|
29
|
+
raise SystemExit(1)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@multisensor.command("insar-optical-fusion")
|
|
33
|
+
@click.option("--insar-velocity", required=True, type=click.Path(exists=True))
|
|
34
|
+
@click.option("--insar-coherence", required=True, type=click.Path(exists=True))
|
|
35
|
+
@click.option("--optical-ref", required=True, type=click.Path(exists=True))
|
|
36
|
+
@click.option("--optical-sec", required=True, type=click.Path(exists=True))
|
|
37
|
+
@click.option("--output-dir", required=True, type=click.Path())
|
|
38
|
+
@click.option("--window-size", default=64, show_default=True, type=int)
|
|
39
|
+
@click.option("--step-size", default=16, show_default=True, type=int)
|
|
40
|
+
@click.option("--snr-threshold", default=3.0, show_default=True, type=float)
|
|
41
|
+
@click.option("--coherence-low", default=0.4, show_default=True, type=float)
|
|
42
|
+
@click.option("--coherence-high", default=0.6, show_default=True, type=float)
|
|
43
|
+
@click.option("--cloud-mask", default=None, type=click.Path(exists=True))
|
|
44
|
+
def insar_optical_fusion_cmd(
|
|
45
|
+
insar_velocity,
|
|
46
|
+
insar_coherence,
|
|
47
|
+
optical_ref,
|
|
48
|
+
optical_sec,
|
|
49
|
+
output_dir,
|
|
50
|
+
window_size,
|
|
51
|
+
step_size,
|
|
52
|
+
snr_threshold,
|
|
53
|
+
coherence_low,
|
|
54
|
+
coherence_high,
|
|
55
|
+
cloud_mask,
|
|
56
|
+
):
|
|
57
|
+
"""Fuse InSAR displacement with optical pixel offset tracking."""
|
|
58
|
+
from pygeofetch.multisensor import insar_optical_displacement_pipeline
|
|
59
|
+
|
|
60
|
+
result = insar_optical_displacement_pipeline(
|
|
61
|
+
insar_velocity,
|
|
62
|
+
insar_coherence,
|
|
63
|
+
optical_ref,
|
|
64
|
+
optical_sec,
|
|
65
|
+
output_dir,
|
|
66
|
+
cloud_mask_path=cloud_mask,
|
|
67
|
+
window_size=window_size,
|
|
68
|
+
step_size=step_size,
|
|
69
|
+
snr_threshold=snr_threshold,
|
|
70
|
+
coherence_low=coherence_low,
|
|
71
|
+
coherence_high=coherence_high,
|
|
72
|
+
)
|
|
73
|
+
_print_result(result)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@multisensor.command("flood-map")
|
|
77
|
+
@click.option("--sar", "sar_input", required=True, type=click.Path(exists=True))
|
|
78
|
+
@click.option("--optical-green", required=True, type=click.Path(exists=True))
|
|
79
|
+
@click.option("--optical-nir", required=True, type=click.Path(exists=True))
|
|
80
|
+
@click.option("--output-dir", required=True, type=click.Path())
|
|
81
|
+
@click.option("--sar-reference", default=None, type=click.Path(exists=True))
|
|
82
|
+
@click.option("--sar-threshold", default=-15.0, show_default=True, type=float)
|
|
83
|
+
@click.option("--ndwi-threshold", default=0.0, show_default=True, type=float)
|
|
84
|
+
@click.option("--cloud-mask", default=None, type=click.Path(exists=True))
|
|
85
|
+
@click.option(
|
|
86
|
+
"--fusion-mode",
|
|
87
|
+
default="cloud_aware",
|
|
88
|
+
show_default=True,
|
|
89
|
+
type=click.Choice(["cloud_aware", "union", "intersection"]),
|
|
90
|
+
)
|
|
91
|
+
def flood_map_cmd(
|
|
92
|
+
sar_input,
|
|
93
|
+
optical_green,
|
|
94
|
+
optical_nir,
|
|
95
|
+
output_dir,
|
|
96
|
+
sar_reference,
|
|
97
|
+
sar_threshold,
|
|
98
|
+
ndwi_threshold,
|
|
99
|
+
cloud_mask,
|
|
100
|
+
fusion_mode,
|
|
101
|
+
):
|
|
102
|
+
"""Fuse SAR and optical NDWI for flood/water extent mapping."""
|
|
103
|
+
from pygeofetch.multisensor import multi_sensor_flood_pipeline
|
|
104
|
+
from pygeofetch.sar import SARProcessor
|
|
105
|
+
|
|
106
|
+
result = multi_sensor_flood_pipeline(
|
|
107
|
+
SARProcessor(),
|
|
108
|
+
sar_input,
|
|
109
|
+
optical_green,
|
|
110
|
+
optical_nir,
|
|
111
|
+
output_dir,
|
|
112
|
+
sar_reference_path=sar_reference,
|
|
113
|
+
sar_threshold=sar_threshold,
|
|
114
|
+
optical_ndwi_threshold=ndwi_threshold,
|
|
115
|
+
cloud_mask_path=cloud_mask,
|
|
116
|
+
fusion_mode=fusion_mode,
|
|
117
|
+
)
|
|
118
|
+
_print_result(result)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@multisensor.command("terrain-change")
|
|
122
|
+
@click.option("--pre", "pre_path", required=True, type=click.Path(exists=True))
|
|
123
|
+
@click.option("--post", "post_path", required=True, type=click.Path(exists=True))
|
|
124
|
+
@click.option("--dem", "dem_path", required=True, type=click.Path(exists=True))
|
|
125
|
+
@click.option(
|
|
126
|
+
"--pre-datetime",
|
|
127
|
+
required=True,
|
|
128
|
+
help="Real UTC ISO datetime, e.g. 2024-01-15T10:30:00",
|
|
129
|
+
)
|
|
130
|
+
@click.option("--post-datetime", required=True, help="Real UTC ISO datetime")
|
|
131
|
+
@click.option("--lat", "latitude", required=True, type=float)
|
|
132
|
+
@click.option("--lon", "longitude", required=True, type=float)
|
|
133
|
+
@click.option("--output-dir", required=True, type=click.Path())
|
|
134
|
+
@click.option("--change-threshold", default=0.1, show_default=True, type=float)
|
|
135
|
+
def terrain_change_cmd(
|
|
136
|
+
pre_path,
|
|
137
|
+
post_path,
|
|
138
|
+
dem_path,
|
|
139
|
+
pre_datetime,
|
|
140
|
+
post_datetime,
|
|
141
|
+
latitude,
|
|
142
|
+
longitude,
|
|
143
|
+
output_dir,
|
|
144
|
+
change_threshold,
|
|
145
|
+
):
|
|
146
|
+
"""Terrain-corrected optical change detection (removes real
|
|
147
|
+
illumination artifacts using each date's own solar position)."""
|
|
148
|
+
from datetime import datetime, timezone
|
|
149
|
+
|
|
150
|
+
from pygeofetch.multisensor import terrain_corrected_change_pipeline
|
|
151
|
+
|
|
152
|
+
def _parse(dt_str):
|
|
153
|
+
dt = datetime.fromisoformat(dt_str)
|
|
154
|
+
return dt if dt.tzinfo else dt.replace(tzinfo=timezone.utc)
|
|
155
|
+
|
|
156
|
+
result = terrain_corrected_change_pipeline(
|
|
157
|
+
pre_path,
|
|
158
|
+
post_path,
|
|
159
|
+
dem_path,
|
|
160
|
+
_parse(pre_datetime),
|
|
161
|
+
_parse(post_datetime),
|
|
162
|
+
latitude,
|
|
163
|
+
longitude,
|
|
164
|
+
output_dir,
|
|
165
|
+
change_threshold=change_threshold,
|
|
166
|
+
)
|
|
167
|
+
_print_result(result)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@multisensor.command("dem-diff")
|
|
171
|
+
@click.option("--dem-old", required=True, type=click.Path(exists=True))
|
|
172
|
+
@click.option("--dem-new", required=True, type=click.Path(exists=True))
|
|
173
|
+
@click.option("--output-dir", required=True, type=click.Path())
|
|
174
|
+
@click.option(
|
|
175
|
+
"--old-rmse",
|
|
176
|
+
default=1.0,
|
|
177
|
+
show_default=True,
|
|
178
|
+
type=float,
|
|
179
|
+
help="Real vertical RMSE of the old DEM, metres.",
|
|
180
|
+
)
|
|
181
|
+
@click.option(
|
|
182
|
+
"--new-rmse",
|
|
183
|
+
default=1.0,
|
|
184
|
+
show_default=True,
|
|
185
|
+
type=float,
|
|
186
|
+
help="Real vertical RMSE of the new DEM, metres.",
|
|
187
|
+
)
|
|
188
|
+
@click.option("--confidence-t", default=1.96, show_default=True, type=float)
|
|
189
|
+
def dem_diff_cmd(dem_old, dem_new, output_dir, old_rmse, new_rmse, confidence_t):
|
|
190
|
+
"""DEM-of-Difference volumetric change analysis."""
|
|
191
|
+
from pygeofetch.multisensor import dem_differencing_pipeline
|
|
192
|
+
|
|
193
|
+
result = dem_differencing_pipeline(
|
|
194
|
+
dem_old,
|
|
195
|
+
dem_new,
|
|
196
|
+
output_dir,
|
|
197
|
+
dem_old_vertical_rmse_m=old_rmse,
|
|
198
|
+
dem_new_vertical_rmse_m=new_rmse,
|
|
199
|
+
confidence_t_value=confidence_t,
|
|
200
|
+
)
|
|
201
|
+
_print_result(result)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@multisensor.command("vegetation-disturbance")
|
|
205
|
+
@click.option("--red-pre", required=True, type=click.Path(exists=True))
|
|
206
|
+
@click.option("--nir-pre", required=True, type=click.Path(exists=True))
|
|
207
|
+
@click.option("--red-post", required=True, type=click.Path(exists=True))
|
|
208
|
+
@click.option("--nir-post", required=True, type=click.Path(exists=True))
|
|
209
|
+
@click.option("--slc-pre", required=True, type=click.Path(exists=True))
|
|
210
|
+
@click.option("--slc-post", required=True, type=click.Path(exists=True))
|
|
211
|
+
@click.option("--output-dir", required=True, type=click.Path())
|
|
212
|
+
@click.option("--ndvi-drop-threshold", default=0.15, show_default=True, type=float)
|
|
213
|
+
@click.option("--coherence-threshold", default=0.3, show_default=True, type=float)
|
|
214
|
+
@click.option("--coherence-window", default=7, show_default=True, type=int)
|
|
215
|
+
def vegetation_disturbance_cmd(
|
|
216
|
+
red_pre,
|
|
217
|
+
nir_pre,
|
|
218
|
+
red_post,
|
|
219
|
+
nir_post,
|
|
220
|
+
slc_pre,
|
|
221
|
+
slc_post,
|
|
222
|
+
output_dir,
|
|
223
|
+
ndvi_drop_threshold,
|
|
224
|
+
coherence_threshold,
|
|
225
|
+
coherence_window,
|
|
226
|
+
):
|
|
227
|
+
"""Classify visible canopy loss vs. real sub-canopy ground
|
|
228
|
+
disturbance, from optical NDVI + SAR coherence."""
|
|
229
|
+
from pygeofetch.multisensor import vegetation_disturbance_pipeline
|
|
230
|
+
|
|
231
|
+
result = vegetation_disturbance_pipeline(
|
|
232
|
+
red_pre,
|
|
233
|
+
nir_pre,
|
|
234
|
+
red_post,
|
|
235
|
+
nir_post,
|
|
236
|
+
slc_pre,
|
|
237
|
+
slc_post,
|
|
238
|
+
output_dir,
|
|
239
|
+
ndvi_drop_threshold=ndvi_drop_threshold,
|
|
240
|
+
coherence_threshold=coherence_threshold,
|
|
241
|
+
coherence_window=coherence_window,
|
|
242
|
+
)
|
|
243
|
+
_print_result(result)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""CLI commands for optical pixel offset tracking (POT)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group()
|
|
14
|
+
def optical() -> None:
|
|
15
|
+
"""Optical pixel offset tracking — measures large 2D ground
|
|
16
|
+
displacement via image correlation, complementing InSAR for
|
|
17
|
+
motion that exceeds phase InSAR's coherence limit."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@optical.command("offset-track")
|
|
21
|
+
@click.option("--ref", "reference_path", required=True, type=click.Path(exists=True), help="Reference-date raster.")
|
|
22
|
+
@click.option("--sec", "secondary_path", required=True, type=click.Path(exists=True), help="Secondary-date raster.")
|
|
23
|
+
@click.option("--output", "-o", required=True, type=click.Path(), help="Output GeoTIFF (dx/dy/snr, 3 bands).")
|
|
24
|
+
@click.option("--band", default=1, show_default=True, type=int, help="1-based band index to correlate.")
|
|
25
|
+
@click.option("--window-size", default=64, show_default=True, type=int, help="Search window size, pixels.")
|
|
26
|
+
@click.option("--step-size", default=16, show_default=True, type=int, help="Spacing between window centers, pixels.")
|
|
27
|
+
@click.option("--chip-size", default=None, type=int, help="Reference template size, pixels. Default: window-size // 2.")
|
|
28
|
+
@click.option("--snr-threshold", default=3.0, show_default=True, type=float, help="Minimum SNR to mark a window reliable.")
|
|
29
|
+
@click.option("--cloud-mask", default=None, type=click.Path(exists=True), help="Real Sentinel-2 SCL raster, already aligned to --ref.")
|
|
30
|
+
@click.option("--water-mask-threshold", default=None, type=float, help="Mask pixels where NDWI exceeds this value (needs --green-band/--nir-band).")
|
|
31
|
+
@click.option("--green-band", default=None, type=int, help="1-based green band index, for --water-mask-threshold.")
|
|
32
|
+
@click.option("--nir-band", default=None, type=int, help="1-based NIR band index, for --water-mask-threshold.")
|
|
33
|
+
def offset_track_cmd(
|
|
34
|
+
reference_path,
|
|
35
|
+
secondary_path,
|
|
36
|
+
output,
|
|
37
|
+
band,
|
|
38
|
+
window_size,
|
|
39
|
+
step_size,
|
|
40
|
+
chip_size,
|
|
41
|
+
snr_threshold,
|
|
42
|
+
cloud_mask,
|
|
43
|
+
water_mask_threshold,
|
|
44
|
+
green_band,
|
|
45
|
+
nir_band,
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
Measure 2D ground displacement between two optical scenes via
|
|
49
|
+
image correlation.
|
|
50
|
+
|
|
51
|
+
\b
|
|
52
|
+
Examples:
|
|
53
|
+
# Basic: two co-registered scenes, real cloud masking via SCL
|
|
54
|
+
pygeofetch optical offset-track \\
|
|
55
|
+
--ref before.tif --sec after.tif \\
|
|
56
|
+
--cloud-mask before_SCL.tif \\
|
|
57
|
+
--output displacement.tif
|
|
58
|
+
|
|
59
|
+
# Mining-scale displacement, larger search window
|
|
60
|
+
pygeofetch optical offset-track \\
|
|
61
|
+
--ref jan.tif --sec jun.tif \\
|
|
62
|
+
--window-size 96 --step-size 24 \\
|
|
63
|
+
--output mine_displacement.tif
|
|
64
|
+
"""
|
|
65
|
+
from pygeofetch.optical.offset_tracking import compute_pixel_offsets, prepare_optical_pair
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
ref_arr, sec_arr, ref_profile = prepare_optical_pair(
|
|
69
|
+
reference_path,
|
|
70
|
+
secondary_path,
|
|
71
|
+
band_index=band,
|
|
72
|
+
cloud_mask_path=cloud_mask,
|
|
73
|
+
water_mask_threshold=water_mask_threshold,
|
|
74
|
+
green_band_index=green_band,
|
|
75
|
+
nir_band_index=nir_band,
|
|
76
|
+
)
|
|
77
|
+
except ValueError as exc:
|
|
78
|
+
console.print(f"[red]✗[/] {exc}")
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
|
|
81
|
+
pixel_size_m = abs(ref_profile["transform"].a)
|
|
82
|
+
result = compute_pixel_offsets(
|
|
83
|
+
ref_arr,
|
|
84
|
+
sec_arr,
|
|
85
|
+
pixel_size_m=pixel_size_m,
|
|
86
|
+
window_size=window_size,
|
|
87
|
+
step_size=step_size,
|
|
88
|
+
chip_size=chip_size,
|
|
89
|
+
snr_threshold=snr_threshold,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
out_transform = ref_profile["transform"] * ref_profile["transform"].scale(step_size, step_size)
|
|
93
|
+
out_profile = dict(ref_profile)
|
|
94
|
+
out_profile.update(
|
|
95
|
+
height=result.dx.shape[0],
|
|
96
|
+
width=result.dx.shape[1],
|
|
97
|
+
transform=out_transform,
|
|
98
|
+
)
|
|
99
|
+
written = result.export_geotiff(output, out_profile)
|
|
100
|
+
|
|
101
|
+
n_reliable = int(result.reliable.sum())
|
|
102
|
+
n_total = int(result.reliable.size)
|
|
103
|
+
console.print(f"[green]✓[/] offset-track → {written}")
|
|
104
|
+
console.print(f" {n_reliable}/{n_total} windows reliable ({100 * n_reliable / n_total:.1f}%)")
|
|
105
|
+
console.print(f" window={window_size}px step={step_size}px pixel_size={pixel_size_m:.2f}m")
|
|
@@ -282,6 +282,7 @@ def search_run(
|
|
|
282
282
|
|
|
283
283
|
sb = PyGeoFetch()
|
|
284
284
|
|
|
285
|
+
optical_config = None
|
|
285
286
|
if validate_optical and (
|
|
286
287
|
optical_max_cloud_cover,
|
|
287
288
|
optical_min_coverage,
|
|
@@ -298,22 +299,48 @@ def search_run(
|
|
|
298
299
|
overrides["required_bands"] = [
|
|
299
300
|
b.strip() for b in optical_required_bands.split(",")
|
|
300
301
|
]
|
|
301
|
-
|
|
302
|
+
optical_config = OpticalValidationConfig(**overrides)
|
|
303
|
+
sb.optical_validation_config = optical_config
|
|
302
304
|
|
|
303
305
|
with console.status(
|
|
304
306
|
f"[cyan]Searching {len(provider_list or [])} provider(s)...[/]"
|
|
305
307
|
):
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
308
|
+
if validate_optical:
|
|
309
|
+
# Real, deliberate choice: search WITHOUT filtering here, then
|
|
310
|
+
# validate directly in this CLI layer, rather than calling
|
|
311
|
+
# search(validate_optical=True) and losing visibility into
|
|
312
|
+
# what got rejected and why. sb.search()'s own contract
|
|
313
|
+
# (returns only the passed scenes) is left untouched for
|
|
314
|
+
# every other caller -- this is a CLI-only reporting path,
|
|
315
|
+
# not a change to the library's real filtering behavior.
|
|
316
|
+
raw_results = sb.search(
|
|
317
|
+
query,
|
|
318
|
+
providers=provider_list,
|
|
319
|
+
use_cache=not no_cache,
|
|
320
|
+
validate_optical=False,
|
|
321
|
+
)
|
|
322
|
+
results, validation_summary = _validate_and_summarize(
|
|
323
|
+
sb, raw_results, query, optical_config
|
|
324
|
+
)
|
|
325
|
+
else:
|
|
326
|
+
results = sb.search(
|
|
327
|
+
query,
|
|
328
|
+
providers=provider_list,
|
|
329
|
+
use_cache=not no_cache,
|
|
330
|
+
validate_optical=False,
|
|
331
|
+
)
|
|
332
|
+
validation_summary = None
|
|
312
333
|
|
|
313
334
|
if not results:
|
|
314
335
|
console.print("[yellow]No results found.[/]")
|
|
315
336
|
return
|
|
316
337
|
|
|
338
|
+
# Real, clean validation info + full results table, organized so the
|
|
339
|
+
# summary always appears before the detailed table -- not interleaved
|
|
340
|
+
# with save/format output below.
|
|
341
|
+
if validation_summary is not None:
|
|
342
|
+
_display_validation_summary(validation_summary)
|
|
343
|
+
|
|
317
344
|
# Save if requested
|
|
318
345
|
if output:
|
|
319
346
|
out_path = Path(output)
|
|
@@ -365,6 +392,92 @@ def search_run(
|
|
|
365
392
|
click.echo(r.id)
|
|
366
393
|
|
|
367
394
|
|
|
395
|
+
def _validate_and_summarize(sb, raw_results, query, optical_config):
|
|
396
|
+
"""
|
|
397
|
+
Real validation pass over every raw candidate, returning both the
|
|
398
|
+
scenes that actually pass and a real, structured summary of what got
|
|
399
|
+
rejected and why -- the information sb.search(validate_optical=True)
|
|
400
|
+
alone discards once it returns only the filtered list.
|
|
401
|
+
"""
|
|
402
|
+
from collections import Counter
|
|
403
|
+
|
|
404
|
+
from pygeofetch.validation import OpticalPreflightValidator, OpticalValidationConfig
|
|
405
|
+
|
|
406
|
+
validator = OpticalPreflightValidator(optical_config or OpticalValidationConfig())
|
|
407
|
+
aoi = sb._derive_aoi_polygon(query)
|
|
408
|
+
|
|
409
|
+
passed_results = []
|
|
410
|
+
rejection_reasons: Counter = Counter()
|
|
411
|
+
warning_reasons: Counter = Counter()
|
|
412
|
+
n_warned_but_passed = 0
|
|
413
|
+
|
|
414
|
+
for scene in raw_results:
|
|
415
|
+
report = validator.validate_scene(scene, aoi, query.start_date, query.end_date)
|
|
416
|
+
if report.passed:
|
|
417
|
+
passed_results.append(scene)
|
|
418
|
+
if report.issues:
|
|
419
|
+
n_warned_but_passed += 1
|
|
420
|
+
for issue in report.issues:
|
|
421
|
+
warning_reasons[issue.code] += 1
|
|
422
|
+
else:
|
|
423
|
+
for issue in report.issues:
|
|
424
|
+
if issue.severity == "ERROR":
|
|
425
|
+
rejection_reasons[issue.code] += 1
|
|
426
|
+
|
|
427
|
+
summary = {
|
|
428
|
+
"n_total": len(raw_results),
|
|
429
|
+
"n_passed": len(passed_results),
|
|
430
|
+
"n_rejected": len(raw_results) - len(passed_results),
|
|
431
|
+
"n_warned_but_passed": n_warned_but_passed,
|
|
432
|
+
"rejection_reasons": rejection_reasons,
|
|
433
|
+
"warning_reasons": warning_reasons,
|
|
434
|
+
}
|
|
435
|
+
return passed_results, summary
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _display_validation_summary(summary: dict) -> None:
|
|
439
|
+
"""Clean, real info block summarizing optical validation -- shown
|
|
440
|
+
before the detailed results table, not interleaved with it."""
|
|
441
|
+
n_total = summary["n_total"]
|
|
442
|
+
n_passed = summary["n_passed"]
|
|
443
|
+
n_rejected = summary["n_rejected"]
|
|
444
|
+
pct = (100 * n_passed / n_total) if n_total else 0.0
|
|
445
|
+
|
|
446
|
+
console.print(
|
|
447
|
+
f"\n[bold]Optical validation:[/] {n_passed}/{n_total} scenes passed "
|
|
448
|
+
f"({pct:.0f}%)"
|
|
449
|
+
+ (f", [yellow]{n_rejected} rejected[/]" if n_rejected else "")
|
|
450
|
+
+ (
|
|
451
|
+
f", [cyan]{summary['n_warned_but_passed']} passed with warnings[/]"
|
|
452
|
+
if summary["n_warned_but_passed"]
|
|
453
|
+
else ""
|
|
454
|
+
)
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
if summary["rejection_reasons"]:
|
|
458
|
+
reasons_table = Table(
|
|
459
|
+
title="Rejection reasons", header_style="bold red", show_lines=False
|
|
460
|
+
)
|
|
461
|
+
reasons_table.add_column("Code", style="red")
|
|
462
|
+
reasons_table.add_column("Count", justify="right")
|
|
463
|
+
for code, count in summary["rejection_reasons"].most_common():
|
|
464
|
+
reasons_table.add_row(code, str(count))
|
|
465
|
+
console.print(reasons_table)
|
|
466
|
+
|
|
467
|
+
if summary["warning_reasons"]:
|
|
468
|
+
warn_table = Table(
|
|
469
|
+
title="Warnings on passed scenes",
|
|
470
|
+
header_style="bold yellow",
|
|
471
|
+
show_lines=False,
|
|
472
|
+
)
|
|
473
|
+
warn_table.add_column("Code", style="yellow")
|
|
474
|
+
warn_table.add_column("Count", justify="right")
|
|
475
|
+
for code, count in summary["warning_reasons"].most_common():
|
|
476
|
+
warn_table.add_row(code, str(count))
|
|
477
|
+
console.print(warn_table)
|
|
478
|
+
console.print() # real spacing before the full results table below
|
|
479
|
+
|
|
480
|
+
|
|
368
481
|
def _display_table(results) -> None:
|
|
369
482
|
table = Table(title=f"{len(results)} Results", header_style="bold blue")
|
|
370
483
|
table.add_column("ID", style="cyan", max_width=30)
|
|
@@ -441,4 +554,4 @@ def _save_csv(results, path: Path) -> None:
|
|
|
441
554
|
)
|
|
442
555
|
writer.writeheader()
|
|
443
556
|
for r in results:
|
|
444
|
-
writer.writerow(_item_to_dict(r))
|
|
557
|
+
writer.writerow(_item_to_dict(r))
|