Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
625921a
Bump ruff from 0.14.5 to 0.14.6 in the pip-deps group
dependabot[bot] Nov 24, 2025
9aab22a
Merge pull request #16 from ASFHyP3/dependabot/pip/pip-deps-f86d19be86
jtherrmann Dec 4, 2025
2d1a2f2
Bump the pip-deps group with 2 updates
dependabot[bot] Dec 8, 2025
c564151
Merge pull request #17 from ASFHyP3/dependabot/pip/pip-deps-3ba37b8ada
mfangaritav Feb 11, 2026
1731aee
Bump the pip-deps group with 2 updates
dependabot[bot] Feb 16, 2026
b999bf5
Merge pull request #18 from ASFHyP3/dependabot/pip/pip-deps-5900ab8a16
mfangaritav Feb 20, 2026
3f5f1fe
Bump ruff from 0.15.1 to 0.15.2 in the pip-deps group
dependabot[bot] Feb 23, 2026
9eabd0b
added parameters to upload products
mfangaritav Feb 24, 2026
2a60adf
Merge pull request #19 from ASFHyP3/dependabot/pip/pip-deps-75011380db
mfangaritav Feb 24, 2026
58a3f93
Merge branch 'develop' into upload-bucket
mfangaritav Feb 24, 2026
e2d0c2a
reused publish bucket parameter
mfangaritav Feb 27, 2026
3e9fddf
rename job-name to project-name
mfangaritav Feb 27, 2026
f49ce10
rename job-name to project-name
mfangaritav Feb 27, 2026
d992b9d
adding parameter for input bucket
mfangaritav Feb 28, 2026
7b1a875
Merge pull request #20 from ASFHyP3/upload-bucket
mfangaritav Feb 28, 2026
c7055e3
switch priority for bucket
mfangaritav Mar 2, 2026
91b6ee0
mypy
mfangaritav Mar 2, 2026
58f719d
added nullable_string function
mfangaritav Mar 2, 2026
a992dc8
updated documentation
mfangaritav Mar 2, 2026
9e6689d
Merge pull request #21 from ASFHyP3/switch-priority
mfangaritav Mar 2, 2026
f63b35e
remove temporary support for python>=3.13
mfangaritav Mar 4, 2026
9f9a97f
changelog
mfangaritav Mar 4, 2026
4666bf5
Merge pull request #23 from ASFHyP3/fix-mintpy
mfangaritav Mar 4, 2026
f2dcb79
restrict python version
mfangaritav Mar 4, 2026
761c11c
restrict python
mfangaritav Mar 4, 2026
61dab54
restrict numpy<2.4
mfangaritav Mar 5, 2026
5741c98
changelog
mfangaritav Mar 5, 2026
1ba1eee
Update CHANGELOG.md
jhkennedy Mar 5, 2026
1f11729
Merge pull request #24 from ASFHyP3/fix-mintpy2
mfangaritav Mar 5, 2026
7780641
fix nan values and save plots
mfangaritav Apr 7, 2026
d033b0d
find nan in baseline
mfangaritav Apr 7, 2026
354f83b
changelog
mfangaritav Apr 7, 2026
53b5260
Merge pull request #26 from ASFHyP3/fix-nan
mfangaritav Apr 8, 2026
8ba1515
Bump the pip-deps group across 1 directory with 2 updates
dependabot[bot] Apr 20, 2026
80cc6c4
add check for valid pixels
mfangaritav May 3, 2026
d600f14
ruff and mypy
mfangaritav May 3, 2026
ce479ba
update dependencies
mfangaritav May 3, 2026
5b6c958
Merge pull request #29 from ASFHyP3/check-valid
mfangaritav May 8, 2026
0ea6aaf
Merge branch 'develop' into dependabot/pip/pip-deps-f9441223f4
mfangaritav May 8, 2026
9acd0b6
Merge pull request #28 from ASFHyP3/dependabot/pip/pip-deps-f9441223f4
mfangaritav May 8, 2026
5e4374c
changelog
mfangaritav May 20, 2026
ba10dd5
dependencies
mfangaritav May 20, 2026
1c6eb33
Merge pull request #31 from ASFHyP3/changelog
mfangaritav May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ jobs:
with:
local_package_name: hyp3_mintpy
python_versions: >-
["3.10", "3.11", "3.12", "3.13"]
["3.10", "3.11", "3.12", "3.13", "3.14"]
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [PEP 440](https://www.python.org/dev/peps/pep-0440/)
and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.2.0]

