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()