91 lines
3.1 KiB
Python
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()
|