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