heave control + plotter
This commit is contained in:
90
A0Calibration/generate_lut.py
Normal file
90
A0Calibration/generate_lut.py
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user