Files
guadaloop_lev_control/A0Calibration/generate_lut.py
2026-04-16 15:22:54 -05:00

91 lines
3.1 KiB
Python

#!/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()