LCOV - code coverage report
Current view: top level - AP_NavEKF3 - AP_NavEKF3_OptFlowFusion.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 378 450 84.0 %
Date: 2026-09-09 11:02:08 Functions: 4 5 80.0 %

          Line data    Source code
       1             : #include <AP_HAL/AP_HAL.h>
       2             : 
       3             : #include "AP_NavEKF3.h"
       4             : 
       5             : #include "AP_NavEKF3_core.h"
       6             : 
       7             : #if EK3_FEATURE_OPTFLOW_FUSION
       8             : 
       9             : #include <GCS_MAVLink/GCS.h>
      10             : #include <AP_DAL/AP_DAL.h>
      11             : 
      12             : /********************************************************
      13             : *                   RESET FUNCTIONS                     *
      14             : ********************************************************/
      15             : 
      16             : /********************************************************
      17             : *                   FUSE MEASURED_DATA                  *
      18             : ********************************************************/
      19             : 
      20             : // select fusion of optical flow measurements
      21    19965274 : void NavEKF3_core::SelectFlowFusion()
      22             : {
      23             :     // Check if the magnetometer has been fused on that time step and the filter is running at faster than 200 Hz
      24             :     // If so, don't fuse measurements on this time step to reduce frame over-runs
      25             :     // Only allow one time slip to prevent high rate magnetometer data preventing fusion of other measurements
      26    19965274 :     if (magFusePerformed && dtIMUavg < 0.005f && !optFlowFusionDelayed) {
      27     1204036 :         optFlowFusionDelayed = true;
      28     1204036 :         return;
      29             :     } else {
      30    18761238 :         optFlowFusionDelayed = false;
      31             :     }
      32             : 
      33    18761238 :     of_elements ofDataDelayed;      // OF data at the fusion time horizon
      34             : 
      35             :     // Check for data at the fusion time horizon
      36    18761238 :     const bool flowDataToFuse = storedOF.recall(ofDataDelayed, imuDataDelayed.time_ms);
      37             : 
      38             :     // Perform Data Checks
      39             :     // Check if the optical flow data is still valid
      40    18761238 :     flowDataValid = ((imuSampleTime_ms - flowValidMeaTime_ms) < 1000);
      41             :     // check is the terrain offset estimate is still valid - if we are using range finder as the main height reference, the ground is assumed to be at 0
      42    18761238 :     gndOffsetValid = ((imuSampleTime_ms - gndHgtValidTime_ms) < 5000) || (activeHgtSource == AP_NavEKF_Source::SourceZ::RANGEFINDER);
      43             :     // Perform tilt check
      44    18761238 :     bool tiltOK = (prevTnb.c.z > frontend->DCM33FlowMin);
      45             :     // Constrain measurements to zero if takeoff is not detected and the height above ground
      46             :     // is insufficient to achieve acceptable focus. This allows the vehicle to be picked up
      47             :     // and carried to test optical flow operation
      48    18761238 :     if (!takeOffDetected && ((terrainState - stateStruct.position.z) < 0.5f)) {
      49     7637644 :         ofDataDelayed.flowRadXYcomp.zero();
      50     7637644 :         ofDataDelayed.flowRadXY.zero();
      51     7637644 :         flowDataValid = true;
      52             :     }
      53             : 
      54             :     // if have valid flow or range measurements, fuse data into a 1-state EKF to estimate terrain height
      55    18761238 :     if (((flowDataToFuse && (frontend->_flowUse == FLOW_USE_TERRAIN)) || rangeDataToFuse) && tiltOK) {
      56             :         // Estimate the terrain offset (runs a one state EKF)
      57      173035 :         EstimateTerrainOffset(ofDataDelayed);
      58             :     }
      59             : 
      60             : #if EK3_FEATURE_OPTFLOW_AGL_KF
      61             :     // Update the IMU-aided AGL KF every IMU step when enabled, regardless of flow/RF data presence.
      62    18761238 :     if (frontend->option_is_enabled(NavEKF3::Option::AglKfForOptflow)) {
      63           0 :         UpdateAglKf();
      64             :     }
      65             : #endif
      66             : 
      67             :     // Fuse optical flow data into the main filter
      68    18761238 :     if (flowDataToFuse && tiltOK) {
      69       19700 :         const bool fuse_optflow = (frontend->_flowUse == FLOW_USE_NAV) && frontend->sources.useVelXYSource(AP_NavEKF_Source::SourceXY::OPTFLOW, core_index);
      70             :         // Set the flow noise used by the fusion processes
      71       19700 :         R_LOS = sq(MAX(frontend->_flowNoise, 0.05f));
      72             :         // Fuse the optical flow X and Y axis data into the main filter sequentially
      73       19700 :         FuseOptFlow(ofDataDelayed, fuse_optflow);
      74             :     }
      75             : }
      76             : 
      77             : /*
      78             : Estimation of terrain offset using a single state EKF
      79             : The filter can fuse motion compensated optical flow rates and range finder measurements
      80             : Equations generated using https://github.com/PX4/ecl/tree/master/EKF/matlab/scripts/Terrain%20Estimator
      81             : */
      82      173035 : void NavEKF3_core::EstimateTerrainOffset(const of_elements &ofDataDelayed)
      83             : {
      84             :     // horizontal velocity squared
      85      173035 :     ftype velHorizSq = sq(stateStruct.velocity.x) + sq(stateStruct.velocity.y);
      86             : 
      87             :     // don't fuse flow data if LOS rate is misaligned, without GPS, or insufficient velocity, as it is poorly observable
      88             :     // don't fuse flow data if it exceeds validity limits
      89             :     // don't update terrain offset if ground is being used as the zero height datum in the main filter
      90      173035 :     bool cantFuseFlowData = ((frontend->_flowUse != FLOW_USE_TERRAIN)
      91       27808 :     || !gpsIsInUse
      92       27370 :     || PV_AidingMode == AID_RELATIVE 
      93       27370 :     || velHorizSq < 25.0f 
      94      189385 :     || (MAX(ofDataDelayed.flowRadXY[0],ofDataDelayed.flowRadXY[1]) > frontend->_maxFlowRate));
      95             : 
      96      173035 :     if ((!rangeDataToFuse && cantFuseFlowData) || (activeHgtSource == AP_NavEKF_Source::SourceZ::RANGEFINDER)) {
      97             :         // skip update
      98        1912 :         inhibitGndState = true;
      99             :     } else {
     100      171123 :         inhibitGndState = false;
     101             : 
     102             :         // propagate ground position state noise each time this is called using the difference in position since the last observations and an RMS gradient assumption
     103             :         // limit distance to prevent intialisation after bad gps causing bad numerical conditioning
     104      171123 :         ftype distanceTravelledSq = sq(stateStruct.position[0] - prevPosN) + sq(stateStruct.position[1] - prevPosE);
     105      171123 :         distanceTravelledSq = MIN(distanceTravelledSq, 100.0f);
     106      171123 :         prevPosN = stateStruct.position[0];
     107      171123 :         prevPosE = stateStruct.position[1];
     108             : 
     109             :         // in addition to a terrain gradient error model, we also have the growth in uncertainty due to the copter's vertical velocity
     110      171123 :         ftype timeLapsed = MIN(0.001f * (imuSampleTime_ms - timeAtLastAuxEKF_ms), 1.0f);
     111      171123 :         ftype Pincrement = (distanceTravelledSq * sq(frontend->_terrGradMax)) + sq(timeLapsed)*P[6][6];
     112      171123 :         Popt += Pincrement;
     113      171123 :         timeAtLastAuxEKF_ms = imuSampleTime_ms;
     114             : 
     115             :         // fuse range finder data
     116      171123 :         if (rangeDataToFuse) {
     117             :             // reset terrain state if rangefinder data not fused for 5 seconds
     118      171123 :             if (imuSampleTime_ms - gndHgtValidTime_ms > 5000) {
     119         132 :                 terrainState = MAX(rangeDataDelayed.rng * prevTnb.c.z, rngOnGnd) + stateStruct.position.z;
     120             :             }
     121             : 
     122             :             // predict range
     123      171123 :             ftype predRngMeas = MAX((terrainState - stateStruct.position[2]),rngOnGnd) / prevTnb.c.z;
     124             :             // Copy required states to local variable names
     125      171123 :             ftype q0 = stateStruct.quat[0]; // quaternion at optical flow measurement time
     126      171123 :             ftype q1 = stateStruct.quat[1]; // quaternion at optical flow measurement time
     127      171123 :             ftype q2 = stateStruct.quat[2]; // quaternion at optical flow measurement time
     128      171123 :             ftype q3 = stateStruct.quat[3]; // quaternion at optical flow measurement time
     129             : 
     130             :             // Set range finder measurement noise variance. TODO make this a function of range and tilt to allow for sensor, alignment and AHRS errors
     131      171123 :             ftype R_RNG = frontend->_rngNoise.get();
     132             : 
     133             :             // calculate Kalman gain
     134      171123 :             ftype SK_RNG = sq(q0) - sq(q1) - sq(q2) + sq(q3);
     135      171123 :             ftype K_RNG = Popt/(SK_RNG*(R_RNG + Popt/sq(SK_RNG)));
     136             : 
     137             :             // Calculate the innovation variance for data logging
     138      171123 :             varInnovRng = (R_RNG + Popt/sq(SK_RNG));
     139             : 
     140             :             // constrain terrain height to be below the vehicle
     141      171123 :             terrainState = MAX(terrainState, stateStruct.position[2] + rngOnGnd);
     142             : 
     143             :             // Calculate the measurement innovation
     144      171123 :             innovRng = predRngMeas - rangeDataDelayed.rng;
     145             : 
     146             :             // calculate the innovation consistency test ratio
     147      171123 :             auxRngTestRatio = sq(innovRng) / (sq(MAX(0.01f * (ftype)frontend->_rngInnovGate, 1.0f)) * varInnovRng);
     148             : 
     149             :             // Check the innovation test ratio and don't fuse if too large
     150      171123 :             if (auxRngTestRatio < 1.0f) {
     151             :                 // correct the state
     152      167425 :                 terrainState -= K_RNG * innovRng;
     153             : 
     154             :                 // constrain the state
     155      167425 :                 terrainState = MAX(terrainState, stateStruct.position[2] + rngOnGnd);
     156             : 
     157             :                 // correct the covariance
     158      167425 :                 Popt = Popt - sq(Popt)/(SK_RNG*(R_RNG + Popt/sq(SK_RNG))*(sq(q0) - sq(q1) - sq(q2) + sq(q3)));
     159             : 
     160             :                 // prevent the state variance from becoming negative
     161      167425 :                 Popt = MAX(Popt,0.0f);
     162             : 
     163             :                 // record the time we last updated the terrain offset state
     164      167425 :                 gndHgtValidTime_ms = imuSampleTime_ms;
     165             :             }
     166             :         }
     167             : 
     168      171123 :         if (!cantFuseFlowData) {
     169             : 
     170             :             Vector3F relVelSensor;          // velocity of sensor relative to ground in sensor axes
     171       16350 :             Vector2F losPred;               // predicted optical flow angular rate measurement
     172       16350 :             ftype q0 = stateStruct.quat[0]; // quaternion at optical flow measurement time
     173       16350 :             ftype q1 = stateStruct.quat[1]; // quaternion at optical flow measurement time
     174       16350 :             ftype q2 = stateStruct.quat[2]; // quaternion at optical flow measurement time
     175       16350 :             ftype q3 = stateStruct.quat[3]; // quaternion at optical flow measurement time
     176             :             ftype K_OPT;
     177             :             ftype H_OPT;
     178             :             Vector2F auxFlowObsInnovVar;
     179             : 
     180             :             // predict range to centre of image
     181       16350 :             ftype flowRngPred = MAX((terrainState - stateStruct.position.z),rngOnGnd) / prevTnb.c.z;
     182             : 
     183             :             // constrain terrain height to be below the vehicle
     184       16350 :             terrainState = MAX(terrainState, stateStruct.position.z + rngOnGnd);
     185             : 
     186             :             // calculate relative velocity in sensor frame
     187       16350 :             relVelSensor = prevTnb*stateStruct.velocity;
     188             : 
     189             :             // divide velocity by range, subtract body rates and apply scale factor to
     190             :             // get predicted sensed angular optical rates relative to X and Y sensor axes
     191       16350 :             losPred.x =   relVelSensor.y / flowRngPred;
     192       16350 :             losPred.y = - relVelSensor.x / flowRngPred;
     193             : 
     194             :             // calculate innovations
     195       16350 :             auxFlowObsInnov = losPred - ofDataDelayed.flowRadXYcomp;
     196             : 
     197             :             // calculate observation jacobians 
     198       16350 :             ftype t2 = q0*q0;
     199       16350 :             ftype t3 = q1*q1;
     200       16350 :             ftype t4 = q2*q2;
     201       16350 :             ftype t5 = q3*q3;
     202       16350 :             ftype t6 = stateStruct.position.z - terrainState;
     203       16350 :             ftype t7 = 1.0f / (t6*t6);
     204       16350 :             ftype t8 = q0*q3*2.0f;
     205       16350 :             ftype t9 = t2-t3-t4+t5;
     206             : 
     207             :             // prevent the state variances from becoming badly conditioned
     208       16350 :             Popt = MAX(Popt,1E-6f);
     209             : 
     210             :             // calculate observation noise variance from parameter
     211       16350 :             ftype flow_noise_variance = sq(MAX(frontend->_flowNoise, 0.05f));
     212             : 
     213             :             // Fuse Y axis data
     214             : 
     215             :             // Calculate observation partial derivative
     216       16350 :             H_OPT = t7*t9*(-stateStruct.velocity.z*(q0*q2*2.0-q1*q3*2.0)+stateStruct.velocity.x*(t2+t3-t4-t5)+stateStruct.velocity.y*(t8+q1*q2*2.0));
     217             : 
     218             :             // calculate innovation variance
     219       16350 :             auxFlowObsInnovVar.y = H_OPT * Popt * H_OPT + flow_noise_variance;
     220             : 
     221             :             // calculate Kalman gain
     222       16350 :             K_OPT = Popt * H_OPT / auxFlowObsInnovVar.y;
     223             : 
     224             :             // calculate the innovation consistency test ratio
     225       16350 :             auxFlowTestRatio.y = sq(auxFlowObsInnov.y) / (sq(MAX(0.01f * (ftype)frontend->_flowInnovGate, 1.0f)) * auxFlowObsInnovVar.y);
     226             : 
     227             :             // don't fuse if optical flow data is outside valid range
     228       16350 :             if (auxFlowTestRatio.y < 1.0f) {
     229             : 
     230             :                 // correct the state
     231       15078 :                 terrainState -= K_OPT * auxFlowObsInnov.y;
     232             : 
     233             :                 // constrain the state
     234       15078 :                 terrainState = MAX(terrainState, stateStruct.position.z + rngOnGnd);
     235             : 
     236             :                 // update intermediate variables used when fusing the X axis
     237       15078 :                 t6 = stateStruct.position.z - terrainState;
     238       15078 :                 t7 = 1.0f / (t6*t6);
     239             : 
     240             :                 // correct the covariance
     241       15078 :                 Popt = Popt - K_OPT * H_OPT * Popt;
     242             : 
     243             :                 // prevent the state variances from becoming badly conditioned
     244       15078 :                 Popt = MAX(Popt,1E-6f);
     245             : 
     246             :                 // record the time we last updated the terrain offset state
     247       15078 :                 gndHgtValidTime_ms = imuSampleTime_ms;
     248             :             }
     249             : 
     250             :             // fuse X axis data
     251       16350 :             H_OPT = -t7*t9*(stateStruct.velocity.z*(q0*q1*2.0+q2*q3*2.0)+stateStruct.velocity.y*(t2-t3+t4-t5)-stateStruct.velocity.x*(t8-q1*q2*2.0));
     252             : 
     253             :             // calculate innovation variances
     254       16350 :             auxFlowObsInnovVar.x = H_OPT * Popt * H_OPT + flow_noise_variance;
     255             : 
     256             :             // calculate Kalman gain
     257       16350 :             K_OPT = Popt * H_OPT / auxFlowObsInnovVar.x;
     258             : 
     259             :             // calculate the innovation consistency test ratio
     260       16350 :             auxFlowTestRatio.x = sq(auxFlowObsInnov.x) / (sq(MAX(0.01f * (ftype)frontend->_flowInnovGate, 1.0f)) * auxFlowObsInnovVar.x);
     261             : 
     262             :             // don't fuse if optical flow data is outside valid range
     263       16350 :             if (auxFlowTestRatio.x < 1.0f) {
     264             : 
     265             :                 // correct the state
     266       16350 :                 terrainState -= K_OPT * auxFlowObsInnov.x;
     267             : 
     268             :                 // constrain the state
     269       16350 :                 terrainState = MAX(terrainState, stateStruct.position.z + rngOnGnd);
     270             : 
     271             :                 // correct the covariance
     272       16350 :                 Popt = Popt - K_OPT * H_OPT * Popt;
     273             : 
     274             :                 // prevent the state variances from becoming badly conditioned
     275       16350 :                 Popt = MAX(Popt,1E-6f);
     276             :             }
     277             :         }
     278             :     }
     279      173035 : }
     280             : 
     281             : /*
     282             :  * Fuse angular motion compensated optical flow rates using explicit algebraic equations generated with Matlab symbolic toolbox.
     283             :  * The script file used to generate these and other equations in this filter can be found here:
     284             :  * https://github.com/PX4/ecl/blob/master/matlab/scripts/Inertial%20Nav%20EKF/GenerateNavFilterEquations.m
     285             :  * Requires a valid terrain height estimate.
     286             :  *
     287             :  * really_fuse should be true to actually fuse into the main filter, false to only calculate variances
     288             : */
     289       19700 : void NavEKF3_core::FuseOptFlow(const of_elements &ofDataDelayed, bool really_fuse)
     290             : {
     291             :     Vector24 H_LOS;
     292             :     Vector2 losPred;
     293             : 
     294             :     // Copy required states to local variable names
     295       19700 :     ftype q0  = stateStruct.quat[0];
     296       19700 :     ftype q1 = stateStruct.quat[1];
     297       19700 :     ftype q2 = stateStruct.quat[2];
     298       19700 :     ftype q3 = stateStruct.quat[3];
     299       19700 :     ftype vn = stateStruct.velocity.x;
     300       19700 :     ftype ve = stateStruct.velocity.y;
     301       19700 :     ftype vd = stateStruct.velocity.z;
     302       19700 :     ftype pd = stateStruct.position.z;
     303             : 
     304             :     // Default is the terrain estimator AGL (terrainState - pd, where pd is the main filter's vertical position)
     305             :     // constrain height above ground to be above range measured on ground
     306       19700 :     ftype heightAboveGndEst = MAX((terrainState - pd), rngOnGnd);
     307             : 
     308             : #if EK3_FEATURE_OPTFLOW_SRTM
     309             :     // if ground offset (aka terrainState) is not valid, use SRTM altitude
     310       19700 :     terrain_srtm_alt_valid = ((imuSampleTime_ms - terrain_srtm_alt_ms) < 5000);
     311       19700 :     if (!gndOffsetValid && terrain_srtm_alt_valid) {
     312           0 :         heightAboveGndEst = MAX((terrain_srtm_alt - pd), rngOnGnd);
     313             :     }
     314             : #endif
     315             : 
     316             : #if EK3_FEATURE_OPTFLOW_AGL_KF
     317             :     // AGL KF override: use the IMU-aided AGL KF estimate when enabled and valid,
     318             :     // instead of terrainState-pd which can drift when the main filter's vertical position
     319             :     // state is unreliable (e.g. poor altitude source, sensor outage, or ground effect).
     320       19700 :     if (frontend->option_is_enabled(NavEKF3::Option::AglKfForOptflow) && aglKfValid) {
     321           0 :         heightAboveGndEst = MAX(aglKfH, rngOnGnd);
     322             :     }
     323             : #endif
     324             : 
     325             :     // calculate range from ground plain to centre of sensor fov assuming flat earth
     326       19700 :     ftype range = constrain_ftype((heightAboveGndEst/prevTnb.c.z),rngOnGnd,1000.0f);
     327             : 
     328             :     // correct range for flow sensor offset body frame position offset
     329             :     // the corrected value is the predicted range from the sensor focal point to the
     330             :     // centre of the image on the ground assuming flat terrain
     331       19700 :     Vector3F posOffsetBody = ofDataDelayed.body_offset - accelPosOffset;
     332       19700 :     if (!posOffsetBody.is_zero()) {
     333           0 :         Vector3F posOffsetEarth = prevTnb.mul_transpose(posOffsetBody);
     334           0 :         range -= posOffsetEarth.z / prevTnb.c.z;
     335             :     }
     336             :     
     337             : #if APM_BUILD_TYPE(APM_BUILD_Rover)
     338             :     // override with user specified height (if given, for rover)
     339           0 :     if (ofDataDelayed.heightOverride > 0) {
     340           0 :         range = ofDataDelayed.heightOverride;
     341             :     }
     342             : #endif
     343             : 
     344             :     // Fuse X and Y axis measurements sequentially assuming observation errors are uncorrelated
     345       59100 :     for (uint8_t obsIndex=0; obsIndex<=1; obsIndex++) { // fuse X axis data first
     346             : 
     347             :         // calculate relative velocity in sensor frame including the relative motion due to rotation
     348       39400 :         const Vector3F relVelSensor = (prevTnb * stateStruct.velocity) + (ofDataDelayed.bodyRadXYZ % posOffsetBody);
     349             : 
     350             :         // divide velocity by range to get predicted angular LOS rates relative to X and Y axes
     351       39400 :         losPred[0] =  relVelSensor.y/range;
     352       39400 :         losPred[1] = -relVelSensor.x/range;
     353             : 
     354             :         // calculate observation jacobians and Kalman gains
     355       39400 :         memset(&H_LOS[0], 0, sizeof(H_LOS));
     356       39400 :         if (obsIndex == 0) {
     357             :             // calculate X axis observation Jacobian
     358       19700 :             ftype t2 = 1.0f / range;
     359       19700 :             H_LOS[0] = t2*(q1*vd*2.0f+q0*ve*2.0f-q3*vn*2.0f);
     360       19700 :             H_LOS[1] = t2*(q0*vd*2.0f-q1*ve*2.0f+q2*vn*2.0f);
     361       19700 :             H_LOS[2] = t2*(q3*vd*2.0f+q2*ve*2.0f+q1*vn*2.0f);
     362       19700 :             H_LOS[3] = -t2*(q2*vd*-2.0f+q3*ve*2.0f+q0*vn*2.0f);
     363       19700 :             H_LOS[4] = -t2*(q0*q3*2.0f-q1*q2*2.0f);
     364       19700 :             H_LOS[5] = t2*(q0*q0-q1*q1+q2*q2-q3*q3);
     365       19700 :             H_LOS[6] = t2*(q0*q1*2.0f+q2*q3*2.0f);
     366             : 
     367             :             // calculate intermediate variables for the X observation innovation variance and Kalman gains
     368       19700 :             ftype t3 = q1*vd*2.0f;
     369       19700 :             ftype t4 = q0*ve*2.0f;
     370       19700 :             ftype t11 = q3*vn*2.0f;
     371       19700 :             ftype t5 = t3+t4-t11;
     372       19700 :             ftype t6 = q0*q3*2.0f;
     373       19700 :             ftype t29 = q1*q2*2.0f;
     374       19700 :             ftype t7 = t6-t29;
     375             :             ftype t8 = q0*q1*2.0f;
     376             :             ftype t9 = q2*q3*2.0f;
     377             :             ftype t10 = t8+t9;
     378       19700 :             ftype t12 = P[0][0]*t2*t5;
     379       19700 :             ftype t13 = q0*vd*2.0f;
     380       19700 :             ftype t14 = q2*vn*2.0f;
     381       19700 :             ftype t28 = q1*ve*2.0f;
     382       19700 :             ftype t15 = t13+t14-t28;
     383       19700 :             ftype t16 = q3*vd*2.0f;
     384       19700 :             ftype t17 = q2*ve*2.0f;
     385       19700 :             ftype t18 = q1*vn*2.0f;
     386       19700 :             ftype t19 = t16+t17+t18;
     387       19700 :             ftype t20 = q3*ve*2.0f;
     388       19700 :             ftype t21 = q0*vn*2.0f;
     389       19700 :             ftype t30 = q2*vd*2.0f;
     390       19700 :             ftype t22 = t20+t21-t30;
     391       19700 :             ftype t23 = q0*q0;
     392       19700 :             ftype t24 = q1*q1;
     393       19700 :             ftype t25 = q2*q2;
     394       19700 :             ftype t26 = q3*q3;
     395       19700 :             ftype t27 = t23-t24+t25-t26;
     396       19700 :             ftype t31 = P[1][1]*t2*t15;
     397       19700 :             ftype t32 = P[6][0]*t2*t10;
     398       19700 :             ftype t33 = P[1][0]*t2*t15;
     399       19700 :             ftype t34 = P[2][0]*t2*t19;
     400       19700 :             ftype t35 = P[5][0]*t2*t27;
     401       19700 :             ftype t79 = P[4][0]*t2*t7;
     402       19700 :             ftype t80 = P[3][0]*t2*t22;
     403       19700 :             ftype t36 = t12+t32+t33+t34+t35-t79-t80;
     404       19700 :             ftype t37 = t2*t5*t36;
     405       19700 :             ftype t38 = P[6][1]*t2*t10;
     406       19700 :             ftype t39 = P[0][1]*t2*t5;
     407       19700 :             ftype t40 = P[2][1]*t2*t19;
     408       19700 :             ftype t41 = P[5][1]*t2*t27;
     409       19700 :             ftype t81 = P[4][1]*t2*t7;
     410       19700 :             ftype t82 = P[3][1]*t2*t22;
     411       19700 :             ftype t42 = t31+t38+t39+t40+t41-t81-t82;
     412       19700 :             ftype t43 = t2*t15*t42;
     413       19700 :             ftype t44 = P[6][2]*t2*t10;
     414       19700 :             ftype t45 = P[0][2]*t2*t5;
     415       19700 :             ftype t46 = P[1][2]*t2*t15;
     416       19700 :             ftype t47 = P[2][2]*t2*t19;
     417       19700 :             ftype t48 = P[5][2]*t2*t27;
     418       19700 :             ftype t83 = P[4][2]*t2*t7;
     419       19700 :             ftype t84 = P[3][2]*t2*t22;
     420       19700 :             ftype t49 = t44+t45+t46+t47+t48-t83-t84;
     421       19700 :             ftype t50 = t2*t19*t49;
     422       19700 :             ftype t51 = P[6][3]*t2*t10;
     423       19700 :             ftype t52 = P[0][3]*t2*t5;
     424       19700 :             ftype t53 = P[1][3]*t2*t15;
     425       19700 :             ftype t54 = P[2][3]*t2*t19;
     426       19700 :             ftype t55 = P[5][3]*t2*t27;
     427       19700 :             ftype t85 = P[4][3]*t2*t7;
     428       19700 :             ftype t86 = P[3][3]*t2*t22;
     429       19700 :             ftype t56 = t51+t52+t53+t54+t55-t85-t86;
     430       19700 :             ftype t57 = P[6][5]*t2*t10;
     431       19700 :             ftype t58 = P[0][5]*t2*t5;
     432       19700 :             ftype t59 = P[1][5]*t2*t15;
     433       19700 :             ftype t60 = P[2][5]*t2*t19;
     434       19700 :             ftype t61 = P[5][5]*t2*t27;
     435       19700 :             ftype t88 = P[4][5]*t2*t7;
     436       19700 :             ftype t89 = P[3][5]*t2*t22;
     437       19700 :             ftype t62 = t57+t58+t59+t60+t61-t88-t89;
     438       19700 :             ftype t63 = t2*t27*t62;
     439       19700 :             ftype t64 = P[6][4]*t2*t10;
     440       19700 :             ftype t65 = P[0][4]*t2*t5;
     441       19700 :             ftype t66 = P[1][4]*t2*t15;
     442       19700 :             ftype t67 = P[2][4]*t2*t19;
     443       19700 :             ftype t68 = P[5][4]*t2*t27;
     444       19700 :             ftype t90 = P[4][4]*t2*t7;
     445       19700 :             ftype t91 = P[3][4]*t2*t22;
     446       19700 :             ftype t69 = t64+t65+t66+t67+t68-t90-t91;
     447       19700 :             ftype t70 = P[6][6]*t2*t10;
     448       19700 :             ftype t71 = P[0][6]*t2*t5;
     449       19700 :             ftype t72 = P[1][6]*t2*t15;
     450       19700 :             ftype t73 = P[2][6]*t2*t19;
     451       19700 :             ftype t74 = P[5][6]*t2*t27;
     452       19700 :             ftype t93 = P[4][6]*t2*t7;
     453       19700 :             ftype t94 = P[3][6]*t2*t22;
     454       19700 :             ftype t75 = t70+t71+t72+t73+t74-t93-t94;
     455       19700 :             ftype t76 = t2*t10*t75;
     456       19700 :             ftype t87 = t2*t22*t56;
     457       19700 :             ftype t92 = t2*t7*t69;
     458       19700 :             ftype t77 = R_LOS+t37+t43+t50+t63+t76-t87-t92;
     459             :             ftype t78;
     460             : 
     461             :             // calculate innovation variance for X axis observation and protect against a badly conditioned calculation
     462       19700 :             if (t77 > R_LOS) {
     463       19700 :                 t78 = 1.0f/t77;
     464       19700 :                 faultStatus.bad_xflow = false;
     465             :             } else {
     466             :                 t77 = R_LOS;
     467             :                 t78 = 1.0f/R_LOS;
     468           0 :                 faultStatus.bad_xflow = true;
     469           0 :                 return;
     470             :             }
     471       19700 :             flowVarInnov[0] = t77;
     472             : 
     473             :             // calculate innovation for X axis observation
     474             :             // flowInnovTime_ms will be updated when Y-axis innovations are calculated
     475       19700 :             flowInnov[0] = losPred[0] - ofDataDelayed.flowRadXYcomp.x;
     476             : 
     477             :             // calculate Kalman gains for X-axis observation
     478       19700 :             Kfusion[0] = t78*(t12-P[0][4]*t2*t7+P[0][1]*t2*t15+P[0][6]*t2*t10+P[0][2]*t2*t19-P[0][3]*t2*t22+P[0][5]*t2*t27);
     479       19700 :             Kfusion[1] = t78*(t31+P[1][0]*t2*t5-P[1][4]*t2*t7+P[1][6]*t2*t10+P[1][2]*t2*t19-P[1][3]*t2*t22+P[1][5]*t2*t27);
     480       19700 :             Kfusion[2] = t78*(t47+P[2][0]*t2*t5-P[2][4]*t2*t7+P[2][1]*t2*t15+P[2][6]*t2*t10-P[2][3]*t2*t22+P[2][5]*t2*t27);
     481       19700 :             Kfusion[3] = t78*(-t86+P[3][0]*t2*t5-P[3][4]*t2*t7+P[3][1]*t2*t15+P[3][6]*t2*t10+P[3][2]*t2*t19+P[3][5]*t2*t27);
     482       19700 :             Kfusion[4] = t78*(-t90+P[4][0]*t2*t5+P[4][1]*t2*t15+P[4][6]*t2*t10+P[4][2]*t2*t19-P[4][3]*t2*t22+P[4][5]*t2*t27);
     483       19700 :             Kfusion[5] = t78*(t61+P[5][0]*t2*t5-P[5][4]*t2*t7+P[5][1]*t2*t15+P[5][6]*t2*t10+P[5][2]*t2*t19-P[5][3]*t2*t22);
     484       19700 :             Kfusion[6] = t78*(t70+P[6][0]*t2*t5-P[6][4]*t2*t7+P[6][1]*t2*t15+P[6][2]*t2*t19-P[6][3]*t2*t22+P[6][5]*t2*t27);
     485             : 
     486             :             // values to calculate in Kfusion (others are set to zero, indices 0-6 ignored)
     487             :             uint32_t kalman_mask = (1<<7) | (1<<8) | (1<<9);
     488             : 
     489       19700 :             if (!inhibitDelAngBiasStates) {
     490             :                 kalman_mask |= (1<<10) | (1<<11) | (1<<12);
     491             :             }
     492             : 
     493       19700 :             if (!inhibitDelVelBiasStates && !badIMUdata) {
     494       78184 :                 for (uint8_t index = 0; index < 3; index++) {
     495       58638 :                     const uint8_t stateIndex = index + 13;
     496       58638 :                     if (!dvelBiasAxisInhibit[index]) {
     497       47295 :                         kalman_mask |= (1<<stateIndex);
     498             :                     }
     499             :                 }
     500             :             }
     501             : 
     502       19700 :             if (!inhibitMagStates) {
     503       11586 :                 kalman_mask |= (1<<16) | (1<<17) | (1<<18) | (1<<19) | (1<<20) | (1<<21);
     504             :             }
     505             : 
     506       19700 :             if (!inhibitWindStates && !treatWindStatesAsTruth) {
     507           0 :                 kalman_mask |= (1<<22) | (1<<23);
     508             :             }
     509             : 
     510      354600 :             for (auto i=7; i<24; i++) { // 0-6 are already computed
     511             :                 ftype res = 0;
     512      334900 :                 if (kalman_mask & (1<<i)) {
     513      234549 :                     res = t78*(P[i][0]*t2*t5-P[i][4]*t2*t7+P[i][1]*t2*t15+P[i][6]*t2*t10+P[i][2]*t2*t19-P[i][3]*t2*t22+P[i][5]*t2*t27);
     514             :                 }
     515      334900 :                 Kfusion[i] = res;
     516             :             }
     517             :         } else {
     518             : 
     519             :             // calculate Y axis observation Jacobian
     520       19700 :             ftype t2 = 1.0f / range;
     521       19700 :             H_LOS[0] = -t2*(q2*vd*-2.0f+q3*ve*2.0f+q0*vn*2.0f);
     522       19700 :             H_LOS[1] = -t2*(q3*vd*2.0f+q2*ve*2.0f+q1*vn*2.0f);
     523       19700 :             H_LOS[2] = t2*(q0*vd*2.0f-q1*ve*2.0f+q2*vn*2.0f);
     524       19700 :             H_LOS[3] = -t2*(q1*vd*2.0f+q0*ve*2.0f-q3*vn*2.0f);
     525       19700 :             H_LOS[4] = -t2*(q0*q0+q1*q1-q2*q2-q3*q3);
     526       19700 :             H_LOS[5] = -t2*(q0*q3*2.0f+q1*q2*2.0f);
     527       19700 :             H_LOS[6] = t2*(q0*q2*2.0f-q1*q3*2.0f);
     528             : 
     529             :             // calculate intermediate variables for the Y observation innovation variance and Kalman gains
     530             :             ftype t3 = q3*ve*2.0f;
     531             :             ftype t4 = q0*vn*2.0f;
     532             :             ftype t11 = q2*vd*2.0f;
     533       19700 :             ftype t5 = t3+t4-t11;
     534             :             ftype t6 = q0*q3*2.0f;
     535             :             ftype t7 = q1*q2*2.0f;
     536             :             ftype t8 = t6+t7;
     537             :             ftype t9 = q0*q2*2.0f;
     538             :             ftype t28 = q1*q3*2.0f;
     539             :             ftype t10 = t9-t28;
     540       19700 :             ftype t12 = P[0][0]*t2*t5;
     541             :             ftype t13 = q3*vd*2.0f;
     542             :             ftype t14 = q2*ve*2.0f;
     543             :             ftype t15 = q1*vn*2.0f;
     544             :             ftype t16 = t13+t14+t15;
     545             :             ftype t17 = q0*vd*2.0f;
     546             :             ftype t18 = q2*vn*2.0f;
     547             :             ftype t29 = q1*ve*2.0f;
     548       19700 :             ftype t19 = t17+t18-t29;
     549             :             ftype t20 = q1*vd*2.0f;
     550             :             ftype t21 = q0*ve*2.0f;
     551             :             ftype t30 = q3*vn*2.0f;
     552             :             ftype t22 = t20+t21-t30;
     553             :             ftype t23 = q0*q0;
     554             :             ftype t24 = q1*q1;
     555             :             ftype t25 = q2*q2;
     556             :             ftype t26 = q3*q3;
     557             :             ftype t27 = t23+t24-t25-t26;
     558       19700 :             ftype t31 = P[1][1]*t2*t16;
     559       19700 :             ftype t32 = P[5][0]*t2*t8;
     560       19700 :             ftype t33 = P[1][0]*t2*t16;
     561       19700 :             ftype t34 = P[3][0]*t2*t22;
     562       19700 :             ftype t35 = P[4][0]*t2*t27;
     563       19700 :             ftype t80 = P[6][0]*t2*t10;
     564       19700 :             ftype t81 = P[2][0]*t2*t19;
     565       19700 :             ftype t36 = t12+t32+t33+t34+t35-t80-t81;
     566       19700 :             ftype t37 = t2*t5*t36;
     567       19700 :             ftype t38 = P[5][1]*t2*t8;
     568       19700 :             ftype t39 = P[0][1]*t2*t5;
     569       19700 :             ftype t40 = P[3][1]*t2*t22;
     570       19700 :             ftype t41 = P[4][1]*t2*t27;
     571       19700 :             ftype t82 = P[6][1]*t2*t10;
     572       19700 :             ftype t83 = P[2][1]*t2*t19;
     573       19700 :             ftype t42 = t31+t38+t39+t40+t41-t82-t83;
     574       19700 :             ftype t43 = t2*t16*t42;
     575       19700 :             ftype t44 = P[5][2]*t2*t8;
     576       19700 :             ftype t45 = P[0][2]*t2*t5;
     577       19700 :             ftype t46 = P[1][2]*t2*t16;
     578       19700 :             ftype t47 = P[3][2]*t2*t22;
     579       19700 :             ftype t48 = P[4][2]*t2*t27;
     580       19700 :             ftype t79 = P[2][2]*t2*t19;
     581       19700 :             ftype t84 = P[6][2]*t2*t10;
     582       19700 :             ftype t49 = t44+t45+t46+t47+t48-t79-t84;
     583       19700 :             ftype t50 = P[5][3]*t2*t8;
     584       19700 :             ftype t51 = P[0][3]*t2*t5;
     585       19700 :             ftype t52 = P[1][3]*t2*t16;
     586       19700 :             ftype t53 = P[3][3]*t2*t22;
     587       19700 :             ftype t54 = P[4][3]*t2*t27;
     588       19700 :             ftype t86 = P[6][3]*t2*t10;
     589       19700 :             ftype t87 = P[2][3]*t2*t19;
     590       19700 :             ftype t55 = t50+t51+t52+t53+t54-t86-t87;
     591       19700 :             ftype t56 = t2*t22*t55;
     592       19700 :             ftype t57 = P[5][4]*t2*t8;
     593       19700 :             ftype t58 = P[0][4]*t2*t5;
     594       19700 :             ftype t59 = P[1][4]*t2*t16;
     595       19700 :             ftype t60 = P[3][4]*t2*t22;
     596       19700 :             ftype t61 = P[4][4]*t2*t27;
     597       19700 :             ftype t88 = P[6][4]*t2*t10;
     598       19700 :             ftype t89 = P[2][4]*t2*t19;
     599       19700 :             ftype t62 = t57+t58+t59+t60+t61-t88-t89;
     600       19700 :             ftype t63 = t2*t27*t62;
     601       19700 :             ftype t64 = P[5][5]*t2*t8;
     602       19700 :             ftype t65 = P[0][5]*t2*t5;
     603       19700 :             ftype t66 = P[1][5]*t2*t16;
     604       19700 :             ftype t67 = P[3][5]*t2*t22;
     605       19700 :             ftype t68 = P[4][5]*t2*t27;
     606       19700 :             ftype t90 = P[6][5]*t2*t10;
     607       19700 :             ftype t91 = P[2][5]*t2*t19;
     608       19700 :             ftype t69 = t64+t65+t66+t67+t68-t90-t91;
     609       19700 :             ftype t70 = t2*t8*t69;
     610       19700 :             ftype t71 = P[5][6]*t2*t8;
     611       19700 :             ftype t72 = P[0][6]*t2*t5;
     612       19700 :             ftype t73 = P[1][6]*t2*t16;
     613       19700 :             ftype t74 = P[3][6]*t2*t22;
     614       19700 :             ftype t75 = P[4][6]*t2*t27;
     615       19700 :             ftype t92 = P[6][6]*t2*t10;
     616       19700 :             ftype t93 = P[2][6]*t2*t19;
     617       19700 :             ftype t76 = t71+t72+t73+t74+t75-t92-t93;
     618       19700 :             ftype t85 = t2*t19*t49;
     619       19700 :             ftype t94 = t2*t10*t76;
     620       19700 :             ftype t77 = R_LOS+t37+t43+t56+t63+t70-t85-t94;
     621             :             ftype t78;
     622             : 
     623             :             // calculate innovation variance for Y axis observation and protect against a badly conditioned calculation
     624       19700 :             if (t77 > R_LOS) {
     625       19700 :                 t78 = 1.0f/t77;
     626       19700 :                 faultStatus.bad_yflow = false;
     627             :             } else {
     628             :                 t77 = R_LOS;
     629             :                 t78 = 1.0f/R_LOS;
     630           0 :                 faultStatus.bad_yflow = true;
     631           0 :                 return;
     632             :             }
     633       19700 :             flowVarInnov[1] = t77;
     634             : 
     635             :             // calculate innovation for Y observation
     636       19700 :             flowInnov[1] = losPred[1] - ofDataDelayed.flowRadXYcomp.y;
     637       19700 :             flowInnovTime_ms = dal.millis();
     638             : 
     639             :             // calculate Kalman gains for the Y-axis observation
     640       19700 :             Kfusion[0] = -t78*(t12+P[0][5]*t2*t8-P[0][6]*t2*t10+P[0][1]*t2*t16-P[0][2]*t2*t19+P[0][3]*t2*t22+P[0][4]*t2*t27);
     641       19700 :             Kfusion[1] = -t78*(t31+P[1][0]*t2*t5+P[1][5]*t2*t8-P[1][6]*t2*t10-P[1][2]*t2*t19+P[1][3]*t2*t22+P[1][4]*t2*t27);
     642       19700 :             Kfusion[2] = -t78*(-t79+P[2][0]*t2*t5+P[2][5]*t2*t8-P[2][6]*t2*t10+P[2][1]*t2*t16+P[2][3]*t2*t22+P[2][4]*t2*t27);
     643       19700 :             Kfusion[3] = -t78*(t53+P[3][0]*t2*t5+P[3][5]*t2*t8-P[3][6]*t2*t10+P[3][1]*t2*t16-P[3][2]*t2*t19+P[3][4]*t2*t27);
     644       19700 :             Kfusion[4] = -t78*(t61+P[4][0]*t2*t5+P[4][5]*t2*t8-P[4][6]*t2*t10+P[4][1]*t2*t16-P[4][2]*t2*t19+P[4][3]*t2*t22);
     645       19700 :             Kfusion[5] = -t78*(t64+P[5][0]*t2*t5-P[5][6]*t2*t10+P[5][1]*t2*t16-P[5][2]*t2*t19+P[5][3]*t2*t22+P[5][4]*t2*t27);
     646       19700 :             Kfusion[6] = -t78*(-t92+P[6][0]*t2*t5+P[6][5]*t2*t8+P[6][1]*t2*t16-P[6][2]*t2*t19+P[6][3]*t2*t22+P[6][4]*t2*t27);
     647             : 
     648             :             // values to calculate in Kfusion (others are set to zero, indices 0-6 ignored)
     649             :             uint32_t kalman_mask = (1<<7) | (1<<8) | (1<<9);
     650             : 
     651       19700 :             if (!inhibitDelAngBiasStates) {
     652             :                 kalman_mask |= (1<<10) | (1<<11) | (1<<12);
     653             :             }
     654             : 
     655       19700 :             if (!inhibitDelVelBiasStates && !badIMUdata) {
     656       78184 :                 for (uint8_t index = 0; index < 3; index++) {
     657       58638 :                     const uint8_t stateIndex = index + 13;
     658       58638 :                     if (!dvelBiasAxisInhibit[index]) {
     659       47295 :                         kalman_mask |= (1<<stateIndex);
     660             :                     }
     661             :                 }
     662             :             }
     663             : 
     664       19700 :             if (!inhibitMagStates) {
     665       11586 :                 kalman_mask |= (1<<16) | (1<<17) | (1<<18) | (1<<19) | (1<<20) | (1<<21);
     666             :             }
     667             : 
     668       19700 :             if (!inhibitWindStates && !treatWindStatesAsTruth) {
     669           0 :                 kalman_mask |= (1<<22) | (1<<23);
     670             :             }
     671             : 
     672      354600 :             for (auto i=7; i<24; i++) { // 0-6 are already computed
     673             :                 ftype res = 0;
     674      334900 :                 if (kalman_mask & (1<<i)) {
     675      234549 :                     res = -t78*(P[i][0]*t2*t5+P[i][5]*t2*t8-P[i][6]*t2*t10+P[i][1]*t2*t16-P[i][2]*t2*t19+P[i][3]*t2*t22+P[i][4]*t2*t27);
     676             :                 }
     677      334900 :                 Kfusion[i] = res;
     678             :             }
     679             :         }
     680             : 
     681             :         // calculate the innovation consistency test ratio
     682       39400 :         flowTestRatio[obsIndex] = sq(flowInnov[obsIndex]) / (sq(MAX(0.01f * (ftype)frontend->_flowInnovGate, 1.0f)) * flowVarInnov[obsIndex]);
     683             : 
     684             :         // Check the innovation for consistency and don't fuse if out of bounds or flow is too fast to be reliable
     685       39400 :         if (really_fuse && (flowTestRatio[obsIndex]) < 1.0f && (ofDataDelayed.flowRadXY.x < frontend->_maxFlowRate) && (ofDataDelayed.flowRadXY.y < frontend->_maxFlowRate)) {
     686             :             // record the last time observations were accepted for fusion
     687       15138 :             prevFlowFuseTime_ms = imuSampleTime_ms;
     688             :             // notify first time only
     689       15138 :             if (!flowFusionActive) {
     690          18 :                 flowFusionActive = true;
     691          18 :                 GCS_SEND_TEXT(MAV_SEVERITY_INFO, "EKF3 IMU%u fusing optical flow",(unsigned)imu_index);
     692             :             }
     693             : 
     694             :             // correct the covariance P = (I - K*H)*P = P - K*H*P. take advantage of
     695             :             // the zero elements of H to reduce the number of operations.
     696      286194 :             for (unsigned i = 0; i<=stateIndexLim; i++) {
     697             :                 // j as the inner loop allows the compiler to hoist the KH product
     698             :                 // to save computation, and do the inner indexing more efficiently.
     699     5250096 :                 for (unsigned j = 0; j<=stateIndexLim; j++) {
     700             :                     ftype res = 0;
     701     4979040 :                     res += (Kfusion[i] * H_LOS[0]) * P[0][j];
     702     4979040 :                     res += (Kfusion[i] * H_LOS[1]) * P[1][j];
     703     4979040 :                     res += (Kfusion[i] * H_LOS[2]) * P[2][j];
     704     4979040 :                     res += (Kfusion[i] * H_LOS[3]) * P[3][j];
     705     4979040 :                     res += (Kfusion[i] * H_LOS[4]) * P[4][j];
     706     4979040 :                     res += (Kfusion[i] * H_LOS[5]) * P[5][j];
     707     4979040 :                     res += (Kfusion[i] * H_LOS[6]) * P[6][j];
     708     4979040 :                     KHP[i][j] = res;
     709             :                 }
     710             :             }
     711             : 
     712             :             // finish fusion from KHP and Kfusion
     713       15138 :             if (FinishFusion(flowInnov[obsIndex])) {
     714             :                 // fault, record bad axis
     715           0 :                 if (obsIndex == 0) {
     716           0 :                     faultStatus.bad_xflow = true;
     717             :                 } else if (obsIndex == 1) {
     718           0 :                     faultStatus.bad_yflow = true;
     719             :                 }
     720             : 
     721             :             }
     722             :         }
     723             :     }
     724             : 
     725             :     // store optical flow rates for use in external calibration
     726       19700 :     flowCalSample.timestamp_ms = imuSampleTime_ms;
     727       19700 :     flowCalSample.flowRate.x = ofDataDelayed.flowRadXY.x;
     728       19700 :     flowCalSample.flowRate.y = ofDataDelayed.flowRadXY.y;
     729       19700 :     flowCalSample.bodyRate.x = ofDataDelayed.bodyRadXYZ.x;
     730       19700 :     flowCalSample.bodyRate.y = ofDataDelayed.bodyRadXYZ.y;
     731       19700 :     flowCalSample.losPred.x = losPred[0];
     732       19700 :     flowCalSample.losPred.y = losPred[1];
     733             : }
     734             : 
     735             : // retrieve latest corrected optical flow samples (used for calibration)
     736       11051 : bool NavEKF3_core::getOptFlowSample(uint32_t& timestamp_ms, Vector2f& flowRate, Vector2f& bodyRate, Vector2f& losPred) const
     737             : {
     738       11051 :     if (flowCalSample.timestamp_ms != 0) {
     739       11051 :         timestamp_ms = flowCalSample.timestamp_ms;
     740       11051 :         flowRate = flowCalSample.flowRate;
     741       11051 :         bodyRate = flowCalSample.bodyRate;
     742       11051 :         losPred = flowCalSample.losPred;
     743       11051 :         return true;
     744             :     }
     745             :     return false;
     746             : }
     747             : 
     748             : /********************************************************
     749             : *                   MISC FUNCTIONS                      *
     750             : ********************************************************/
     751             : 
     752             : #if EK3_FEATURE_OPTFLOW_AGL_KF
     753             : /*
     754             :  * 2-state IMU-aided AGL Kalman filter
     755             :  *
     756             :  * State:       x  = [h_agl (m), v_agl (m/s)]'   (AGL is "up")
     757             :  * Transition:  F  = [[1, imuDt], [0, 1]]
     758             :  * Input:       u  = -velDotNED.z * imuDt             (gravity-included accel)
     759             :  * Noise:       Qvel = sq(EK3_ACC_P_NSE * imuDt)
     760             :  *              Qhgt = sq(EK3_TERR_GRAD) * horizDist²
     761             :  *
     762             :  * Prediction:  x(k+1) = F*x(k) + [0, u]'
     763             :  *              P(k+1) = F*P*F' + Q
     764             :  *
     765             :  * Observation: z = rng * prevTnb.c.z,  H = [1, 0],  R = sq(EK3_RNG_M_NSE)
     766             :  * Update:      innov = z - H*x,  innovVar = H*P*H' + R
     767             :  *              K = P*H' / innovVar
     768             :  *              x += K * innov
     769             :  *              P  = (I-KH)*P*(I-KH)' + K*R*K'
     770             :  */
     771           0 : void NavEKF3_core::UpdateAglKf()
     772             : {
     773           0 :     const ftype imuDt = imuDataDelayed.delVelDT;
     774             :     const uint32_t aglKfRngTimeout_ms = 5000;   // mark filter invalid / hard-reset after this gap without RF fusion
     775             : 
     776             :     // h_agl(k+1) = h_agl(k) + v_agl(k)*imuDt
     777           0 :     aglKfH += aglKfV * imuDt;
     778             : 
     779             :     // v_agl(k+1) = v_agl(k) - velDotNED.z*imuDt
     780             :     // velDotNED.z is NED-down acceleration (positive = downward, includes gravity).
     781             :     // Negate: downward acceleration reduces AGL rate.
     782           0 :     aglKfV -= velDotNED.z * imuDt;
     783             : 
     784             :     // First-order decay of v_agl toward zero when RF is absent (tau = 2 s).
     785             :     // Without range measurements v_agl is unobservable; accumulated IMU bias
     786             :     // error will cause it to drift, pulling h_agl to the floor during
     787             :     // subsequent climbs.  The decay limits that drift.
     788             :     // At the aglKfRngTimeout_ms validity timeout (5 s), |v| is at most
     789             :     // exp(-5/2) ~ 8% of its value at last RF fusion, so the hard reset finds v near zero.
     790           0 :     if (!rangeDataToFuse) {
     791             :         const ftype tauV = 2.0f;
     792           0 :         aglKfV *= expf(-imuDt / tauV);
     793             :     }
     794             : 
     795             :     // AGL cannot go below the on-ground sensor reading
     796           0 :     aglKfH = MAX(aglKfH, rngOnGnd);
     797             : 
     798             :     // ----- Covariance prediction: P = F*P*F' + Q -----
     799             :     //
     800             :     // F = [[1, imuDt],   state-transition matrix
     801             :     //      [0,  1]]
     802             :     //
     803             :     // Process noise Q:
     804             :     //
     805             :     // Qhgt — terrain-induced AGL uncertainty during forward flight.
     806             :     //   As the vehicle moves horizontally by dist over terrain with unknown gradient "g",
     807             :     //   the true AGL changes by ~g * dist.  Since "g" is unknown, we treat it as zero-mean
     808             :     //   with std-dev terrGradMax, giving variance: Qhgt = terrGradMax² * horizDist²
     809             :     //   horizDist² = (vx²+vy²)*imuDt² is the squared horizontal distance travelled this step.
     810             :     //   Capped at 1 m² so a single large-velocity step can't blow up the covariance.
     811             :     //   The intended effect is that P[0][0] grows quickly during fast horizontal flight over rough terrain,
     812             :     //   allowing the RF measurement to pull h_agl back when the next reading arrives.
     813             :     //
     814             :     // Qvel — unmodelled vertical accelerations (IMU noise, vibration, model error).
     815             :     //   Uses the same accNoise parameter as CovariancePrediction (sq(imuDt*accNoise)),
     816             :     //   so the velocity uncertainty budget is consistent with the main EKF.
     817             :     //   The intended effect is that P[1][1] grows every step when RF is absent, reflecting accumulating IMU integration error in v_agl.
     818             :     //
     819           0 :     const ftype horizDistSq = MIN(sq(stateStruct.velocity.x * imuDt)
     820           0 :                                   + sq(stateStruct.velocity.y * imuDt), 1.0f);  // cap at 1 m²
     821           0 :     const ftype Qvel = sq(frontend->_accNoise * imuDt);   // matches CovariancePrediction: sq(imuDt*accNoise)
     822           0 :     const ftype Qhgt = sq(frontend->_terrGradMax) * horizDistSq;
     823             : 
     824             :     // Capture before overwrite (P is symmetric, so P[0][1] == P[1][0])
     825           0 :     const ftype P00 = aglKfP[0][0];
     826           0 :     const ftype P01 = aglKfP[0][1];   // == P[1][0]
     827           0 :     const ftype P11 = aglKfP[1][1];
     828             : 
     829             :     // Expanded F*P*F' + Q:
     830           0 :     aglKfP[0][0] = P00 + imuDt * (P01 + P01) + sq(imuDt) * P11 + Qhgt;
     831           0 :     aglKfP[0][1] = aglKfP[1][0] = P01 + imuDt * P11;
     832           0 :     aglKfP[1][1] = P11 + Qvel;
     833             : 
     834             :     // Cap covariance to prevent runaway during prolonged RF absence
     835           0 :     aglKfP[0][0] = MIN(aglKfP[0][0], 100.0f);  // 10 m std-dev cap
     836           0 :     aglKfP[1][1] = MIN(aglKfP[1][1], 100.0f);  // 10 m/s std-dev cap
     837             : 
     838             :     // mark invalid if RF has been absent too long
     839           0 :     if (!rangeDataToFuse) {
     840           0 :         if (imuSampleTime_ms - lastAglRngFuseTime_ms > aglKfRngTimeout_ms) {
     841           0 :             aglKfValid = false;
     842             :         }
     843           0 :         return;
     844             :     }
     845             : 
     846             :     // Only fuse when vehicle tilt is within acceptable limits
     847           0 :     if (prevTnb.c.z < frontend->DCM33FlowMin) {
     848             :         return;
     849             :     }
     850             : 
     851             :     // After the timeout of IMU-only propagation, vertical velocity drift makes the
     852             :     // prediction unreliable.  Re-initialise directly from the rangefinder.
     853           0 :     if (imuSampleTime_ms - lastAglRngFuseTime_ms > aglKfRngTimeout_ms) {
     854             :         // Tilt-corrected AGL directly from rangefinder reading
     855           0 :         aglKfH = MAX(rangeDataDelayed.rng * prevTnb.c.z, rngOnGnd);
     856           0 :         aglKfV = 0.0f;                          // assume stationary on reset
     857           0 :         aglKfP[0][0] = sq(frontend->_rngNoise); // initialise h uncertainty to RF noise
     858           0 :         aglKfP[0][1] = aglKfP[1][0] = 0.0f;
     859           0 :         aglKfP[1][1] = 1.0f;                    // 1 m/s velocity uncertainty after reset
     860           0 :         lastAglRngFuseTime_ms = imuSampleTime_ms;
     861           0 :         aglKfValid = true;
     862           0 :         return;  // skip measurement update this cycle (just used the reading for reset)
     863             :     }
     864             : 
     865             :     // Measurement update — fuse tilt-corrected rangefinder reading
     866             :     //
     867             :     // Observation model: z = h_agl,  H = [1, 0]
     868             :     //   z_meas = rng * cos(tilt) = rng * prevTnb.c.z
     869             :     //
     870           0 :     const ftype hgtMeas = MAX(rangeDataDelayed.rng * prevTnb.c.z, rngOnGnd);
     871             : 
     872             :     // Measurement noise variance R (reuse EK3_RNG_M_NSE)
     873           0 :     const ftype measNoiseVar = sq(frontend->_rngNoise);
     874             : 
     875             :     // Innovation and innovation covariance
     876             :     // hgtInnov = hgtMeas - H*x = hgtMeas - h_agl
     877             :     // innovVar = H*P*H' + R    = P[0][0] + R
     878           0 :     const ftype hgtInnov = hgtMeas - aglKfH;
     879           0 :     const ftype innovVar = aglKfP[0][0] + measNoiseVar;
     880             : 
     881             :     // reject outliers (RF glitches, specular reflections, etc.)
     882             :     // gate is expressed as a multiplier on the 1-sigma bound.
     883           0 :     const ftype innovGate = MAX(0.01f * (ftype)frontend->_rngInnovGate, 1.0f);
     884           0 :     if (sq(hgtInnov) > sq(innovGate) * innovVar) {
     885             :         // Innovation too large, likely a glitch.  Inflate both height and velocity
     886             :         // uncertainty so the next valid reading can correct both states more aggressively.
     887           0 :         aglKfP[0][0] = MIN(aglKfP[0][0] * 2.0f, 100.0f);
     888           0 :         aglKfP[1][1] = MIN(aglKfP[1][1] * 2.0f, 100.0f);
     889           0 :         return;
     890             :     }
     891             : 
     892             :     // Kalman gain:  K = P*H' / innovVar = [P[0][0]/innovVar, P[1][0]/innovVar]'
     893             :     // (H = [1, 0], so P*H' = first column of P)
     894           0 :     const ftype Kh = aglKfP[0][0] / innovVar;
     895           0 :     const ftype Kv = aglKfP[1][0] / innovVar;
     896             : 
     897             :     // State update:  x += K * hgtInnov
     898           0 :     aglKfH += Kh * hgtInnov;
     899           0 :     aglKfV += Kv * hgtInnov;
     900           0 :     aglKfH  = MAX(aglKfH, rngOnGnd);  // enforce physical constraint after update
     901             : 
     902             :     // Covariance update P = (I-KH)*P*(I-KH)' + K*R*K'
     903             :     // With H = [1, 0], (I-KH) = [[1-Kh, 0], [-Kv, 1]]:
     904             :     //   P[0][0] = (1-Kh)²*Phh + Kh²*R
     905             :     //   P[0][1] = (1-Kh)*(Phv - Kv*Phh) + Kh*Kv*R
     906             :     //   P[1][1] = Pvv - 2*Kv*Phv + Kv²*innovVar
     907           0 :     const ftype oneMinusKh = 1.0f - Kh;
     908           0 :     const ftype Phh = aglKfP[0][0];
     909           0 :     const ftype Phv = aglKfP[0][1];
     910           0 :     const ftype Pvv = aglKfP[1][1];
     911             : 
     912           0 :     aglKfP[0][0] = MAX(sq(oneMinusKh) * Phh + sq(Kh) * measNoiseVar, 0.0f);
     913           0 :     aglKfP[0][1] = aglKfP[1][0] = oneMinusKh * (Phv - Kv * Phh) + Kh * Kv * measNoiseVar;
     914           0 :     aglKfP[1][1] = MAX(Pvv - 2.0f * Kv * Phv + sq(Kv) * innovVar, 0.0f);
     915             : 
     916           0 :     lastAglRngFuseTime_ms = imuSampleTime_ms;
     917           0 :     aglKfValid = true;
     918             : }
     919             : 
     920             : #endif  // EK3_FEATURE_OPTFLOW_AGL_KF
     921             : 
     922             : #endif  //  EK3_FEATURE_OPTFLOW_FUSION

Generated by: LCOV version 1.14