Skip to content

Commit

Permalink
Add a layover_shadow_mask_files to mask pixels in layover/shadow duri…
Browse files Browse the repository at this point in the history
…ng `wrapped_phase` (#462)

* Update `dolphin timeseries` cli for new options

* Add a `layover_shadow_mask_files` to mask pixels in layover/shadow during `wrapped_phase`
  • Loading branch information
scottstanie authored Oct 25, 2024
1 parent a2c20c2 commit 6346122
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 32 deletions.
4 changes: 4 additions & 0 deletions src/dolphin/workflows/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def _create_burst_cfg(
grouped_slc_files: dict[str, list[Path]],
grouped_amp_mean_files: dict[str, list[Path]],
grouped_amp_dispersion_files: dict[str, list[Path]],
grouped_layover_shadow_mask_files: dict[str, list[Path]],
) -> DisplacementWorkflow:
cfg_temp_dict = cfg.model_dump(exclude={"cslc_file_list"})

Expand All @@ -31,6 +32,9 @@ def _create_burst_cfg(
cfg_temp_dict["cslc_file_list"] = grouped_slc_files[burst_id]
cfg_temp_dict["amplitude_mean_files"] = grouped_amp_mean_files[burst_id]
cfg_temp_dict["amplitude_dispersion_files"] = grouped_amp_dispersion_files[burst_id]
cfg_temp_dict["layover_shadow_mask_files"] = grouped_layover_shadow_mask_files[
burst_id
]
return DisplacementWorkflow(**cfg_temp_dict)


Expand Down
8 changes: 8 additions & 0 deletions src/dolphin/workflows/config/_displacement.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,14 @@ class DisplacementWorkflow(WorkflowBase):
" calculation. If none provided, computed using the input SLC stack."
),
)
layover_shadow_mask_files: list[Path] = Field(
default_factory=list,
description=(
"Paths to layover/shadow binary masks, where 0 indicates a pixel in"
" layover/shadow, 1 is a good pixel. If none provided, no masking is"
" performed for layover/shadow."
),
)

