STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
export_lut_for_fsw.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""
3Export controller LUT from .npz to a C++-readable binary format.
4
5The binary format is designed for ControllerLUT.cpp:
6 - Magic "LUTC" (4 bytes)
7 - Version u8 = 1
8 - num_axes u8, num_outputs u8
9 - For each axis: name_len u8, name bytes, grid_len u32, grid (float64 LE)
10 - For each output: name_len u8, name bytes, shape (num_axes × u32), data (float64 LE)
11
12Usage:
13 python -m scripts.controller_lut.export_lut_for_fsw --input output/lut/controller_lut_ddp_small.npz --output output/lut/controller_lut.bin
14"""
15
16from __future__ import annotations
17
18import argparse
19import json
20import struct
21from pathlib import Path
22
23import numpy as np
24
25MAGIC = b"LUTC"
26VERSION = 1
27
28
29def export_lut(npz_path: Path, out_path: Path) -> None:
30 """Export .npz LUT to FSW binary format."""
31 data = np.load(npz_path, allow_pickle=True)
32 if "meta" not in data:
33 raise ValueError(f"LUT file {npz_path} missing required 'meta' entry")
34
35 meta_raw = data["meta"].tolist()
36 meta = json.loads(meta_raw)
37 axes_meta = meta.get("axes", [])
38 outputs_meta = meta.get("outputs", [])
39
40 axes: list[tuple[str, np.ndarray]] = []
41 for ax in axes_meta:
42 name = ax["name"]
43 key = f"axes/{name}"
44 if key not in data:
45 raise ValueError(f"LUT missing axis '{name}'")
46 axes.append((name, np.asarray(data[key], dtype=np.float64)))
47
48 outputs: list[tuple[str, np.ndarray]] = []
49 for out_name in outputs_meta:
50 key = f"data/{out_name}"
51 if key not in data:
52 raise ValueError(f"LUT missing output '{out_name}'")
53 arr = np.asarray(data[key], dtype=np.float64)
54 if arr.shape != tuple(len(ax[1]) for ax in axes):
55 raise ValueError(f"Output '{out_name}' shape {arr.shape} != axes")
56 outputs.append((out_name, arr))
57
58 out_path.parent.mkdir(parents=True, exist_ok=True)
59 with open(out_path, "wb") as f:
60 f.write(MAGIC)
61 f.write(struct.pack("<BBB", VERSION, len(axes), len(outputs)))
62 f.write(b"\x00") # reserved
63
64 for name, grid in axes:
65 name_bytes = name.encode("utf-8")
66 f.write(struct.pack("<B", min(len(name_bytes), 255)))
67 f.write(name_bytes[:255])
68 f.write(struct.pack("<I", len(grid)))
69 f.write(grid.astype(np.float64).tobytes())
70
71 for name, arr in outputs:
72 name_bytes = name.encode("utf-8")
73 f.write(struct.pack("<B", min(len(name_bytes), 255)))
74 f.write(name_bytes[:255])
75 shape = np.array(arr.shape, dtype=np.uint32)
76 f.write(shape.tobytes())
77 f.write(arr.astype(np.float64).tobytes())
78
79 print(f"[export] Wrote {out_path} ({len(axes)} axes, {len(outputs)} outputs)")
80
81
82def main() -> None:
83 parser = argparse.ArgumentParser(description="Export controller LUT for FSW")
84 parser.add_argument(
85 "--input", "-i", type=Path, required=True, help="Input .npz LUT path"
86 )
87 parser.add_argument(
88 "--output", "-o", type=Path, required=True, help="Output .bin path"
89 )
90 parser.add_argument(
91 "--project-root",
92 type=Path,
93 default=Path(__file__).resolve().parents[2],
94 help="Project root (for resolving relative paths)",
95 )
96 args = parser.parse_args()
97 inp = args.input if args.input.is_absolute() else args.project_root / args.input
98 out = args.output if args.output.is_absolute() else args.project_root / args.output
99 export_lut(inp, out)
100
101
102if __name__ == "__main__":
103 main()
#define min(a, b)
Definition Arduino.h:42
None export_lut(Path npz_path, Path out_path)