Apply current workspace changes
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
GAMMA_FLOAT32 = np.dtype(">f4")
|
||||
ZERO_ERROR_RE = re.compile(r"number of zero values\s+(\d+)\s+in MLI1 image patch exceeds threshold:\s+(\d+)", re.IGNORECASE)
|
||||
|
||||
|
||||
@dataclass
|
||||
class GammaShape:
|
||||
width: int
|
||||
lines: int
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Scan init_offsetm patch positions for LT-1 DEM-assisted coreg.")
|
||||
parser.add_argument("run_root", help="Experiment run root, e.g. /mnt/d/.../run_20260420T093322Z")
|
||||
parser.add_argument("--case", dest="cases", action="append", default=[], help="Case name to scan. Can be repeated.")
|
||||
parser.add_argument("--date", dest="dates", action="append", default=[], help="Slave date to scan. Can be repeated.")
|
||||
parser.add_argument("--project", default="pyint_stage")
|
||||
parser.add_argument("--patch-size", type=int, default=512)
|
||||
parser.add_argument("--stride", type=int, default=256)
|
||||
parser.add_argument("--max-candidates", type=int, default=20)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def parse_gamma_par_value(path: Path, key: str) -> str:
|
||||
prefix = key.strip() + ":"
|
||||
for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith(prefix):
|
||||
_, _, tail = stripped.partition(":")
|
||||
return tail.strip().split()[0]
|
||||
raise ValueError(f"Missing key '{key}' in {path}")
|
||||
|
||||
|
||||
def parse_gamma_shape(path: Path) -> GammaShape:
|
||||
width = int(float(parse_gamma_par_value(path, "range_samples")))
|
||||
lines = int(float(parse_gamma_par_value(path, "azimuth_lines")))
|
||||
return GammaShape(width=width, lines=lines)
|
||||
|
||||
|
||||
def build_valid_mask(path: Path, shape: GammaShape) -> np.ndarray:
|
||||
arr = np.memmap(path, dtype=GAMMA_FLOAT32, mode="r", shape=(shape.lines, shape.width))
|
||||
valid = np.isfinite(arr) & (arr != 0)
|
||||
return np.asarray(valid, dtype=np.uint8)
|
||||
|
||||
|
||||
def patch_sum(integral: np.ndarray, top: int, left: int, bottom: int, right: int) -> int:
|
||||
br = int(integral[bottom, right])
|
||||
tr = int(integral[top, right])
|
||||
bl = int(integral[bottom, left])
|
||||
tl = int(integral[top, left])
|
||||
return br - tr - bl + tl
|
||||
|
||||
|
||||
def candidate_positions(mask: np.ndarray, *, patch_size: int, stride: int, max_candidates: int) -> list[dict[str, Any]]:
|
||||
lines, width = mask.shape
|
||||
patch_size = max(1, min(patch_size, lines, width))
|
||||
half = patch_size // 2
|
||||
valid_y = range(half, lines - (patch_size - half) + 1, max(1, stride))
|
||||
valid_x = range(half, width - (patch_size - half) + 1, max(1, stride))
|
||||
|
||||
integral = np.pad(mask.astype(np.int64), ((1, 0), (1, 0)), mode="constant")
|
||||
integral = integral.cumsum(axis=0).cumsum(axis=1)
|
||||
patch_area = patch_size * patch_size
|
||||
|
||||
center_y = lines // 2
|
||||
center_x = width // 2
|
||||
center_key = (center_x, center_y)
|
||||
|
||||
candidates: list[dict[str, Any]] = []
|
||||
seen: set[tuple[int, int]] = set()
|
||||
|
||||
for y in valid_y:
|
||||
for x in valid_x:
|
||||
top = y - half
|
||||
left = x - half
|
||||
bottom = top + patch_size
|
||||
right = left + patch_size
|
||||
nonzero = patch_sum(integral, top, left, bottom, right)
|
||||
ratio = float(nonzero / patch_area)
|
||||
entry = {
|
||||
"rpos": int(x),
|
||||
"azpos": int(y),
|
||||
"patch_nonzero_count": int(nonzero),
|
||||
"patch_nonzero_ratio": ratio,
|
||||
"is_center": bool((x, y) == center_key),
|
||||
}
|
||||
candidates.append(entry)
|
||||
|
||||
candidates.sort(key=lambda item: item["patch_nonzero_ratio"], reverse=True)
|
||||
|
||||
selected: list[dict[str, Any]] = []
|
||||
for entry in candidates:
|
||||
key = (entry["rpos"], entry["azpos"])
|
||||
if key in seen:
|
||||
continue
|
||||
selected.append(entry)
|
||||
seen.add(key)
|
||||
if len(selected) >= max_candidates:
|
||||
break
|
||||
|
||||
if center_key not in seen:
|
||||
top = center_y - half
|
||||
left = center_x - half
|
||||
top = max(0, min(lines - patch_size, top))
|
||||
left = max(0, min(width - patch_size, left))
|
||||
bottom = top + patch_size
|
||||
right = left + patch_size
|
||||
nonzero = patch_sum(integral, top, left, bottom, right)
|
||||
selected.append(
|
||||
{
|
||||
"rpos": int(left + half),
|
||||
"azpos": int(top + half),
|
||||
"patch_nonzero_count": int(nonzero),
|
||||
"patch_nonzero_ratio": float(nonzero / patch_area),
|
||||
"is_center": True,
|
||||
}
|
||||
)
|
||||
|
||||
for index, entry in enumerate(selected, start=1):
|
||||
entry["rank"] = index
|
||||
return selected
|
||||
|
||||
|
||||
def parse_zero_error(text: str) -> dict[str, Any]:
|
||||
match = ZERO_ERROR_RE.search(text or "")
|
||||
if not match:
|
||||
return {}
|
||||
return {
|
||||
"zero_count": int(match.group(1)),
|
||||
"zero_threshold": int(match.group(2)),
|
||||
}
|
||||
|
||||
|
||||
def run_init_offsetm(
|
||||
*,
|
||||
mli0: Path,
|
||||
samp: Path,
|
||||
diff0: Path,
|
||||
output_dir: Path,
|
||||
patch_size: int,
|
||||
rpos: int,
|
||||
azpos: int,
|
||||
) -> dict[str, Any]:
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
diff_copy = output_dir / f"r{rpos}_a{azpos}.diff_par"
|
||||
shutil.copy2(diff0, diff_copy)
|
||||
|
||||
cmd = [
|
||||
"init_offsetm",
|
||||
str(mli0),
|
||||
str(samp),
|
||||
str(diff_copy),
|
||||
"1",
|
||||
"1",
|
||||
str(int(rpos)),
|
||||
str(int(azpos)),
|
||||
"-",
|
||||
"-",
|
||||
"-",
|
||||
str(int(patch_size)),
|
||||
"0",
|
||||
]
|
||||
result = subprocess.run(cmd, text=True, capture_output=True, check=False)
|
||||
combined = ((result.stdout or "") + "\n" + (result.stderr or "")).strip()
|
||||
payload = {
|
||||
"command": " ".join(cmd),
|
||||
"returncode": int(result.returncode),
|
||||
"ok": result.returncode == 0,
|
||||
"stdout_tail": (result.stdout or "")[-4000:],
|
||||
"stderr_tail": (result.stderr or "")[-4000:],
|
||||
"diff_par_copy": str(diff_copy),
|
||||
}
|
||||
payload.update(parse_zero_error(combined))
|
||||
return payload
|
||||
|
||||
|
||||
def scan_case_date(
|
||||
*,
|
||||
run_root: Path,
|
||||
project: str,
|
||||
case_name: str,
|
||||
slave_date: str,
|
||||
patch_size: int,
|
||||
stride: int,
|
||||
max_candidates: int,
|
||||
output_root: Path,
|
||||
) -> dict[str, Any]:
|
||||
case_root = run_root / case_name / project
|
||||
slc_dir = case_root / "SLC" / slave_date
|
||||
rslc_dir = case_root / "RSLC" / slave_date
|
||||
amp_par = slc_dir / f"{slave_date}_2rlks.amp.par"
|
||||
shape = parse_gamma_shape(amp_par)
|
||||
mask = build_valid_mask(rslc_dir / "mli0", shape)
|
||||
|
||||
candidates = candidate_positions(mask, patch_size=patch_size, stride=stride, max_candidates=max_candidates)
|
||||
output_dir = output_root / case_name / slave_date
|
||||
results = []
|
||||
for candidate in candidates:
|
||||
command_result = run_init_offsetm(
|
||||
mli0=rslc_dir / "mli0",
|
||||
samp=slc_dir / f"{slave_date}_2rlks.amp",
|
||||
diff0=rslc_dir / "diff0",
|
||||
output_dir=output_dir / "diff_par",
|
||||
patch_size=patch_size,
|
||||
rpos=int(candidate["rpos"]),
|
||||
azpos=int(candidate["azpos"]),
|
||||
)
|
||||
row = dict(candidate)
|
||||
row.update(command_result)
|
||||
results.append(row)
|
||||
|
||||
success_count = sum(1 for item in results if item["ok"])
|
||||
best_ratio = max((float(item["patch_nonzero_ratio"]) for item in results), default=0.0)
|
||||
best_zero = min((int(item.get("zero_count", 10**18)) for item in results if "zero_count" in item), default=None)
|
||||
payload = {
|
||||
"case": case_name,
|
||||
"slave_date": slave_date,
|
||||
"shape": {"width": shape.width, "lines": shape.lines},
|
||||
"patch_size": int(patch_size),
|
||||
"stride": int(stride),
|
||||
"candidate_count": len(results),
|
||||
"success_count": int(success_count),
|
||||
"best_patch_nonzero_ratio": best_ratio,
|
||||
"best_zero_count": best_zero,
|
||||
"results": results,
|
||||
}
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
(output_dir / "scan_summary.json").write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
return payload
|
||||
|
||||
|
||||
def write_summary_tsv(path: Path, summaries: list[dict[str, Any]]) -> None:
|
||||
header = [
|
||||
"case",
|
||||
"slave_date",
|
||||
"rank",
|
||||
"is_center",
|
||||
"rpos",
|
||||
"azpos",
|
||||
"patch_nonzero_ratio",
|
||||
"returncode",
|
||||
"ok",
|
||||
"zero_count",
|
||||
"zero_threshold",
|
||||
]
|
||||
lines = ["\t".join(header)]
|
||||
for summary in summaries:
|
||||
for row in summary["results"]:
|
||||
values: list[str] = []
|
||||
for key in header:
|
||||
if key in {"case", "slave_date"}:
|
||||
value = summary[key]
|
||||
else:
|
||||
value = row.get(key)
|
||||
if isinstance(value, float):
|
||||
values.append(f"{value:.6f}")
|
||||
elif value is None:
|
||||
values.append("")
|
||||
else:
|
||||
values.append(str(value))
|
||||
lines.append("\t".join(values))
|
||||
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
run_root = Path(args.run_root).resolve()
|
||||
case_names = tuple(args.cases) if args.cases else ("case_A_baseline", "case_C_precise_orbit_rewrite")
|
||||
slave_dates = tuple(args.dates) if args.dates else ("20230624", "20230920")
|
||||
output_root = run_root / "scan_init_offsetm_patch"
|
||||
output_root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if not shutil.which("init_offsetm"):
|
||||
raise RuntimeError("init_offsetm is not available in PATH")
|
||||
|
||||
summaries = []
|
||||
for case_name in case_names:
|
||||
for slave_date in slave_dates:
|
||||
summaries.append(
|
||||
scan_case_date(
|
||||
run_root=run_root,
|
||||
project=args.project,
|
||||
case_name=case_name,
|
||||
slave_date=slave_date,
|
||||
patch_size=int(args.patch_size),
|
||||
stride=int(args.stride),
|
||||
max_candidates=int(args.max_candidates),
|
||||
output_root=output_root,
|
||||
)
|
||||
)
|
||||
|
||||
(output_root / "scan_summary.json").write_text(
|
||||
json.dumps({"run_root": str(run_root), "summaries": summaries}, ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
write_summary_tsv(output_root / "scan_summary.tsv", summaries)
|
||||
print(json.dumps({"run_root": str(run_root), "output_root": str(output_root), "scan_count": len(summaries)}, ensure_ascii=False))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user