1 #include "calibration/common/sphere_fit_calibration.h"
2
3 #include <errno.h>
4 #include <stdarg.h>
5 #include <stdio.h>
6 #include <string.h>
7
8 #include "calibration/util/cal_log.h"
9 #include "common/math/mat.h"
10 #include "common/math/vec.h"
11
12 // FORWARD DECLARATIONS
13 ///////////////////////////////////////////////////////////////////////////////
14 // Utility for converting solver state to a calibration data structure.
15 static void convertStateToCalStruct(const float x[SF_STATE_DIM],
16 struct ThreeAxisCalData *calstruct);
17
18 static bool runCalibration(struct SphereFitCal *sphere_cal,
19 const struct SphereFitData *data,
20 uint64_t timestamp_nanos);
21
22 #define MIN_VALID_DATA_NORM (1e-4)
23
24 // FUNCTION IMPLEMENTATIONS
25 //////////////////////////////////////////////////////////////////////////////
sphereFitInit(struct SphereFitCal * sphere_cal,const struct LmParams * lm_params,const size_t min_num_points_for_cal)26 void sphereFitInit(struct SphereFitCal *sphere_cal,
27 const struct LmParams *lm_params,
28 const size_t min_num_points_for_cal) {
29 ASSERT_NOT_NULL(sphere_cal);
30 ASSERT_NOT_NULL(lm_params);
31
32 // Initialize LM solver.
33 lmSolverInit(&sphere_cal->lm_solver, lm_params,
34 &sphereFitResidAndJacobianFunc);
35
36 // Reset other parameters.
37 sphereFitReset(sphere_cal);
38
39 // Set num points for calibration, checking that it is above min.
40 if (min_num_points_for_cal < MIN_NUM_SPHERE_FIT_POINTS) {
41 sphere_cal->min_points_for_cal = MIN_NUM_SPHERE_FIT_POINTS;
42 } else {
43 sphere_cal->min_points_for_cal = min_num_points_for_cal;
44 }
45 }
46
sphereFitReset(struct SphereFitCal * sphere_cal)47 void sphereFitReset(struct SphereFitCal *sphere_cal) {
48 ASSERT_NOT_NULL(sphere_cal);
49
50 // Set state to default (diagonal scale matrix and zero offset).
51 memset(&sphere_cal->x0[0], 0, sizeof(float) * SF_STATE_DIM);
52 sphere_cal->x0[eParamScaleMatrix11] = 1.f;
53 sphere_cal->x0[eParamScaleMatrix22] = 1.f;
54 sphere_cal->x0[eParamScaleMatrix33] = 1.f;
55 memcpy(sphere_cal->x, sphere_cal->x0, sizeof(sphere_cal->x));
56
57 // Reset time.
58 sphere_cal->estimate_time_nanos = 0;
59 }
60
sphereFitSetSolverData(struct SphereFitCal * sphere_cal,struct LmData * lm_data)61 void sphereFitSetSolverData(struct SphereFitCal *sphere_cal,
62 struct LmData *lm_data) {
63 ASSERT_NOT_NULL(sphere_cal);
64 ASSERT_NOT_NULL(lm_data);
65
66 // Set solver data.
67 lmSolverSetData(&sphere_cal->lm_solver, lm_data);
68 }
69
sphereFitRunCal(struct SphereFitCal * sphere_cal,const struct SphereFitData * data,uint64_t timestamp_nanos)70 bool sphereFitRunCal(struct SphereFitCal *sphere_cal,
71 const struct SphereFitData *data,
72 uint64_t timestamp_nanos) {
73 ASSERT_NOT_NULL(sphere_cal);
74 ASSERT_NOT_NULL(data);
75
76 // Run calibration if have enough points.
77 if (data->num_fit_points >= sphere_cal->min_points_for_cal) {
78 return runCalibration(sphere_cal, data, timestamp_nanos);
79 }
80
81 return false;
82 }
83
sphereFitSetInitialBias(struct SphereFitCal * sphere_cal,const float initial_bias[THREE_AXIS_DIM])84 void sphereFitSetInitialBias(struct SphereFitCal *sphere_cal,
85 const float initial_bias[THREE_AXIS_DIM]) {
86 ASSERT_NOT_NULL(sphere_cal);
87 sphere_cal->x0[eParamOffset1] = initial_bias[0];
88 sphere_cal->x0[eParamOffset2] = initial_bias[1];
89 sphere_cal->x0[eParamOffset3] = initial_bias[2];
90 }
91
sphereFitGetLatestCal(const struct SphereFitCal * sphere_cal,struct ThreeAxisCalData * cal_data)92 void sphereFitGetLatestCal(const struct SphereFitCal *sphere_cal,
93 struct ThreeAxisCalData *cal_data) {
94 ASSERT_NOT_NULL(sphere_cal);
95 ASSERT_NOT_NULL(cal_data);
96 convertStateToCalStruct(sphere_cal->x, cal_data);
97 cal_data->calibration_time_nanos = sphere_cal->estimate_time_nanos;
98 }
99
sphereFitResidAndJacobianFunc(const float * state,const void * f_data,float * residual,float * jacobian)100 void sphereFitResidAndJacobianFunc(const float *state, const void *f_data,
101 float *residual, float *jacobian) {
102 ASSERT_NOT_NULL(state);
103 ASSERT_NOT_NULL(f_data);
104 ASSERT_NOT_NULL(residual);
105
106 const struct SphereFitData *data = (const struct SphereFitData*)f_data;
107
108 // Verify that expected norm is non-zero, else use default of 1.0.
109 float expected_norm = 1.0;
110 ASSERT(data->expected_norm > MIN_VALID_DATA_NORM);
111 if (data->expected_norm > MIN_VALID_DATA_NORM) {
112 expected_norm = data->expected_norm;
113 }
114
115 // Convert parameters to calibration structure.
116 struct ThreeAxisCalData calstruct;
117 convertStateToCalStruct(state, &calstruct);
118
119 // Compute Jacobian helper matrix if Jacobian requested.
120 //
121 // J = d(||M(x_data - bias)|| - expected_norm)/dstate
122 // = (M(x_data - bias) / ||M(x_data - bias)||) * d(M(x_data - bias))/dstate
123 // = x_corr / ||x_corr|| * A
124 // A = d(M(x_data - bias))/dstate
125 // = [dy/dM11, dy/dM21, dy/dM22, dy/dM31, dy/dM32, dy/dM3,...
126 // dy/db1, dy/db2, dy/db3]'
127 // where:
128 // dy/dM11 = [x_data[0] - bias[0], 0, 0]
129 // dy/dM21 = [0, x_data[0] - bias[0], 0]
130 // dy/dM22 = [0, x_data[1] - bias[1], 0]
131 // dy/dM31 = [0, 0, x_data[0] - bias[0]]
132 // dy/dM32 = [0, 0, x_data[1] - bias[1]]
133 // dy/dM33 = [0, 0, x_data[2] - bias[2]]
134 // dy/db1 = [-scale_factor_x, 0, 0]
135 // dy/db2 = [0, -scale_factor_y, 0]
136 // dy/db3 = [0, 0, -scale_factor_z]
137 float A[SF_STATE_DIM * THREE_AXIS_DIM];
138 if (jacobian) {
139 memset(jacobian, 0, sizeof(float) * SF_STATE_DIM * data->num_fit_points);
140 memset(A, 0, sizeof(A));
141 A[0 * SF_STATE_DIM + eParamOffset1] = -calstruct.scale_factor_x;
142 A[1 * SF_STATE_DIM + eParamOffset2] = -calstruct.scale_factor_y;
143 A[2 * SF_STATE_DIM + eParamOffset3] = -calstruct.scale_factor_z;
144 }
145
146 // Loop over all data points to compute residual and Jacobian.
147 // TODO(dvitus): Use fit_data_std when available to weight residuals.
148 float x_corr[THREE_AXIS_DIM];
149 float x_bias_corr[THREE_AXIS_DIM];
150 size_t i;
151 for (i = 0; i < data->num_fit_points; ++i) {
152 const float *x_data = &data->fit_data[i * THREE_AXIS_DIM];
153
154 // Compute corrected sensor data
155 calDataCorrectData(&calstruct, x_data, x_corr);
156
157 // Compute norm of x_corr.
158 const float norm = vecNorm(x_corr, THREE_AXIS_DIM);
159
160 // Compute residual error: f_x = norm - exp_norm
161 residual[i] = norm - data->expected_norm;
162
163 // Compute Jacobian if valid pointer.
164 if (jacobian) {
165 if (norm < MIN_VALID_DATA_NORM) {
166 return;
167 }
168 const float scale = 1.f / norm;
169
170 // Compute bias corrected data.
171 vecSub(x_bias_corr, x_data, calstruct.bias, THREE_AXIS_DIM);
172
173 // Populate non-bias terms for A
174 A[0 + eParamScaleMatrix11] = x_bias_corr[0];
175 A[1 * SF_STATE_DIM + eParamScaleMatrix21] = x_bias_corr[0];
176 A[1 * SF_STATE_DIM + eParamScaleMatrix22] = x_bias_corr[1];
177 A[2 * SF_STATE_DIM + eParamScaleMatrix31] = x_bias_corr[0];
178 A[2 * SF_STATE_DIM + eParamScaleMatrix32] = x_bias_corr[1];
179 A[2 * SF_STATE_DIM + eParamScaleMatrix33] = x_bias_corr[2];
180
181 // Compute J = x_corr / ||x_corr|| * A
182 matTransposeMultiplyVec(&jacobian[i * SF_STATE_DIM], A, x_corr,
183 THREE_AXIS_DIM, SF_STATE_DIM);
184 vecScalarMulInPlace(&jacobian[i * SF_STATE_DIM], scale, SF_STATE_DIM);
185 }
186 }
187 }
188
convertStateToCalStruct(const float x[SF_STATE_DIM],struct ThreeAxisCalData * calstruct)189 void convertStateToCalStruct(const float x[SF_STATE_DIM],
190 struct ThreeAxisCalData *calstruct) {
191 memcpy(&calstruct->bias[0], &x[eParamOffset1],
192 sizeof(float) * THREE_AXIS_DIM);
193 calstruct->scale_factor_x = x[eParamScaleMatrix11];
194 calstruct->skew_yx = x[eParamScaleMatrix21];
195 calstruct->scale_factor_y = x[eParamScaleMatrix22];
196 calstruct->skew_zx = x[eParamScaleMatrix31];
197 calstruct->skew_zy = x[eParamScaleMatrix32];
198 calstruct->scale_factor_z = x[eParamScaleMatrix33];
199 }
200
runCalibration(struct SphereFitCal * sphere_cal,const struct SphereFitData * data,uint64_t timestamp_nanos)201 bool runCalibration(struct SphereFitCal *sphere_cal,
202 const struct SphereFitData *data,
203 uint64_t timestamp_nanos) {
204 float x_sol[SF_STATE_DIM];
205
206 // Run calibration
207 const enum LmStatus status = lmSolverSolve(&sphere_cal->lm_solver,
208 sphere_cal->x0, (void *)data,
209 SF_STATE_DIM, data->num_fit_points,
210 x_sol);
211
212 // Check if solver was successful
213 if (status == RELATIVE_STEP_SUFFICIENTLY_SMALL ||
214 status == GRADIENT_SUFFICIENTLY_SMALL) {
215 // TODO(dvitus): Check quality of new fit before using.
216 // Store new fit.
217 #ifdef SPHERE_FIT_DBG_ENABLED
218 CAL_DEBUG_LOG(
219 "[SPHERE CAL]",
220 "Solution found in %d iterations with status %d.\n",
221 sphere_cal->lm_solver.num_iter, status);
222 CAL_DEBUG_LOG(
223 "[SPHERE CAL]",
224 "Solution:\n {%s%d.%06d [M(1,1)], %s%d.%06d [M(2,1)], "
225 "%s%d.%06d [M(2,2)], \n"
226 "%s%d.%06d [M(3,1)], %s%d.%06d [M(3,2)], %s%d.%06d [M(3,3)], \n"
227 "%s%d.%06d [b(1)], %s%d.%06d [b(2)], %s%d.%06d [b(3)]}.",
228 CAL_ENCODE_FLOAT(x_sol[0], 6), CAL_ENCODE_FLOAT(x_sol[1], 6),
229 CAL_ENCODE_FLOAT(x_sol[2], 6), CAL_ENCODE_FLOAT(x_sol[3], 6),
230 CAL_ENCODE_FLOAT(x_sol[4], 6), CAL_ENCODE_FLOAT(x_sol[5], 6),
231 CAL_ENCODE_FLOAT(x_sol[6], 6), CAL_ENCODE_FLOAT(x_sol[7], 6),
232 CAL_ENCODE_FLOAT(x_sol[8], 6));
233 #endif
234 memcpy(sphere_cal->x, x_sol, sizeof(x_sol));
235 sphere_cal->estimate_time_nanos = timestamp_nanos;
236 return true;
237 } else {
238 #ifdef SPHERE_FIT_DBG_ENABLED
239 CAL_DEBUG_LOG(
240 "[SPHERE CAL]",
241 "Solution failed in %d iterations with status %d.\n",
242 sphere_cal->lm_solver.num_iter, status);
243 #endif
244 }
245
246 return false;
247 }
248