### Added
- Added new parameters `--publish-bucket` and `--publish-prefix` to upload products to s3 bucket.
- Added parameter `--input-bucket` to pull multiburst products from an specific s3 bucket.
- Added network and coherence plots to the output product.
- Added check for valid pixels on the downloaded pairs, if it does not find any it removes the pair.

### Changed
- Renamed `--prefix` parameter to `--input-prefix` to pull product from an specific s3 prefix.
- Restricted numpy to <2.4
- Changed `rename_products` function to find nan baselines and change them for 0.

## [1.1.0]

### Added
Expand Down
1 change: 1 addition & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies:
- hyp3lib>=3,<4
- hyp3_sdk
- mintpy
- numpy<2.4
- rasterio
# TODO: insert conda-forge dependencies as list here
- pip:
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ classifiers=[
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
dependencies = [
"hyp3lib>=3,<5",
Expand Down
4 changes: 2 additions & 2 deletions requirements-static.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
ruff==0.14.5
mypy==1.18.2
ruff==0.15.12
mypy==2.1.0
opensarlab_lib
51 changes: 44 additions & 7 deletions src/hyp3_mintpy/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from hyp3lib.fetch import write_credentials_to_netrc_file

from hyp3_mintpy.process import process_mintpy
from hyp3_mintpy.util import nullable_string, upload_file_to_s3_with_publish_access_keys


def main() -> None:
Expand All @@ -19,15 +20,37 @@ def main() -> None:
parser.add_argument('--bucket-prefix', default='', help='Add a bucket prefix to product(s)')

# TODO: Your arguments here
parser.add_argument('--job-name', help='The name of the HyP3 job', required=False)
parser.add_argument('--project-name', type=nullable_string, help='The name of the HyP3 project', required=False)
parser.add_argument('--start-date', type=nullable_string, help='Start date for the timeseries (YYYY-MM-DD)')
parser.add_argument('--end-date', type=nullable_string, help='End date for the timeseries (YYYY-MM-DD)')
parser.add_argument(
'--prefix', help='Folder that contains multiburst products in the volcsarvatory bucket', required=False
'--min-coherence', default=0.01, type=float, help='The minimum coherence to process', required=False
)
parser.add_argument(
'--min-coherence', default=0.01, type=float, help='The minimum coherence to process', required=False
'--input-bucket',
type=nullable_string,
default=None,
help='Bucket to pull multiburst products',
)
parser.add_argument(
'--input-prefix',
type=nullable_string,
default=None,
help='Prefix to pull the products from the publish bucket',
)
parser.add_argument(
'--publish-bucket',
type=nullable_string,
default=None,
help='Additionally, publish products to this bucket. Necessary credentials must be provided '
'via the `PUBLISH_ACCESS_KEY_ID` and `PUBLISH_SECRET_ACCESS_KEY` environment variables.',
)
parser.add_argument(
'--publish-prefix',
type=nullable_string,
default=None,
help='Prefix for the bucket where the products will be published',
)
parser.add_argument('--start-date', type=str, help='Start date for the timeseries (YYYY-MM-DD)')
parser.add_argument('--end-date', type=str, help='End date for the timeseries (YYYY-MM-DD)')

args = parser.parse_args()

Expand All @@ -47,8 +70,9 @@ def main() -> None:
)

product_file = process_mintpy(
job_name=args.job_name,
prefix=args.prefix,
project_name=args.project_name,
input_bucket=args.input_bucket,
input_prefix=args.input_prefix,
min_coherence=args.min_coherence,
start=args.start_date,
end=args.end_date,
Expand All @@ -57,6 +81,19 @@ def main() -> None:
if args.bucket:
upload_file_to_s3(product_file, args.bucket, args.bucket_prefix)

if args.publish_bucket:
if args.publish_prefix:
prefix = args.publish_prefix
s3_name = str(product_file)
else:
if args.start_date is None or args.end_date is None:
prefix = 'multiburst_ts/'
s3_name = str(product_file)
else:
prefix = f'multiburst_ts/{str(product_file).split(".zip")[0]}'
s3_name = f'{args.start_date.replace("-", "")}_{args.end_date.replace("-", "")}.zip'
upload_file_to_s3_with_publish_access_keys(product_file, args.publish_bucket, prefix, s3_name)


if __name__ == '__main__':
main()
58 changes: 41 additions & 17 deletions src/hyp3_mintpy/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ def rename_products(folder: str) -> None:
ar = txts[0].open()
lines = ar.readlines()
ar.close()
nan = any(['Baseline' in line and 'nan' in line for line in lines])
if nan:
ar = txts[0].open('w')
for line in lines:
if 'Baseline' in line and 'nan' in line:
line = line.replace('nan', '0')
ar.write(line)
ar.close()
burst = lines[0].split('_')[1] + '_' + lines[0].split('_')[2]
for f in fs:
name = f.name
Expand Down Expand Up @@ -83,6 +91,9 @@ def download_job_pairs(
for z in file_list:
if check_product(z.name, start, end):
shutil.unpack_archive(str(z), folder)
wphase = list(Path(folder).glob('**/*_wrapped_phase.tif'))[0]
if not util.check_valid_pixels(wphase):
shutil.rmtree('/'.join(str(wphase).split('/')[0:-1]))
z.unlink()

rename_products(folder)
Expand All @@ -91,31 +102,33 @@ def download_job_pairs(


def download_bucket_pairs(
key: str | None = None,
bucket: str,
key: str = '',
start: str | None = None,
end: str | None = None,
path: str = 'multiburst_products/',
bucket: str = 'volcsarvatory-data-test',
) -> str:
"""Downloads multiburst products from bucket and renames files to meet MintPy standards.

Args:
bucket: Name of the bucket.
key: Folder name that contains the multiburst product.
start: Start date for the timeseries if one of the product dates is before this, it won't be downloaded.
end: End date for the timeseries if one of the product dates is after this, it won't be downloaded.
path: Additional prefix to the products.
bucket: Name of the bucket.
"""
s3 = boto3.resource('s3', config=boto3.session.Config(signature_version=botocore.UNSIGNED))
buck = s3.Bucket(bucket)
folder = str(key).split('/')[-1]
folder = f'{str(key).split("/")[-1]}'
Path.mkdir(Path(folder))
for s3_object in tqdm(buck.objects.filter(Prefix=f'{path}{key}')):
for s3_object in tqdm(buck.objects.filter(Prefix=f'{key}')):
path, filename = os.path.split(s3_object.key)
if check_product(filename, start, end):
buck.download_file(s3_object.key, f'{folder}/{filename}')
z = Path(f'{folder}/{filename}')
shutil.unpack_archive(str(z), folder)
wphase = list(Path(folder).glob('**/*_wrapped_phase.tif'))[0]
if not util.check_valid_pixels(wphase):
shutil.rmtree('/'.join(str(wphase).split('/')[0:-1]))
z.unlink()
rename_products(folder)

Expand Down Expand Up @@ -300,36 +313,47 @@ def run_mintpy(output_name: str) -> Path:
subprocess.call(f'mv {output_name}/MintPy/*.h5 {output_name}/', shell=True)
subprocess.call(f'mv {output_name}/MintPy/inputs/geometry*.h5 {output_name}/', shell=True)
subprocess.call(f'mv {output_name}/MintPy/*.txt {output_name}/', shell=True)
subprocess.call(f'mv {output_name}/MintPy/*.pdf {output_name}/', shell=True)
subprocess.call(f'mv {output_name}/MintPy/*.png {output_name}/', shell=True)
subprocess.call(f'rm -rf {output_name}/MintPy {output_name}/S1_* {output_name}/shape_*', shell=True)
output_zip = shutil.make_archive(base_name=output_name, format='zip', base_dir=output_name)

return Path(output_zip)


def process_mintpy(
job_name: str | None, prefix: str | None, min_coherence: float, start: str | None = None, end: str | None = None
project_name: str | None,
input_bucket: str | None,
input_prefix: str | None,
min_coherence: float,
start: str | None = None,
end: str | None = None,
) -> Path:
"""Create a greeting product.

Args:
job_name: Name of the HyP3 project.
prefix: Folder that contains multiburst products.
project_name: Name of the HyP3 project.
input_bucket: Bucket that contains multiburst products.
input_prefix: Folder that contains multiburst products.
min_coherence: Minimum coherence for timeseries processing.
start: Start date for the timeseries
end: End date for the timeseries

Returns:
Path for the output zip file.
"""
if job_name is None and prefix is None:
raise ValueError('You should give a job name or a bucket to pull the data from')
elif job_name is not None and prefix is not None:
warnings.warn('Both job name and prefix were given. You should give just one. Using job name...')

if job_name is not None:
output_name = download_job_pairs(job_name, start, end)
if project_name is None and (input_bucket is None or input_prefix is None):
raise ValueError('You should give a project name or a bucekt and a prefix to pull the data')
elif project_name is not None and input_prefix is not None:
warnings.warn('Both job name and prefix were given. You should give just one. Using input prefix...')

if input_bucket is not None:
if input_prefix is None:
input_prefix = ''
output_name = download_bucket_pairs(str(input_bucket), input_prefix, start, end)
else:
output_name = download_bucket_pairs(prefix, start, end)
output_name = download_job_pairs(str(project_name), start, end)

set_same_frame(output_name, wgs84=True)

write_cfg(output_name, str(min_coherence))
Expand Down
68 changes: 68 additions & 0 deletions src/hyp3_mintpy/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
from datetime import datetime
from pathlib import Path

import boto3
import geopandas as gpd
import numpy as np
import rasterio
import shapely.wkt
from hyp3lib.aws import get_content_type, get_tag_set
from mintpy.utils import readfile
from osgeo import gdal, ogr, osr
from pyproj import Transformer
Expand Down Expand Up @@ -280,3 +282,69 @@ def save_shapefile(
feat = None

ds = layer = feat = None


def nullable_string(argument_string: str) -> str | None:
"""Identify if string is None.

Takes:
argument_string: Input string.

Returns: None if input string is 'None' else input string
"""
argument_string = argument_string.replace('None', '').strip()
return argument_string if argument_string else None


def check_valid_pixels(image_path: Path) -> bool:
"""Check if the image has valid pixels.

Takes:
image: Local image path

Returns: True if there's at least one valid pixel, False otherwise
"""
has_valid = False
with rasterio.open(str(image_path)) as src:
data = src.read(1)
nodata = src.nodata
valid_mask = ~np.isnan(data) & ~np.isinf(data)
if nodata is not None:
valid = data != nodata
valid_mask &= valid
has_valid = bool(np.any(valid_mask))
return has_valid


def upload_file_to_s3_with_publish_access_keys(
path_to_file: Path, bucket: str, prefix: str = '', s3_name: str | None = None
) -> None:
"""Uploads file to s3 bucket.

path_file: Local file path
bucket: Bucket name
prefix: Bucket prefix
s3_name: Name of file in s3 bucekt
"""
try:
access_key_id = os.environ['PUBLISH_ACCESS_KEY_ID']
access_key_secret = os.environ['PUBLISH_SECRET_ACCESS_KEY']
except KeyError:
raise ValueError(
'Please provide S3 Bucket upload access key credentials via the '
'PUBLISH_ACCESS_KEY_ID and PUBLISH_SECRET_ACCESS_KEY environment variables'
)

s3_client = boto3.client('s3', aws_access_key_id=access_key_id, aws_secret_access_key=access_key_secret)

if s3_name is None:
s3_name = path_to_file.name
key = str(Path(prefix) / s3_name)

extra_args = {'ContentType': get_content_type(key)}

s3_client.upload_file(str(path_to_file), bucket, key, extra_args)

tag_set = get_tag_set(path_to_file.name)

s3_client.put_object_tagging(Bucket=bucket, Key=key, Tagging=tag_set)
Loading