Skip to content

Commit

Permalink
Merge pull request #244 from ASFHyP3/develop
Browse files Browse the repository at this point in the history
Release v2.1.0
  • Loading branch information
jtherrmann authored Aug 27, 2024
2 parents abd51ec + 834e432 commit 43b81e6
Show file tree
Hide file tree
Showing 21 changed files with 917 additions and 614 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ 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).

## [2.1.0]
### Added
- The ability for the `insar_tops_burst` workflow to support processing multiple bursts as one SLC.

### Changed
- The interface for `insar_tops_burst` so that it takes `--reference` and `--secondary` granule lists. The positional `granules` argument is now optional and deprecated.
- Moved HyP3 product packaging functionality out of `insar_tops_burst.py` and to a new `packaging.py` so that both `insar_tops` and `insar_tops_burst` can use it.

## [2.0.0]
### Changed
- Orbit files are now retrieved using the [s1-orbits](https://github.com/ASFHyP3/sentinel1-orbits-py) library.
Expand Down
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,32 @@ The HyP3-ISCE2 plugin provides a set of workflows to process SAR satellite data
## Usage
The HyP3-ISCE2 plugin provides a set of workflows (accessible directly in Python or via a CLI) that can be used to process SAR data using ISCE2. The workflows currently included in this plugin are:

- `insar_tops`: A workflow for creating full-SLC Sentinel-1 geocoded unwrapped interferogram using ISCE2's TOPS processing workflow
- `insar_tops_burst`: A workflow for creating single-burst Sentinel-1 geocoded unwrapped interferogram using ISCE2's TOPS processing workflow
- `insar_stripmap`: A workflow for creating ALOS-1 geocoded unwrapped interferogram using ISCE2's Stripmap processing workflow
- `insar_tops`: A workflow for creating full-SLC Sentinel-1 geocoded unwrapped interferogram using ISCE2's TOPS processing workflow
- `insar_tops_burst`: A workflow for creating burst-based Sentinel-1 geocoded unwrapped interferogram using ISCE2's TOPS processing workflow
---

To run a workflow, simply run `python -m hyp3_isce2 ++process [WORKFLOW_NAME] [WORKFLOW_ARGS]`. For example, to run the `insar_tops_burst` workflow:

```
python -m hyp3_isce2 ++process insar_tops_burst \
S1_136231_IW2_20200604T022312_VV_7C85-BURST \
S1_136231_IW2_20200616T022313_VV_5D11-BURST \
--reference S1_136231_IW2_20200604T022312_VV_7C85-BURST \
--secondary S1_136231_IW2_20200616T022313_VV_5D11-BURST \
--looks 20x4 \
--apply-water-mask True
```

This command will create a Sentinel-1 interferogram that contains a deformation signal related to a
2020 Iranian earthquake.
and, for multiple burst pairs:

```
python -m hyp3_isce2 ++process insar_tops_burst \
--reference S1_136231_IW2_20200604T022312_VV_7C85-BURST S1_136232_IW2_20200604T022315_VV_7C85-BURST \
--secondary S1_136231_IW2_20200616T022313_VV_5D11-BURST S1_136232_IW2_20200616T022316_VV_5D11-BURST \
--looks 20x4 \
--apply-water-mask True
```

These commands will both create a Sentinel-1 interferogram that contains a deformation signal related to a 2020 Iranian earthquake.

### Product Merging Utility Usage
**This feature is under active development and is subject to change!**
Expand Down
1 change: 1 addition & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies:
- asf_search>=6.4.0
- gdal
- opencv
- burst2safe
# For packaging, and testing
- flake8
- flake8-import-order
Expand Down
6 changes: 3 additions & 3 deletions images/coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies = [
"gdal",
"hyp3lib>=3,<4",
"s1_orbits",
"burst2safe"
# Conda-forge only dependencies are listed below
# "opencv",
# "isce2>=2.6.3",
Expand Down
96 changes: 34 additions & 62 deletions src/hyp3_isce2/burst.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
from dataclasses import dataclass
from datetime import datetime, timedelta
from pathlib import Path
from secrets import token_hex
from typing import Iterator, List, Optional, Tuple, Union
from typing import Iterable, Iterator, List, Optional, Tuple, Union

import asf_search
import numpy as np
Expand Down Expand Up @@ -345,45 +344,6 @@ def download_bursts(param_list: Iterator[BurstParams]) -> List[BurstMetadata]:
return bursts


def get_product_name(reference_scene: str, secondary_scene: str, pixel_spacing: int) -> str:
"""Get the name of the interferogram product.
Args:
reference_scene: The reference burst name.
secondary_scene: The secondary burst name.
pixel_spacing: The spacing of the pixels in the output image.
Returns:
The name of the interferogram product.
"""

reference_split = reference_scene.split('_')
secondary_split = secondary_scene.split('_')

platform = reference_split[0]
burst_id = reference_split[1]
image_plus_swath = reference_split[2]
reference_date = reference_split[3][0:8]
secondary_date = secondary_split[3][0:8]
polarization = reference_split[4]
product_type = 'INT'
pixel_spacing = str(int(pixel_spacing))
product_id = token_hex(2).upper()

return '_'.join(
[
platform,
burst_id,
image_plus_swath,
reference_date,
secondary_date,
polarization,
product_type + pixel_spacing,
product_id,
]
)


def get_burst_params(scene_name: str) -> BurstParams:
results = asf_search.search(product_list=[scene_name])

Expand All @@ -400,35 +360,47 @@ def get_burst_params(scene_name: str) -> BurstParams:
)


def validate_bursts(reference_scene: str, secondary_scene: str) -> None:
def validate_bursts(reference: Union[str, Iterable[str]], secondary: Union[str, Iterable[str]]) -> None:
"""Check whether the reference and secondary bursts are valid.
Args:
reference_scene: The reference burst name.
secondary_scene: The secondary burst name.
Returns:
None
reference: Reference granule(s)
secondary: Secondary granule(s)
"""
ref_split = reference_scene.split('_')
sec_split = secondary_scene.split('_')
if isinstance(reference, str):
reference = [reference]
if isinstance(secondary, str):
secondary = [secondary]

if len(reference) < 1 or len(secondary) < 1:
raise ValueError('Must include at least 1 reference and 1 secondary burst')
if len(reference) != len(secondary):
raise ValueError('Must have the same number of reference and secondary bursts')

ref_num_swath_pol = sorted(g.split('_')[1] + '_' + g.split('_')[2] + '_' + g.split('_')[4] for g in reference)
sec_num_swath_pol = sorted(g.split('_')[1] + '_' + g.split('_')[2] + '_' + g.split('_')[4] for g in secondary)
if ref_num_swath_pol != sec_num_swath_pol:
msg = 'The reference and secondary burst ID sets do not match.\n'
msg += f' Reference IDs: {ref_num_swath_pol}\n'
msg += f' Secondary IDs: {sec_num_swath_pol}'
raise ValueError(msg)

pols = list(set(g.split('_')[4] for g in reference + secondary))

ref_burst_id = ref_split[1]
sec_burst_id = sec_split[1]
if len(pols) > 1:
raise ValueError(f'All bursts must have a single polarization. Polarizations present: {" ".join(pols)}')

ref_polarization = ref_split[4]
sec_polarization = sec_split[4]
if pols[0] not in ['VV', 'HH']:
raise ValueError(f'{pols[0]} polarization is not currently supported, only VV and HH.')

if ref_burst_id != sec_burst_id:
raise ValueError(f'The reference and secondary burst IDs are not the same: {ref_burst_id} and {sec_burst_id}.')
ref_dates = list(set(g.split('_')[3][:8] for g in reference))
sec_dates = list(set(g.split('_')[3][:8] for g in secondary))

if ref_polarization != sec_polarization:
raise ValueError(
f'The reference and secondary polarizations are not the same: {ref_polarization} and {sec_polarization}.'
)
if len(ref_dates) > 1 or len(sec_dates) > 1:
raise ValueError('Reference granules must be from one date and secondary granules must be another.')

if ref_polarization != 'VV' and ref_polarization != 'HH':
raise ValueError(f'{ref_polarization} polarization is not currently supported, only VV and HH.')
if ref_dates[0] >= sec_dates[0]:
raise ValueError('Reference granules must be older than secondary granules.')


def load_burst_position(swath_xml_path: str, burst_number: int) -> BurstPosition:
Expand Down Expand Up @@ -572,7 +544,7 @@ def safely_multilook(
if subset_to_valid:
last_line = position.first_valid_line + position.n_valid_lines
last_sample = position.first_valid_sample + position.n_valid_samples
mask[position.first_valid_line: last_line, position.first_valid_sample: last_sample] = identity_value
mask[position.first_valid_line:last_line, position.first_valid_sample:last_sample] = identity_value
else:
mask[:, :] = identity_value

Expand Down
36 changes: 21 additions & 15 deletions src/hyp3_isce2/insar_stripmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
log = logging.getLogger(__name__)


def insar_stripmap(user: str, password: str, reference_scene: str, secondary_scene: str) -> Path:
def insar_stripmap(reference_scene: str, secondary_scene: str) -> Path:
"""Create a Stripmap interferogram
Args:
Expand All @@ -35,12 +35,22 @@ def insar_stripmap(user: str, password: str, reference_scene: str, secondary_sce
Returns:
Path to the output files
"""
session = asf_search.ASFSession().auth_with_creds(user, password)

reference_product, secondary_product = asf_search.search(
scenes = sorted([reference_scene, secondary_scene])
print(scenes)
reference_scene = scenes[0]
secondary_scene = scenes[1]
products = asf_search.search(
granule_list=[reference_scene, secondary_scene],
processingLevel=asf_search.L1_0,
processingLevel="L1.0",
)

if products[0].properties['sceneName'] == reference_scene:
reference_product = products[0]
secondary_product = products[1]
else:
reference_product = products[1]
secondary_product = products[0]

assert reference_product.properties['sceneName'] == reference_scene
assert secondary_product.properties['sceneName'] == secondary_scene
products = (reference_product, secondary_product)
Expand All @@ -51,7 +61,7 @@ def insar_stripmap(user: str, password: str, reference_scene: str, secondary_sce
dem_path = download_dem_for_isce2(insar_roi, dem_name='glo_30', dem_dir=Path('dem'), buffer=0)

urls = [product.properties['url'] for product in products]
asf_search.download_urls(urls=urls, path=os.getcwd(), session=session, processes=2)
asf_search.download_urls(urls=urls, path=os.getcwd(), processes=2)

zip_paths = [product.properties['fileName'] for product in products]
for zip_path in zip_paths:
Expand Down Expand Up @@ -93,7 +103,7 @@ def insar_stripmap(user: str, password: str, reference_scene: str, secondary_sce

def get_product_file(product: asf_search.ASFProduct, file_prefix: str) -> str:
paths = glob.glob(str(Path(product.properties['fileID']) / f'{file_prefix}*'))
assert len(paths) == 1
assert len(paths) > 0
return paths[0]


Expand All @@ -104,10 +114,8 @@ def main():

parser.add_argument('--bucket', help='AWS S3 bucket HyP3 for upload the final product(s)')
parser.add_argument('--bucket-prefix', default='', help='Add a bucket prefix to product(s)')
parser.add_argument('--username', type=str, required=True)
parser.add_argument('--password', type=str, required=True)
parser.add_argument('--reference-scene', type=str, required=True)
parser.add_argument('--secondary-scene', type=str, required=True)
parser.add_argument('--reference', type=str, required=True)
parser.add_argument('--secondary', type=str, required=True)

args = parser.parse_args()

Expand All @@ -117,10 +125,8 @@ def main():
log.info('Begin InSAR Stripmap run')

product_dir = insar_stripmap(
user=args.username,
password=args.password,
reference_scene=args.reference_scene,
secondary_scene=args.secondary_scene,
reference_scene=args.reference,
secondary_scene=args.secondary,
)

log.info('InSAR Stripmap run completed successfully')
Expand Down
Loading

0 comments on commit 43b81e6

Please sign in to comment.