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")
35 meta_raw = data[
"meta"].tolist()
36 meta = json.loads(meta_raw)
37 axes_meta = meta.get(
"axes", [])
38 outputs_meta = meta.get(
"outputs", [])
40 axes: list[tuple[str, np.ndarray]] = []
45 raise ValueError(f
"LUT missing axis '{name}'")
46 axes.append((name, np.asarray(data[key], dtype=np.float64)))
48 outputs: list[tuple[str, np.ndarray]] = []
49 for out_name
in outputs_meta:
50 key = f
"data/{out_name}"
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))
58 out_path.parent.mkdir(parents=
True, exist_ok=
True)
59 with open(out_path,
"wb")
as f:
61 f.write(struct.pack(
"<BBB", VERSION, len(axes), len(outputs)))
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())
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())
79 print(f
"[export] Wrote {out_path} ({len(axes)} axes, {len(outputs)} outputs)")
83 parser = argparse.ArgumentParser(description=
"Export controller LUT for FSW")
85 "--input",
"-i", type=Path, required=
True, help=
"Input .npz LUT path"
88 "--output",
"-o", type=Path, required=
True, help=
"Output .bin path"
93 default=Path(__file__).resolve().parents[2],
94 help=
"Project root (for resolving relative paths)",
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