Improve orbit corruption diagnostics

This commit is contained in:
2026-04-16 00:06:46 +08:00
parent 428802eac0
commit 5af296cc2d
4 changed files with 276 additions and 72 deletions
+101 -20
View File
@@ -161,25 +161,55 @@ def _inspect_orbit_txt_health(path: str) -> Dict[str, Any]:
"contains_nul_bytes": False,
"nul_byte_count": 0,
"tail_has_nul_bytes": False,
"first_nul_offset": -1,
}
try:
nul_byte_count = 0
tail = b""
offset = 0
first_nul_offset = -1
with open(path, "rb") as stream:
while True:
chunk = stream.read(1024 * 1024)
if not chunk:
break
if first_nul_offset < 0:
chunk_nul_index = chunk.find(b"\x00")
if chunk_nul_index >= 0:
first_nul_offset = offset + chunk_nul_index
nul_byte_count += chunk.count(b"\x00")
tail = (tail + chunk)[-4096:]
offset += len(chunk)
info["nul_byte_count"] = nul_byte_count
info["contains_nul_bytes"] = nul_byte_count > 0
info["tail_has_nul_bytes"] = b"\x00" in tail
info["first_nul_offset"] = first_nul_offset
except OSError as exc:
info["read_error"] = str(exc)
return info
def _truncate_message(text: str, limit: int = 1200) -> str:
value = str(text or "").replace("\x00", "[NUL]").strip()
if len(value) <= limit:
return value
return value[:limit] + "...<truncated>"
def _compact_error_message(text: str, limit: int = 1200) -> str:
value = str(text or "").replace("\x00", "[NUL]").strip()
if not value:
return ""
lines = [line.strip() for line in value.splitlines() if line.strip()]
if len(lines) > 1:
for line in reversed(lines):
if re.match(r"^[A-Za-z_][\w.]*:\s", line):
return _truncate_message(line, limit=limit)
return _truncate_message(value, limit=limit)
def _convert_to_isce2_xml_file(txt_path: str, xml_path: str) -> None:
if "isce2" not in _CONVERTERS:
raise KeyError("未注册 isce2 轨道转换器")
@@ -209,16 +239,57 @@ def _build_invalid_source_record(
record: Dict[str, Any] = {
"name": stem,
"source": txt_path,
"error": str(error),
"error": _compact_error_message(error),
}
if envi_path:
record["envi_path"] = envi_path
if isce2_path:
record["isce2_path"] = isce2_path
record.update(_inspect_orbit_txt_health(txt_path))
record["has_corruption_signal"] = _has_orbit_corruption_signal(record)
return record
def _has_orbit_corruption_signal(item: Dict[str, Any]) -> bool:
first_nul_offset = item.get("first_nul_offset", -1)
return bool(
item.get("read_error")
or item.get("contains_nul_bytes")
or item.get("tail_has_nul_bytes")
or int(item.get("nul_byte_count") or 0) > 0
or (
isinstance(first_nul_offset, (int, float))
and int(first_nul_offset) >= 0
)
)
def _build_source_gap_sample(
stem: str,
source_entry: Optional[Dict[str, Any]] = None,
envi_entry: Optional[Dict[str, Any]] = None,
isce2_entry: Optional[Dict[str, Any]] = None,
inspect_source: bool = False,
) -> Dict[str, Any]:
source_path = (source_entry or {}).get("path", "")
sample: Dict[str, Any] = {
"name": stem,
"source": source_path,
"source_path": source_path,
"envi_path": (envi_entry or {}).get("path", ""),
"isce2_path": (isce2_entry or {}).get("path", ""),
"has_corruption_signal": False,
}
if inspect_source and source_path:
sample.update(_inspect_orbit_txt_health(source_path))
sample["has_corruption_signal"] = _has_orbit_corruption_signal(sample)
if sample.get("read_error"):
sample["error"] = f"Source health scan failed: {sample['read_error']}"
elif sample["has_corruption_signal"]:
sample["error"] = "Source TXT contains NUL bytes and appears corrupted"
return sample
def validate_orbit_source(txt_path: str, stem: str = "", scratch_root: str = "") -> Dict[str, Any]:
name = stem or os.path.splitext(os.path.basename(txt_path))[0]
try:
@@ -259,28 +330,42 @@ def summarize_source_orbit_gaps(
source_without_envi = sorted(source_stems - envi_stems)
envi_without_source = sorted(envi_stems - source_stems)
isce2_without_source = sorted(isce2_stems - source_stems)
sample_limit = 20
def _make_samples(stems: List[str]) -> List[Dict[str, str]]:
samples: List[Dict[str, str]] = []
for stem in stems[:20]:
def _make_samples(
stems: List[str],
inspect_source: bool = False,
) -> List[Dict[str, Any]]:
samples: List[Dict[str, Any]] = []
for stem in stems[:sample_limit]:
samples.append(
{
"name": stem,
"source_path": source_files.get(stem, {}).get("path", ""),
"envi_path": envi_files.get(stem, {}).get("path", ""),
"isce2_path": isce2_files.get(stem, {}).get("path", ""),
}
_build_source_gap_sample(
stem,
source_files.get(stem),
envi_files.get(stem),
isce2_files.get(stem),
inspect_source=inspect_source,
)
)
return samples
suspect_bad_samples = _make_samples(source_without_isce2, inspect_source=True)
bad_source_samples = [
item for item in suspect_bad_samples
if item.get("has_corruption_signal")
]
return {
"sample_limit": sample_limit,
"quarantine_path": _default_quarantine_root(source_dir, quarantine_root),
"source_without_isce2_count": len(source_without_isce2),
"source_without_envi_count": len(source_without_envi),
"envi_without_source_count": len(envi_without_source),
"isce2_without_source_count": len(isce2_without_source),
"suspect_bad_count": len(source_without_isce2),
"suspect_bad_samples": _make_samples(source_without_isce2),
"suspect_bad_samples": suspect_bad_samples,
"bad_source_sample_count": len(bad_source_samples),
"bad_source_samples": bad_source_samples,
"source_without_envi_samples": _make_samples(source_without_envi),
"envi_without_source_samples": _make_samples(envi_without_source),
"isce2_without_source_samples": _make_samples(isce2_without_source),
@@ -558,7 +643,8 @@ def _convert_to_isce2_xml(
timeout=60,
)
if result.returncode != 0:
raise RuntimeError(result.stderr or result.stdout or "Orbit conversion failed without output")
detail = result.stderr or result.stdout or "Orbit conversion failed without output"
raise RuntimeError(_compact_error_message(detail))
# ---------------------------------------------------------------------------
@@ -857,14 +943,9 @@ def repair_orbit_pools(
_convert_to_isce2_xml_file(txt_path, xml_path)
repaired_from_envi.append(stem)
except Exception as exc:
repair_errors.append(
{
"name": stem,
"source": txt_path,
"target": xml_path,
"error": str(exc),
}
)
item = _build_invalid_source_record(stem, txt_path, exc, envi_path=txt_path, isce2_path=xml_path)
item["target"] = xml_path
repair_errors.append(item)
after = check_orbit_consistency(envi_pool, isce2_pool)
return {