phase_linking: PhaseLinkingOptions = Field(default_factory=PhaseLinkingOptions)
interferogram_network: InterferogramNetwork = Field(
Expand Down
8 changes: 8 additions & 0 deletions src/dolphin/workflows/config/_ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ class PsWorkflow(WorkflowBase):
# Options for each step in the workflow
ps_options: PsOptions = Field(default_factory=PsOptions)
output_options: OutputOptions = Field(default_factory=OutputOptions)
layover_shadow_mask_files: list[Path] = Field(
default_factory=list,
description=(
"Paths to layover/shadow binary masks, where 0 indicates a pixel in"
" layover/shadow, 1 is a good pixel. If none provided, no masking is"
" performed for layover/shadow."
),
)

# internal helpers
model_config = ConfigDict(
Expand Down
7 changes: 7 additions & 0 deletions src/dolphin/workflows/displacement.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ def run(
grouped_amp_mean_files = group_by_burst(cfg.amplitude_mean_files)
else:
grouped_amp_mean_files = defaultdict(list)
if cfg.layover_shadow_mask_files:
grouped_layover_shadow_mask_files = group_by_burst(
cfg.layover_shadow_mask_files
)
else:
grouped_layover_shadow_mask_files = defaultdict(list)

grouped_iono_files = parse_ionosphere_files(
cfg.correction_options.ionosphere_files, cfg.correction_options._iono_date_fmt
Expand All @@ -109,6 +115,7 @@ def run(
grouped_slc_files,
grouped_amp_mean_files,
grouped_amp_dispersion_files,
grouped_layover_shadow_mask_files,
),
)
for burst in grouped_slc_files
Expand Down
39 changes: 24 additions & 15 deletions src/dolphin/workflows/ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@
import opera_utils

import dolphin.ps
from dolphin import __version__
from dolphin import __version__, masking
from dolphin._log import log_runtime, setup_logging
from dolphin.io import VRTStack
from dolphin.utils import get_max_memory_usage
from dolphin.workflows.wrapped_phase import _get_mask

from .config import PsWorkflow
from .config import DisplacementWorkflow, PsWorkflow

logger = logging.getLogger(__name__)


@log_runtime
def run(
cfg: PsWorkflow,
cfg: PsWorkflow | DisplacementWorkflow,
compute_looked: bool = False,
debug: bool = False,
) -> list[Path]:
Expand Down Expand Up @@ -70,25 +71,32 @@ def run(
msg = "No input files found"
raise ValueError(msg)

# #############################################
# Make a VRT pointing to the input SLC files
# #############################################
subdataset = cfg.input_options.subdataset
vrt_stack = VRTStack(
input_file_list,
subdataset=subdataset,
outfile=cfg.work_directory / "slc_stack.vrt",
)
# Mark any files beginning with "compressed" as compressed
is_compressed = ["compressed" in str(f).lower() for f in input_file_list]

# Make the nodata mask from the polygons, if we're using OPERA CSLCs
try:
nodata_mask_file = cfg.work_directory / "nodata_mask.tif"
opera_utils.make_nodata_mask(
vrt_stack.file_list, out_file=nodata_mask_file, buffer_pixels=200
)
except Exception as e:
logger.warning(f"Could not make nodata mask: {e}")
nodata_mask_file = None
non_compressed_slcs = [
f for f, is_comp in zip(input_file_list, is_compressed) if not is_comp
]

layover_shadow_mask = (
cfg.layover_shadow_mask_files[0] if cfg.layover_shadow_mask_files else None
)
mask_filename = _get_mask(
output_dir=cfg.work_directory,
output_bounds=cfg.output_options.bounds,
output_bounds_wkt=cfg.output_options.bounds_wkt,
output_bounds_epsg=cfg.output_options.bounds_epsg,
like_filename=vrt_stack.outfile,
layover_shadow_mask=layover_shadow_mask,
cslc_file_list=non_compressed_slcs,
)
nodata_mask = masking.load_mask_as_numpy(mask_filename) if mask_filename else None

logger.info(f"Creating persistent scatterer file {ps_output}")
dolphin.ps.create_ps(
Expand All @@ -98,6 +106,7 @@ def run(
output_amp_dispersion_file=output_file_list[2],
like_filename=vrt_stack.outfile,
amp_dispersion_threshold=cfg.ps_options.amp_dispersion_threshold,
nodata_mask=nodata_mask,
block_shape=cfg.worker_settings.block_shape,
)
# Save a looked version of the PS mask too
Expand Down
38 changes: 21 additions & 17 deletions src/dolphin/workflows/wrapped_phase.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,17 @@ def run(
non_compressed_slcs = [
f for f, is_comp in zip(input_file_list, is_compressed) if not is_comp
]

layover_shadow_mask = (
cfg.layover_shadow_mask_files[0] if cfg.layover_shadow_mask_files else None
)
# Create a mask file from input bounding polygons and/or specified output bounds
mask_filename = _get_mask(
output_dir=cfg.work_directory,
output_bounds=cfg.output_options.bounds,
output_bounds_wkt=cfg.output_options.bounds_wkt,
output_bounds_epsg=cfg.output_options.bounds_epsg,
like_filename=vrt_stack.outfile,
layover_shadow_mask=layover_shadow_mask,
cslc_file_list=non_compressed_slcs,
)

Expand Down Expand Up @@ -461,9 +464,11 @@ def _get_mask(
output_bounds_wkt: str | None,
output_bounds_epsg: int,
like_filename: Filename,
layover_shadow_mask: Filename | None,
cslc_file_list: Sequence[Filename],
) -> Path | None:
# Make the nodata mask from the polygons, if we're using OPERA CSLCs
mask_files: list[Path] = []

try:
nodata_mask_file = output_dir / "nodata_mask.tif"
Expand All @@ -472,11 +477,11 @@ def _get_mask(
out_file=nodata_mask_file,
buffer_pixels=200,
)
mask_files.append(nodata_mask_file)
except Exception as e:
logger.warning(f"Could not make nodata mask: {e}")
nodata_mask_file = None

mask_filename: Path | None = None
# Also mask outside the area of interest if we've specified a small bounds
if output_bounds is not None or output_bounds_wkt is not None:
# Make a mask just from the bounds
Expand All @@ -488,23 +493,22 @@ def _get_mask(
output_filename=bounds_mask_filename,
like_filename=like_filename,
)
mask_files.append(bounds_mask_filename)

# Then combine with the nodata mask
if nodata_mask_file is not None:
combined_mask_filename = output_dir / "combined_mask.tif"
if not combined_mask_filename.exists():
masking.combine_mask_files(
mask_files=[bounds_mask_filename, nodata_mask_file],
output_file=combined_mask_filename,
output_convention=masking.MaskConvention.ZERO_IS_NODATA,
)
mask_filename = combined_mask_filename
else:
mask_filename = bounds_mask_filename
else:
mask_filename = nodata_mask_file
if layover_shadow_mask is not None:
mask_files.append(Path(layover_shadow_mask))

if not mask_files:
return None

return mask_filename
combined_mask_filename = output_dir / "combined_mask.tif"
if not combined_mask_filename.exists():
masking.combine_mask_files(
mask_files=mask_files,
output_file=combined_mask_filename,
output_convention=masking.MaskConvention.ZERO_IS_NODATA,
)
return combined_mask_filename


def _is_single_reference_network(
Expand Down

0 comments on commit 6346122

Please sign in to comment.