Files

652 lines
26 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import json
import math
import os
import re
import shutil
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List
try:
from .run_lt1_pyint_pipeline import (
DEFAULT_DERAMP_MODE,
DEFAULT_REFERENCE_MODE,
DEFAULT_REFLATTEN_AZIMUTH_STEP,
DEFAULT_REFLATTEN_COH_THRESHOLD,
DEFAULT_REFLATTEN_FALLBACK_COH_THRESHOLD,
DEFAULT_REFLATTEN_MODEL,
DEFAULT_REFLATTEN_RANGE_STEP,
assert_output_sanity,
assert_required_outputs,
calculate_dem_oversampling,
collect_expected_outputs,
collect_output_sanity_checks,
collect_stage_error_logs,
copy_native_outputs,
ensure_directory,
export_standard_products,
format_gamma_number,
hardlink_or_copy,
inspect_prepared_dem_path,
load_json_file,
load_pair_meta,
load_shell_environment,
normalize_date_text,
parse_args,
require_task_layout,
rerun_pair_product_stages,
run_gamma_reflatten,
run_logged,
safe_rmtree,
validate_unit_interval,
write_ifgram_list,
write_text,
write_wrapper_scripts,
)
except ImportError:
from run_lt1_pyint_pipeline import (
DEFAULT_DERAMP_MODE,
DEFAULT_REFERENCE_MODE,
DEFAULT_REFLATTEN_AZIMUTH_STEP,
DEFAULT_REFLATTEN_COH_THRESHOLD,
DEFAULT_REFLATTEN_FALLBACK_COH_THRESHOLD,
DEFAULT_REFLATTEN_MODEL,
DEFAULT_REFLATTEN_RANGE_STEP,
assert_output_sanity,
assert_required_outputs,
calculate_dem_oversampling,
collect_expected_outputs,
collect_output_sanity_checks,
collect_stage_error_logs,
copy_native_outputs,
ensure_directory,
export_standard_products,
format_gamma_number,
hardlink_or_copy,
inspect_prepared_dem_path,
load_json_file,
load_pair_meta,
load_shell_environment,
normalize_date_text,
parse_args,
require_task_layout,
rerun_pair_product_stages,
run_gamma_reflatten,
run_logged,
safe_rmtree,
validate_unit_interval,
write_ifgram_list,
write_text,
write_wrapper_scripts,
)
_WINDOWS_DRIVE_RE = re.compile(r"^[A-Za-z]:[\\/]")
def _normalize_runtime_path(value: Any) -> Path:
text = str(value or "").strip()
if not text:
return Path()
normalized = text.replace("\\", "/")
if normalized.startswith("/mnt/") or normalized.startswith("/"):
return Path(normalized).resolve()
if _WINDOWS_DRIVE_RE.match(text):
drive = text[0].lower()
tail = text[2:].replace("\\", "/").lstrip("/")
return Path(f"/mnt/{drive}/{tail}").resolve()
return Path(normalized).resolve()
def _build_s1_template_text(
*,
project_name: str,
satellite: str,
master_date: str,
range_looks: int,
azimuth_looks: int,
target_grid_size_m: int,
dem_lat_ovr: float,
dem_lon_ovr: float,
unwrap_coh_threshold: float,
geo_interp: str,
atmcor: bool,
atmcor_use_for_disp: bool,
reflatten: bool,
reflatten_model: str,
reflatten_coh_threshold: float,
parallel_workers: int,
unwrap: bool,
geocode: bool,
dem_mode: str,
fabdem_root: str,
prepared_dem_path: str,
opentopo_dem_type: str,
opentopo_api_key: str,
) -> str:
prepared_dem = inspect_prepared_dem_path(prepared_dem_path) if dem_mode == "prepared_file" else {}
lines = [
f"# Auto-generated for {project_name}",
f"satelite={satellite}",
f"masterDate={master_date}",
f"range_looks={int(range_looks)}",
f"azimuth_looks={int(azimuth_looks)}",
f"target_grid_size_m={int(target_grid_size_m or 0)}",
f"dem_lat_ovr={format_gamma_number(dem_lat_ovr)}",
f"dem_lon_ovr={format_gamma_number(dem_lon_ovr)}",
"download_data=0",
"raw2slc_all=1",
f"raw2slc_all_parallel={int(parallel_workers)}",
"extract_burst_all=1",
f"extract_all_parallel={int(parallel_workers)}",
"coreg_all=1",
f"coreg_all_parallel={int(parallel_workers)}",
"select_pairs=0",
"diff_all=1",
f"diff_all_parallel={int(parallel_workers)}",
"pot_all=0",
f"pot_all_parallel={int(parallel_workers)}",
f"unwrap_all={1 if unwrap else 0}",
f"unwrap_all_parallel={int(parallel_workers)}",
f"unwrapThreshold={format_gamma_number(unwrap_coh_threshold)}",
"make_mask=1",
"auto_unw=1",
"r_refer=-",
"a_refer=-",
f"atmcor_all={1 if atmcor else 0}",
f"atmcor_all_parallel={int(parallel_workers)}",
f"atmcor_use_for_disp={1 if (atmcor and atmcor_use_for_disp) else 0}",
f"reflatten={1 if reflatten else 0}",
f"reflatten_model={str(reflatten_model or DEFAULT_REFLATTEN_MODEL).strip().lower()}",
f"reflatten_coh_threshold={format_gamma_number(reflatten_coh_threshold)}",
f"geocode_all={1 if geocode else 0}",
f"geocode_all_parallel={int(parallel_workers)}",
f"geo_interp={str(geo_interp or '0').strip()}",
"gacos_correction=0",
"load_data=0",
"geocode_products=hyp3,licsbas",
"start_swath=1",
"end_swath=3",
"start_burst=1",
"end_burst=20",
]
if dem_mode == "local_fabdem" and fabdem_root:
lines.append(f"fabdem_dir={fabdem_root}")
else:
lines.append("fabdem_dir=-")
if dem_mode == "prepared_file" and prepared_dem.get("kind") == "gamma_ready":
lines.append(f"DEM={prepared_dem['direct_dem_path']}")
if dem_mode == "prepared_file" and prepared_dem.get("kind") == "source_dem":
lines.append(f"prepared_dem_source={prepared_dem['source_dem_path']}")
else:
lines.append("prepared_dem_source=-")
if dem_mode == "opentopo":
lines.append(f"opentopo_dem_type={opentopo_dem_type or 'SRTMGL1'}")
lines.append(f"opentopo_api_key={opentopo_api_key or '-'}")
else:
lines.append("opentopo_dem_type=-")
lines.append("opentopo_api_key=-")
return "\n".join(lines) + "\n"
def _resolve_s1_inputs(input_assets_payload: Dict[str, Any]) -> Dict[str, Any]:
task_source = input_assets_payload.get("task_source") or {}
prod = task_source.get("production_inputs") or {}
orbits = input_assets_payload.get("orbits") or {}
master_scene = _normalize_runtime_path(
((prod.get("master_scene") or {}).get("staged_path") or (prod.get("master_scene") or {}).get("path") or (prod.get("master_zip") or {}).get("staged_path") or (prod.get("master_zip") or {}).get("path") or "")
)
slave_scene = _normalize_runtime_path(
((prod.get("slave_scene") or {}).get("staged_path") or (prod.get("slave_scene") or {}).get("path") or (prod.get("slave_zip") or {}).get("staged_path") or (prod.get("slave_zip") or {}).get("path") or "")
)
master_eof = _normalize_runtime_path(
((orbits.get("master") or {}).get("staged_path") or (orbits.get("master") or {}).get("path") or "")
)
slave_eof = _normalize_runtime_path(
((orbits.get("slave") or {}).get("staged_path") or (orbits.get("slave") or {}).get("path") or "")
)
return {
"master_scene": master_scene,
"slave_scene": slave_scene,
"master_eof": master_eof,
"slave_eof": slave_eof,
}
def main() -> int:
args = parse_args()
task_dir = Path(args.task_dir).resolve()
project_dir = Path(args.project_dir).resolve()
run_root = project_dir.parent
template_root = Path(args.template_root).resolve()
output_dir = Path(args.output_dir).resolve()
pyint_home = Path(args.pyint_home).resolve()
pyint_app_script = Path(args.pyint_app_script).resolve()
dem_root = Path(args.dem_root).resolve()
input_assets_dir = Path(args.input_assets_dir).resolve() if args.input_assets_dir else None
input_assets_json = Path(args.input_assets_json).resolve() if args.input_assets_json else None
input_assets_payload = load_json_file(input_assets_json)
dem_mode = str(args.dem_mode or "local_fabdem").strip().lower() or "local_fabdem"
prepared_dem_info = inspect_prepared_dem_path(args.prepared_dem_path) if dem_mode == "prepared_file" else {}
dem_oversampling = calculate_dem_oversampling(
dem_resolution_m=float(args.dem_resolution_m),
target_grid_size_m=float(args.target_grid_size_m or 0),
dem_lat_ovr=float(args.dem_lat_ovr or 0.0),
dem_lon_ovr=float(args.dem_lon_ovr or 0.0),
)
unwrap_coh_threshold = validate_unit_interval(args.unwrap_coh_threshold, "--unwrap-coh-threshold")
coherence_mask_threshold = validate_unit_interval(args.coherence_mask_threshold, "--coherence-mask-threshold")
reference_mode = str(args.reference_mode or DEFAULT_REFERENCE_MODE).strip().lower() or DEFAULT_REFERENCE_MODE
deramp_mode = str(args.deramp_mode or DEFAULT_DERAMP_MODE).strip().lower() or DEFAULT_DERAMP_MODE
reference_coh_threshold = validate_unit_interval(args.reference_coh_threshold, "--reference-coh-threshold")
deramp_coh_threshold = validate_unit_interval(args.deramp_coh_threshold, "--deramp-coh-threshold")
geo_interp = str(args.geo_interp or "1").strip()
if geo_interp not in {"0", "1"}:
raise ValueError("--geo-interp must be 0 or 1.")
gamma_nodata_value = float(args.gamma_nodata_value)
if not math.isfinite(gamma_nodata_value):
raise ValueError("--gamma-nodata-value must be a finite number.")
reflatten_model = str(args.reflatten_model or DEFAULT_REFLATTEN_MODEL).strip().lower()
if reflatten_model == "linear":
reflatten_model = "plane"
if reflatten_model not in {"plane", "quadratic"}:
raise ValueError("--reflatten-model must be plane or quadratic.")
reflatten_coh_threshold = validate_unit_interval(args.reflatten_coh_threshold, "--reflatten-coh-threshold")
reflatten_fallback_coh_threshold = validate_unit_interval(
args.reflatten_fallback_coh_threshold,
"--reflatten-fallback-coh-threshold",
)
reflatten_range_step = max(1, int(args.reflatten_range_step or DEFAULT_REFLATTEN_RANGE_STEP))
reflatten_azimuth_step = max(1, int(args.reflatten_azimuth_step or DEFAULT_REFLATTEN_AZIMUTH_STEP))
require_task_layout(task_dir)
if not pyint_app_script.is_file():
raise FileNotFoundError(f"pyintApp.py not found: {pyint_app_script}")
if not input_assets_json or not input_assets_json.is_file():
raise RuntimeError("Sentinel-1 PyINT runner requires --input-assets-json.")
if dem_mode == "prepared_file" and not prepared_dem_info.get("kind"):
raise RuntimeError(
"Prepared DEM mode requires either a Gamma DEM with .par, "
"or a source DEM with .xml/.hdr/.vrt sidecars."
)
if args.force:
safe_rmtree(run_root)
safe_rmtree(template_root)
safe_rmtree(output_dir)
if run_root.exists():
raise RuntimeError(f"PyINT run root already exists, rerun with --force: {run_root}")
pair_meta = load_pair_meta(task_dir)
s1_inputs = _resolve_s1_inputs(input_assets_payload)
master_scene = s1_inputs["master_scene"]
slave_scene = s1_inputs["slave_scene"]
master_eof = s1_inputs["master_eof"]
slave_eof = s1_inputs["slave_eof"]
if not (master_scene.is_file() or master_scene.is_dir()):
raise FileNotFoundError(f"Sentinel-1 master scene not found: {master_scene}")
if not (slave_scene.is_file() or slave_scene.is_dir()):
raise FileNotFoundError(f"Sentinel-1 slave scene not found: {slave_scene}")
if not master_eof.is_file():
raise FileNotFoundError(f"Sentinel-1 master EOF not found: {master_eof}")
if not slave_eof.is_file():
raise FileNotFoundError(f"Sentinel-1 slave EOF not found: {slave_eof}")
master_date = normalize_date_text(args.master_date) or normalize_date_text(pair_meta.get("master_imaging_date"))
slave_date = normalize_date_text(args.slave_date) or normalize_date_text(pair_meta.get("slave_imaging_date"))
if not master_date or not slave_date:
raise RuntimeError("Unable to determine master/slave dates from pair metadata.")
pair_name = f"{master_date}-{slave_date}"
task_alias = str(args.task_alias or pair_meta.get("task_alias") or task_dir.name).strip() or task_dir.name
pair_key = str(args.pair_key or pair_meta.get("pair_key") or "").strip()
time_baseline_days = int(args.time_baseline_days or pair_meta.get("time_baseline_days") or 0)
satellite = str(pair_meta.get("master_satellite") or "S1A").strip().upper() or "S1A"
ensure_directory(run_root)
ensure_directory(template_root)
ensure_directory(output_dir)
ensure_directory(dem_root)
pyint_scripts_dir = pyint_home / "pyint"
wrappers_dir = ensure_directory(run_root / "wrappers")
write_wrapper_scripts(
wrappers_dir=wrappers_dir,
pyint_home=pyint_home,
python_cmd=args.python,
gamma_env_script=args.gamma_env_script,
)
for shim_name in ("python", "python3"):
shim_path = wrappers_dir / shim_name
write_text(
shim_path,
"\n".join(
[
"#!/usr/bin/env bash",
f"exec '{args.python}' \"$@\"",
"",
]
),
)
shim_path.chmod(shim_path.stat().st_mode | 0o111)
template_path = write_text(
template_root / f"{args.project_name}.template",
_build_s1_template_text(
project_name=args.project_name,
satellite=satellite,
master_date=master_date,
range_looks=args.range_looks,
azimuth_looks=args.azimuth_looks,
target_grid_size_m=int(args.target_grid_size_m or 0),
dem_lat_ovr=dem_oversampling["dem_lat_ovr"],
dem_lon_ovr=dem_oversampling["dem_lon_ovr"],
unwrap_coh_threshold=unwrap_coh_threshold,
geo_interp=geo_interp,
atmcor=bool(args.atmcor),
atmcor_use_for_disp=bool(args.atmcor_use_for_disp),
reflatten=bool(args.reflatten),
reflatten_model=reflatten_model,
reflatten_coh_threshold=reflatten_coh_threshold,
parallel_workers=args.parallel_workers,
unwrap=bool(args.unwrap),
geocode=bool(args.geocode),
dem_mode=dem_mode,
fabdem_root=str(args.fabdem_root or "").strip(),
prepared_dem_path=str(args.prepared_dem_path or "").strip(),
opentopo_dem_type=str(args.opentopo_dem_type or "SRTMGL1").strip(),
opentopo_api_key=str(args.opentopo_api_key or "").strip(),
),
)
scratch_root = ensure_directory(project_dir.parent)
archive_materialization: List[Dict[str, str]] = []
env = os.environ.copy()
if args.gamma_env_script:
env.update(load_shell_environment(args.gamma_env_script, env))
opod_dir = project_dir / "OPOD"
env.update(
{
"SCRATCHDIR": str(scratch_root),
"TEMPLATEDIR": str(template_root),
"DEMDIR": str(dem_root),
"OPOD_DIR": str(opod_dir),
"PATH": f"{wrappers_dir}:{pyint_scripts_dir}:{env.get('PATH', '')}",
"PYTHONPATH": f"{pyint_home}:{env.get('PYTHONPATH', '')}",
"PYINT_LT1_PRECISE_ORBIT_ENABLED": "false",
}
)
generate_stdout = run_root / "pyint_generate.stdout.log"
generate_stderr = run_root / "pyint_generate.stderr.log"
generate_result = run_logged(
[str(wrappers_dir / "pyintApp.py"), "-g", args.project_name],
env=env,
cwd=scratch_root,
stdout_path=generate_stdout,
stderr_path=generate_stderr,
)
if generate_result.returncode != 0:
raise RuntimeError(
f"pyintApp.py -g failed with rc={generate_result.returncode}: "
f"{(generate_result.stderr or generate_result.stdout or '').strip()}"
)
pyint_project_dir = project_dir
ensure_directory(pyint_project_dir)
opod_dir = ensure_directory(opod_dir)
download_dir = ensure_directory(pyint_project_dir / "DOWNLOAD")
ifgram_list_path = write_ifgram_list(pyint_project_dir / "ifgram_list.txt", master_date, slave_date, time_baseline_days)
for role, src_path in (("master", master_scene), ("slave", slave_scene)):
target_path = download_dir / src_path.name
if src_path.is_dir():
if target_path.exists():
if target_path.is_dir():
safe_rmtree(target_path)
else:
target_path.unlink()
shutil.copytree(src_path, target_path)
op = "copied_dir"
else:
op = hardlink_or_copy(src_path, target_path)
archive_materialization.append(
{
"role": role,
"source": str(src_path),
"target": str(target_path),
"operation": op,
}
)
for role, src_path in (("master_orbit", master_eof), ("slave_orbit", slave_eof)):
target_path = opod_dir / src_path.name
op = hardlink_or_copy(src_path, target_path)
archive_materialization.append(
{
"role": role,
"source": str(src_path),
"target": str(target_path),
"operation": op,
}
)
run_stdout = run_root / "pyint.stdout.log"
run_stderr = run_root / "pyint.stderr.log"
run_started_at = datetime.utcnow().isoformat(timespec="seconds") + "Z"
run_result = run_logged(
[str(wrappers_dir / "pyintApp.py"), args.project_name],
env=env,
cwd=scratch_root,
stdout_path=run_stdout,
stderr_path=run_stderr,
)
if run_result.returncode != 0:
stage_error_logs = collect_stage_error_logs(pyint_project_dir)
detail_text = (run_result.stderr or run_result.stdout or "").strip()
if stage_error_logs:
log_text = ", ".join(f"{name}={path}" for name, path in stage_error_logs.items())
detail_text = f"{detail_text}\nStage logs: {log_text}" if detail_text else f"Stage logs: {log_text}"
raise RuntimeError(f"pyintApp.py failed with rc={run_result.returncode}: {detail_text}")
repair_summary: Dict[str, Any] = {
"attempted": False,
"attempt_count": 0,
"max_attempts": 1,
}
outputs = collect_expected_outputs(pyint_project_dir, pair_name, args.range_looks)
try:
assert_required_outputs(outputs, unwrap=bool(args.unwrap), geocode=bool(args.geocode))
output_sanity_checks = collect_output_sanity_checks(outputs, unwrap=bool(args.unwrap), geocode=bool(args.geocode))
assert_output_sanity(output_sanity_checks)
except RuntimeError as exc:
repair_summary = rerun_pair_product_stages(
project_name=args.project_name,
project_dir=pyint_project_dir,
run_root=run_root,
scratch_root=scratch_root,
env=env,
pair_name=pair_name,
master_date=master_date,
slave_date=slave_date,
range_looks=args.range_looks,
unwrap=bool(args.unwrap),
atmcor=bool(args.atmcor),
geocode=bool(args.geocode),
)
outputs = collect_expected_outputs(pyint_project_dir, pair_name, args.range_looks)
assert_required_outputs(outputs, unwrap=bool(args.unwrap), geocode=bool(args.geocode))
output_sanity_checks = collect_output_sanity_checks(outputs, unwrap=bool(args.unwrap), geocode=bool(args.geocode))
assert_output_sanity(output_sanity_checks)
stage_error_logs = collect_stage_error_logs(pyint_project_dir)
reflatten_summary: Dict[str, Any] = {
"enabled": bool(args.reflatten),
"applied": False,
"reason": "",
"model": reflatten_model,
"coherence_threshold": reflatten_coh_threshold,
"fallback_coherence_threshold": reflatten_fallback_coh_threshold,
"range_step": reflatten_range_step,
"azimuth_step": reflatten_azimuth_step,
}
if bool(args.reflatten) and bool(args.unwrap):
reflatten_summary = run_gamma_reflatten(
project_dir=pyint_project_dir,
run_root=run_root,
output_dir=output_dir,
outputs=outputs,
pair_name=pair_name,
master_date=master_date,
range_looks=args.range_looks,
env=env,
model=reflatten_model,
coherence_threshold=reflatten_coh_threshold,
fallback_coherence_threshold=reflatten_fallback_coh_threshold,
range_step=reflatten_range_step,
azimuth_step=reflatten_azimuth_step,
geo_interp=geo_interp,
)
outputs = collect_expected_outputs(pyint_project_dir, pair_name, args.range_looks)
output_sanity_checks = collect_output_sanity_checks(outputs, unwrap=bool(args.unwrap), geocode=bool(args.geocode))
assert_output_sanity(output_sanity_checks)
elif bool(args.reflatten):
reflatten_summary["reason"] = "unwrap disabled"
else:
reflatten_summary["reason"] = "disabled"
copied_paths = copy_native_outputs(
project_dir=pyint_project_dir,
output_dir=output_dir,
pair_name=pair_name,
template_path=template_path,
ifgram_list_path=ifgram_list_path,
stdout_path=run_stdout,
stderr_path=run_stderr,
)
standard_products = (
export_standard_products(
project_dir=pyint_project_dir,
output_dir=output_dir,
pair_name=pair_name,
master_date=master_date,
range_looks=args.range_looks,
azimuth_looks=args.azimuth_looks,
target_grid_size_m=int(args.target_grid_size_m or 0),
coherence_mask_threshold=coherence_mask_threshold,
reference_mode=reference_mode,
reference_coh_threshold=reference_coh_threshold,
deramp_mode=deramp_mode,
deramp_coh_threshold=deramp_coh_threshold,
atmcor_enabled=bool(args.atmcor),
atmcor_use_for_disp=bool(args.atmcor_use_for_disp),
reflatten_summary=reflatten_summary,
gamma_nodata_value=gamma_nodata_value,
outputs=outputs,
env=env,
run_root=run_root,
)
if bool(args.geocode)
else {"enabled": False, "reason": "geocode disabled"}
)
summary = {
"ok": True,
"task_dir": str(task_dir),
"task_alias": task_alias,
"pair_key": pair_key,
"project_name": args.project_name,
"project_dir": str(pyint_project_dir),
"run_root": str(run_root),
"template_root": str(template_root),
"output_dir": str(output_dir),
"pyint_home": str(pyint_home),
"pyint_app_script": str(pyint_app_script),
"gamma_env_script": args.gamma_env_script,
"dem": {
"mode": dem_mode,
"dem_root": str(dem_root),
"fabdem_root": str(args.fabdem_root or "").strip(),
"prepared_dem_path": str(args.prepared_dem_path or "").strip(),
"prepared_dem_kind": str(prepared_dem_info.get("kind") or ""),
"configured_resolution_m": float(args.dem_resolution_m),
"oversampling": dem_oversampling,
"opentopo_dem_type": str(args.opentopo_dem_type or "SRTMGL1").strip(),
"opentopo_api_key_configured": bool(str(args.opentopo_api_key or "").strip()),
},
"orbit_policy": "require_eof",
"precise_orbit_bridge": {"enabled": False, "mode": "not_applicable"},
"input_assets_dir": str(input_assets_dir) if input_assets_dir else "",
"input_assets_json": str(input_assets_json) if input_assets_json else "",
"input_assets": input_assets_payload,
"master_date": master_date,
"slave_date": slave_date,
"pair_name": pair_name,
"time_baseline_days": time_baseline_days,
"target_grid_size_m": int(args.target_grid_size_m or 0),
"range_looks": int(args.range_looks),
"azimuth_looks": int(args.azimuth_looks),
"dem_resolution_m": float(args.dem_resolution_m),
"dem_oversampling": dem_oversampling,
"unwrap_coh_threshold": unwrap_coh_threshold,
"coherence_quality_threshold": coherence_mask_threshold,
"reference_mode": reference_mode,
"reference_coh_threshold": reference_coh_threshold,
"deramp_mode": deramp_mode,
"deramp_coh_threshold": deramp_coh_threshold,
"gamma_nodata_value": gamma_nodata_value,
"geo_interp": geo_interp,
"atmcor": bool(args.atmcor),
"atmcor_use_for_disp": bool(args.atmcor_use_for_disp),
"reflatten": bool(args.reflatten),
"reflatten_model": reflatten_model,
"reflatten_coh_threshold": reflatten_coh_threshold,
"reflatten_fallback_coh_threshold": reflatten_fallback_coh_threshold,
"reflatten_range_step": reflatten_range_step,
"reflatten_azimuth_step": reflatten_azimuth_step,
"parallel_workers": int(args.parallel_workers),
"unwrap": bool(args.unwrap),
"geocode": bool(args.geocode),
"archives": {
"master": [str(master_scene)],
"slave": [str(slave_scene)],
},
"orbit_files": {
"master": str(master_eof),
"slave": str(slave_eof),
},
"archive_materialization": archive_materialization,
"workspace_outputs": outputs,
"output_sanity_checks": output_sanity_checks,
"output_repair": repair_summary,
"reflatten_summary": reflatten_summary,
"copied_outputs": copied_paths,
"standard_products": standard_products,
"logs": {
"generate_stdout": str(generate_stdout),
"generate_stderr": str(generate_stderr),
"run_stdout": str(run_stdout),
"run_stderr": str(run_stderr),
"stage_error_logs": stage_error_logs,
},
"started_at": run_started_at,
"finished_at": datetime.utcnow().isoformat(timespec="seconds") + "Z",
}
summary_path = output_dir / "pyint_run_summary.json"
write_text(summary_path, json.dumps(summary, ensure_ascii=True, indent=2) + "\n")
print(json.dumps(summary, ensure_ascii=True, indent=2))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as exc:
print(str(exc), file=sys.stderr)
raise