#!/usr/bin/env python3 """ Generate piecewise-linear LUT arrays for IndSensorLUT.cpp from calibrated xlsx. Reads A0Calibration/data/Sensor{0,1,2,5}.xlsx (columns: mm_val, avg_adc), resamples onto a uniform mm grid over the active region, enforces strict monotonicity, and prints C++ arrays ready to paste into IndSensorLUT.cpp. Usage: python generate_lut.py [--mm-min 4.0] [--mm-max 16.0] [--step 0.1] [--sensors 0,1,2,5] """ import argparse from pathlib import Path import numpy as np import pandas as pd DATA_DIR = Path(__file__).parent / 'data' def build_lut(mm, adc, grid): order = np.argsort(mm) mm, adc = mm[order], adc[order] # Drop duplicate mm (keep mean) so np.interp is well-defined uniq, inv = np.unique(mm, return_inverse=True) if len(uniq) != len(mm): adc = np.array([adc[inv == i].mean() for i in range(len(uniq))]) mm = uniq lut = np.interp(grid, mm, adc) lut_i = np.clip(np.round(lut).astype(int), 0, 65535) # Enforce strict monotonic increasing for i in range(1, len(lut_i)): if lut_i[i] <= lut_i[i - 1]: lut_i[i] = lut_i[i - 1] + 1 return lut_i def format_array(name, values, per_row=10): out = [f'static const uint16_t {name}[{len(values)}] = {{'] for i in range(0, len(values), per_row): row = ', '.join(f'{v:4d}' for v in values[i:i + per_row]) sep = '' if i + per_row >= len(values) else ',' out.append(' ' + row + sep) out.append('};') return '\n'.join(out) def main(): ap = argparse.ArgumentParser() ap.add_argument('--mm-min', type=float, default=4.0) ap.add_argument('--mm-max', type=float, default=16.0) ap.add_argument('--step', type=float, default=0.1) ap.add_argument('--sensors', type=str, default='0,1,2,5') args = ap.parse_args() sensors = [int(s) for s in args.sensors.split(',')] n = int(round((args.mm_max - args.mm_min) / args.step)) + 1 grid = np.round(args.mm_min + np.arange(n) * args.step, 4) print(f'// Active range: {args.mm_min}..{args.mm_max} mm @ {args.step} mm ' f'({n} entries per sensor)') print() for sn in sensors: path = DATA_DIR / f'Sensor{sn}.xlsx' if not path.exists(): print(f'// [skip] {path} not found') continue df = pd.read_excel(path) df = df.dropna(subset=['mm_val', 'avg_adc']) in_range = (df['mm_val'] >= args.mm_min - args.step) & \ (df['mm_val'] <= args.mm_max + args.step) if in_range.sum() < 2: print(f'// [skip] Sensor{sn}: insufficient points in active range') continue lut_i = build_lut(df['mm_val'].to_numpy(), df['avg_adc'].to_numpy(), grid) print(f'// Sensor{sn} — {len(df)} raw points, ' f'ADC {lut_i[0]}..{lut_i[-1]}') print(format_array(f'ind{sn}LUT', lut_i)) print() print('// IndSensorLUT struct initializers:') for sn in sensors: print(f'const IndSensorLUT ind{sn}LUTCal = ' f'{{ ind{sn}LUT, {n}, {args.mm_min}f, {args.step}f }};') if __name__ == '__main__': main()