Apply current workspace changes

This commit is contained in:
2026-04-22 01:52:21 +08:00
parent cf34b9a8ed
commit cb518b431f
449 changed files with 92252 additions and 321 deletions
+317
View File
@@ -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())