From 8007a8c8ea738313c518ae2e96869e401cd7c265 Mon Sep 17 00:00:00 2001 From: menzel Date: Tue, 25 Aug 2026 12:27:25 +0200 Subject: [PATCH] debug: recover the scan command from the file itself Asked where the scan command can be recovered reliably. It is in the master file: the file writer stores the whole scan metadata under /entry/collection/metadata/bec (file_writer.py:255), including request_inputs, which holds the arguments the scan was actually called with -- arg_bundle, inputs and kwargs. That beats notebooks, logbooks and recollection, because it is written by the component that ran the scan rather than by anyone describing it afterwards. It also settles the open question directly: whether S00473 was 0.65 um or 6.5 um is answerable from the file, not from the email thread. --command prints it and stops, request_inputs first. Co-Authored-By: Claude Opus 5 (1M context) --- bin/debug/scan_artefact_check.py | 60 ++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/bin/debug/scan_artefact_check.py b/bin/debug/scan_artefact_check.py index 83390f67..876db24e 100755 --- a/bin/debug/scan_artefact_check.py +++ b/bin/debug/scan_artefact_check.py @@ -536,7 +536,57 @@ def check_line_motion(signals, grid, fast_axis) -> None: # --------------------------------------------------------------------------- # -def analyse(path: str, do_list: bool, force_lines=None, force_points=None) -> None: +def dump_scan_command(h5: h5py.File) -> None: + """Print what the scan was actually asked to do. + + This is the authoritative record: the file writer stores the whole scan + metadata under /entry/collection/metadata/bec, including `request_inputs`, + which holds the arguments the scan was called with. It beats notebooks, + logbooks and memory because it is written by the thing that ran the scan. + """ + roots = [p for p in ("/entry/collection/metadata/bec", + "/entry/collection/metadata") if p in h5] + if not roots: + print(" no metadata group found -- cannot recover the command from this file") + return + for root in roots[:1]: + print(f" scan request, from {root}:") + shown = 0 + + def show(name, obj): + nonlocal shown + if not isinstance(obj, h5py.Dataset): + return + try: + val = obj[()] + except Exception: # pylint: disable=broad-except + return + if isinstance(val, bytes): + val = val.decode(errors="replace") + if isinstance(val, np.ndarray): + val = val.tolist() + text = str(val) + if len(text) > 300: + text = text[:300] + " ..." + print(f" {name:<46} {text}") + shown += 1 + + # request_inputs first: that is the command itself + for sub in ("request_inputs", "scan_args", "scan_parameters"): + if sub in h5[root]: + h5[f"{root}/{sub}"].visititems(lambda n, o, s=sub: show(f"{s}/{n}", o)) + for key in sorted(h5[root]): + if key in ("request_inputs", "scan_args", "scan_parameters"): + continue + node = h5[f"{root}/{key}"] + if isinstance(node, h5py.Dataset): + show(key, node) + if not shown: + print(" (nothing readable -- run --list to see the raw structure)") + + +def analyse(path: str, do_list: bool, force_lines=None, force_points=None, + show_command: bool = False) -> None: print("=" * 80) print(os.path.basename(path)) print("=" * 80) @@ -547,6 +597,10 @@ def analyse(path: str, do_list: bool, force_lines=None, force_points=None) -> No "frames_per_trigger") if k in meta} print(" metadata:", interesting if interesting else "(none of the expected keys found)") + if show_command: + dump_scan_command(h5) + return + if do_list: # Per-point arrays first: the file is dominated by scalar configuration # entries, and a naive dump buries the data under hundreds of them. @@ -647,6 +701,8 @@ def main(argv=None) -> int: ap.add_argument("--root", default="/sls/x12sa/data/p23080/raw/data", help="data root used with --scan") ap.add_argument("--list", action="store_true", help="dump the file structure and stop") + ap.add_argument("--command", action="store_true", + help="print the scan request recorded in the file, and stop") ap.add_argument("--lines", type=int, help="override number of lines") ap.add_argument("--points", type=int, help="override points per line") args = ap.parse_args(argv) @@ -663,7 +719,7 @@ def main(argv=None) -> int: for path in paths: try: - analyse(path, args.list, args.lines, args.points) + analyse(path, args.list, args.lines, args.points, args.command) except Exception as exc: # pylint: disable=broad-except print(f" FAILED on {path}: {type(exc).__name__}: {exc}") print()