Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a layover_shadow_mask_files to mask pixels in layover/shadow during wrapped_phase #462

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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