Home | History | Annotate | Download | only in common
      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 //////////////////////////////////////////////////////////////////////////////
     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 
     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 
     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 
     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 
     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 
     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 
    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 
    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 
    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