Program Listing for File ConstraintSearchThread.cpp

Return to documentation for file (lib/SlamSystem/ConstraintSearchThread.cpp)

#include "ConstraintSearchThread.h"

#include <boost/thread/shared_lock_guard.hpp>

#include <g2o/core/robust_kernel_impl.h>

#include "SlamSystem.h"
#include "GlobalMapping/KeyFrameGraph.h"

#include "SlamSystem/OptimizationThread.h"

namespace lsd_slam {

    using active_object::ActiveIdle;


ConstraintSearchThread::ConstraintSearchThread( SlamSystem &system, bool threaded )
    : _system( system ),
        _perf(),
        constraintTracker( new Sim3Tracker( Conf().slamImageSize ) ),
        constraintSE3Tracker(  new SE3Tracker( Conf().slamImageSize )  ),
        // newKF(  nullptr  ),
        // candidateTrackingReference(  new TrackingReference()  ),
        _failedToRetrack( 0 ),
        _thread( threaded ? ActiveIdle::createActiveIdle( std::bind( &ConstraintSearchThread::idleImpl, this ), std::chrono::milliseconds(500)) : NULL )
{
}

ConstraintSearchThread::~ConstraintSearchThread( void )
{
    if( _thread) delete _thread.release();
}


//=== callbacks ====


void ConstraintSearchThread::idleImpl( void )
{
    // bool doneSomething = false;

    {
        std::lock_guard< std::mutex > lock( _system.keyFrameGraph()->keyframesForRetrackMutex );

        if(_system.keyFrameGraph()->keyframesForRetrack.size() > 10) {
            std::deque< KeyFrame::SharedPtr >::iterator toReTrack = _system.keyFrameGraph()->keyframesForRetrack.begin() + (rand() % (_system.keyFrameGraph()->keyframesForRetrack.size()/3));
            KeyFrame::SharedPtr toReTrackFrame( *toReTrack );

            _system.keyFrameGraph()->keyframesForRetrack.erase(toReTrack);
            _system.keyFrameGraph()->keyframesForRetrack.push_back(toReTrackFrame);

            // lock.unlock();

            int found = findConstraintsForNewKeyFrames(toReTrackFrame, false, false, 2.0);
            if(found == 0)
                _failedToRetrack++;
            else
                _failedToRetrack=0;

//          if(_failedToRetrack < (int)_system.keyFrameGraph()->keyframesForRetrack.size() - 5) doIdle();

                // doneSomething = true;
        }
    }

    // If you did something, go again immediately
    // if( doneSomething ) _thread->send( std::bind( &ConstraintSearchThread::callbackIdle, this ) );
}

int ConstraintSearchThread::fullReconstraintSearchImpl( void )
{
    LOG(INFO) << "Searching full map for constraints!";

    int added = 0;
    for( auto &keyframe : _system.keyFrameGraph()->keyframesAll ) {
        if(keyframe->frame()->pose->isInGraph)
            added += findConstraintsForNewKeyFrames(keyframe, false, false, 1.0);
    }

    LOG(INFO) << "Done searching full map! Added " << added << " constraints.";

    // doFullReConstraintTrack = false;
    fullReConstraintSearchComplete.notify();

    return added;
}

void ConstraintSearchThread::checkNewKeyFrameImpl( const KeyFrame::SharedPtr &keyframe  )
{
    {
        Timer timer;

        findConstraintsForNewKeyFrames( keyframe, true, true, 1.0);
        _failedToRetrack=0;

        _perf.findConstraint.update( timer );
    }

    FrameMemory::getInstance().pruneActiveFrames();
}



//=== Meat =====


// void ConstraintSearchThread::constraintSearchThreadLoop()
// {
//  LOG(INFO) << "Started constraint search thread!";
//
//  std::unique_lock<std::mutex> lock(newKeyFrames.mutex());
//  int failedToRetrack = 0;
//
//  while(keepRunning)
//  {
//      if(newKeyFrames().size() == 0)
//      {
//          lock.unlock();
//          keyFrameGraph->keyframesForRetrackMutex.lock();
//          bool doneSomething = false;
//          if(keyFrameGraph->keyframesForRetrack.size() > 10)
//          {
//              std::deque< Frame* >::iterator toReTrack = keyFrameGraph->keyframesForRetrack.begin() + (rand() % (keyFrameGraph->keyframesForRetrack.size()/3));
//              Frame* toReTrackFrame = *toReTrack;
//
//              keyFrameGraph->keyframesForRetrack.erase(toReTrack);
//              keyFrameGraph->keyframesForRetrack.push_back(toReTrackFrame);
//
//              keyFrameGraph->keyframesForRetrackMutex.unlock();
//
//              int found = findConstraintsForNewKeyFrames(toReTrackFrame, false, false, 2.0);
//              if(found == 0)
//                  failedToRetrack++;
//              else
//                  failedToRetrack=0;
//
//              if(failedToRetrack < (int)keyFrameGraph->keyframesForRetrack.size() - 5)
//                  doneSomething = true;
//          }
//          else
//              keyFrameGraph->keyframesForRetrackMutex.unlock();
//
//          lock.lock();
//
//          if(!doneSomething)
//          {
//              LOG_IF(DEBUG, enablePrintDebugInfo && printConstraintSearchInfo) << "nothing to re-track... waiting.";
//              newKeyFrames.cv().wait_for(lock,std::chrono::milliseconds(500));
//
//          }
//      }
//      else
//      {
//          Frame* newKF = newKeyFrames().front();
//          newKeyFrames().pop_front();
//          lock.unlock();
//
//
//          {
//              Timer timer;
//
//              findConstraintsForNewKeyFrames(newKF, true, true, 1.0);
//              failedToRetrack=0;
//
//              _perf.findConstraint.update( timer );
//          }
//
//          FrameMemory::getInstance().pruneActiveFrames();
//          lock.lock();
//      }
//
//
//      if(doFullReConstraintTrack)
//      {
//          lock.unlock();
//          LOG(INFO) << "Optimizing Full Map!";
//
//          int added = 0;
//          for(unsigned int i=0;i<keyFrameGraph->keyframesAll.size();i++)
//          {
//              if(keyFrameGraph->keyframesAll[i]->pose->isInGraph)
//                  added += findConstraintsForNewKeyFrames(keyFrameGraph->keyframesAll[i], false, false, 1.0);
//          }
//
//          LOG(INFO) << "Done optimizing Full Map! Added " << added << " constraints.";
//
//          doFullReConstraintTrack = false;
//
//          lastNumConstraintsAddedOnFullRetrack = added;
//          lock.lock();
//      }
//
//
//
//  }
//
//  LOG(INFO) << "Exited constraint search thread";
// }





int ConstraintSearchThread::findConstraintsForNewKeyFrames(const KeyFrame::SharedPtr &newKeyFrame, bool forceParent, bool useFABMAP, float closeCandidatesTH)
{
    if(!newKeyFrame->frame()->hasTrackingParent()) {
        // {
        //  std::lock_guard<std::mutex> lock( _system.optThread->newConstraintMutex );
            _system.keyFrameGraph()->addKeyFrame(newKeyFrame);
        // }
        _system.optThread()->doNewConstraint();
        return 0;
    }

    if(!forceParent && (newKeyFrame->frame()->lastConstraintTrackedCamToWorld * newKeyFrame->frame()->getCamToWorld().inverse()).log().norm() < 0.01)
        return 0;


    newKeyFrame->frame()->lastConstraintTrackedCamToWorld = newKeyFrame->frame()->getCamToWorld();

    // =============== get all potential candidates and their initial relative pose. =================
    std::vector<KFConstraintStruct*> constraints;
    KeyFrame::SharedPtr fabMapResult(nullptr);
    std::unordered_set< KeyFrame::SharedPtr > candidates = _system.trackableKeyFrameSearch()->findCandidates(newKeyFrame, fabMapResult, useFABMAP, closeCandidatesTH);
    std::map< KeyFrame::SharedPtr, Sim3 > candidateToFrame_initialEstimateMap;


    // erase the ones that are already neighbours.
    for(std::unordered_set<KeyFrame::SharedPtr>::iterator c = candidates.begin(); c != candidates.end();)
    {
        if(newKeyFrame->neighbors.find(*c) != newKeyFrame->neighbors.end())
        {
            LOGF_IF(DEBUG, Conf().print.constraintSearchInfo,"SKIPPING %d on %d cause it already exists as constraint.\n", (*c)->id(), newKeyFrame->id());
            c = candidates.erase(c);
        }
        else
            ++c;
    }

    std::unordered_map<KeyFrame::SharedPtr, int> distancesToNewKeyFrame;
    {
        boost::shared_lock_guard<boost::shared_mutex> lock(_system.poseConsistencyMutex);
        for (auto candidate : candidates)
        {
            Sim3 candidateToFrame_initialEstimate = newKeyFrame->frame()->getCamToWorld().inverse() * candidate->frame()->getCamToWorld();
            candidateToFrame_initialEstimateMap[candidate] = candidateToFrame_initialEstimate;
        }

        if(newKeyFrame->frame()->hasTrackingParent())
            _system.keyFrameGraph()->calculateGraphDistancesToFrame(newKeyFrame->frame()->trackingParent(), distancesToNewKeyFrame);
    }





    // =============== distinguish between close and "far" candidates in Graph =================
    // Do a first check on trackability of close candidates.
    std::unordered_set<KeyFrame::SharedPtr> closeCandidates;
    std::vector<KeyFrame::SharedPtr> farCandidates;

    // will be null if newKeyFrame doesn't have a trackingParent
    KeyFrame::SharedPtr parent( newKeyFrame->frame()->trackingParent() );

    int closeFailed = 0;
    int closeInconsistent = 0;

    SO3 disturbance = SO3::exp(Sophus::Vector3d(0.05,0,0));

    for (auto candidate : candidates)
    {
        if (candidate->id() == newKeyFrame->id())
            continue;
        if(!candidate->pose()->isInGraph)
            continue;
        if(newKeyFrame->frame()->isTrackingParent( candidate ) )
            continue;
        if(candidate->frame()->idxInKeyframes < INITIALIZATION_PHASE_COUNT)
            continue;

        SE3 c2f_init = se3FromSim3(candidateToFrame_initialEstimateMap[candidate].inverse()).inverse();
        c2f_init.so3() = c2f_init.so3() * disturbance;
        SE3 c2f = constraintSE3Tracker->trackFrameOnPermaref(candidate, newKeyFrame->frame(), c2f_init);
        if(!constraintSE3Tracker->trackingWasGood) {closeFailed++; continue;}


        SE3 f2c_init = se3FromSim3(candidateToFrame_initialEstimateMap[candidate]).inverse();
        f2c_init.so3() = disturbance * f2c_init.so3();
        SE3 f2c = constraintSE3Tracker->trackFrameOnPermaref(newKeyFrame, candidate->frame(), f2c_init);
        if(!constraintSE3Tracker->trackingWasGood) {closeFailed++; continue;}

        if((f2c.so3() * c2f.so3()).log().norm() >= 0.09) {closeInconsistent++; continue;}

        closeCandidates.insert(candidate);
    }



    int farFailed = 0;
    int farInconsistent = 0;
    for (auto candidate : candidates)
    {
        if (candidate->id() == newKeyFrame->id())
            continue;
        if(!candidate->pose()->isInGraph)
            continue;
        if(newKeyFrame->frame()->isTrackingParent( candidate ))
            continue;
        if(candidate->frame()->idxInKeyframes < INITIALIZATION_PHASE_COUNT)
            continue;

        if(candidate == fabMapResult)
        {
            farCandidates.push_back(candidate);
            continue;
        }

        if(distancesToNewKeyFrame.at(candidate) < 4)
            continue;

        farCandidates.push_back(candidate);
    }




    int closeAll = closeCandidates.size();
    int farAll = farCandidates.size();

    // erase the ones that we tried already before (close)
    for( auto c = closeCandidates.begin(); c != closeCandidates.end(); )
    {
        if(newKeyFrame->trackingFailed.find(*c) == newKeyFrame->trackingFailed.end())
        {
            ++c;
            continue;
        }
        auto range = newKeyFrame->trackingFailed.equal_range(*c);

        bool skip = false;
        Sim3 f2c = candidateToFrame_initialEstimateMap[*c].inverse();
        for (auto it = range.first; it != range.second; ++it)
        {
            if((f2c * it->second).log().norm() < 0.1)
            {
                skip=true;
                break;
            }
        }

        if(skip)
        {
            LOGF_IF(DEBUG, Conf().print.constraintSearchInfo,
                        "SKIPPING %d on %d (NEAR), cause we already have tried it.\n", (*c)->id(), newKeyFrame->id());
            c = closeCandidates.erase(c);
        }
        else
            ++c;
    }

    // erase the ones that are already neighbours (far)
    for(unsigned int i=0;i<farCandidates.size();i++)
    {
        if(newKeyFrame->trackingFailed.find(farCandidates[i]) == newKeyFrame->trackingFailed.end())
            continue;

        auto range = newKeyFrame->trackingFailed.equal_range(farCandidates[i]);

        bool skip = false;
        for (auto it = range.first; it != range.second; ++it)
        {
            if((it->second).log().norm() < 0.2)
            {
                skip=true;
                break;
            }
        }

        if(skip)
        {
            LOGF_IF(DEBUG, Conf().print.constraintSearchInfo,
                        "SKIPPING %d on %d (FAR), cause we already have tried it.\n", farCandidates[i]->id(), newKeyFrame->id());
            farCandidates[i] = farCandidates.back();
            farCandidates.pop_back();
            i--;
        }
    }



    LOGF_IF(DEBUG, Conf().print.constraintSearchInfo,
                "Final Loop-Closure Candidates: %d / %d close (%d failed, %d inconsistent) + %d / %d far (%d failed, %d inconsistent) = %d\n",
                (int)closeCandidates.size(),closeAll, closeFailed, closeInconsistent,
                (int)farCandidates.size(), farAll, farFailed, farInconsistent,
                (int)closeCandidates.size() + (int)farCandidates.size());



    // =============== limit number of close candidates ===============
    // while too many, remove the one with the highest connectivity.
    while((int)closeCandidates.size() > maxLoopClosureCandidates)
    {
        KeyFrame::SharedPtr worst(nullptr);
        int worstNeighbours = 0;
        for(auto f : closeCandidates)
        {
            int neightboursInCandidates = 0;
            for(auto n : f->neighbors)
                if(closeCandidates.find(n) != closeCandidates.end())
                    neightboursInCandidates++;

            if(neightboursInCandidates > worstNeighbours || worst == 0)
            {
                worst = f;
                worstNeighbours = neightboursInCandidates;
            }
        }

        closeCandidates.erase(worst);
    }


    // =============== limit number of far candidates ===============
    // delete randomly
    int maxNumFarCandidates = (maxLoopClosureCandidates +1) / 2;
    if(maxNumFarCandidates < 5) maxNumFarCandidates = 5;
    while((int)farCandidates.size() > maxNumFarCandidates)
    {
        int toDelete = rand() % farCandidates.size();
        if(farCandidates[toDelete] != fabMapResult)
        {
            farCandidates[toDelete] = farCandidates.back();
            farCandidates.pop_back();
        }
    }



    // =============== TRACK! ===============

    // make tracking reference for newKeyFrame.
    //newKFTrackingReference->importFrame(newKeyFrame->frame());


    for (auto candidate : closeCandidates)
    {
        KFConstraintStruct* e1=0;
        KFConstraintStruct* e2=0;

        testConstraint(
                newKeyFrame,
                candidate, e1, e2,
                candidateToFrame_initialEstimateMap[candidate],
                loopclosureStrictness);

        LOG_IF(DEBUG, Conf().print.constraintSearchInfo) << " CLOSE (" << distancesToNewKeyFrame.at(candidate) << ")";

        if(e1 != 0)
        {
            constraints.push_back(e1);
            constraints.push_back(e2);

            // delete from far candidates if it's in there.
            for(unsigned int k=0;k<farCandidates.size();k++)
            {
                if(farCandidates[k] == candidate)
                {
                    LOGF_IF(DEBUG, Conf().print.constraintSearchInfo,
                        " DELETED %d from far, as close was successful!\n", candidate->id());

                    farCandidates[k] = farCandidates.back();
                    farCandidates.pop_back();
                }
            }
        }
    }


    for (auto candidate : farCandidates)
    {
        KFConstraintStruct* e1=0;
        KFConstraintStruct* e2=0;

        testConstraint(
                newKeyFrame,
                candidate, e1, e2,
                Sim3(),
                loopclosureStrictness);

        LOG_IF(DEBUG, Conf().print.constraintSearchInfo) << " FAR (" << distancesToNewKeyFrame.at(candidate) << ")";

        if(e1 != 0)
        {
            constraints.push_back(e1);
            constraints.push_back(e2);
        }
    }



    if(parent != 0 && forceParent)
    {
        KFConstraintStruct* e1=0;
        KFConstraintStruct* e2=0;
        testConstraint(
                newKeyFrame,
                parent, e1, e2,
                candidateToFrame_initialEstimateMap[parent],
                100);
        LOG_IF(DEBUG, Conf().print.constraintSearchInfo) << " PARENT (0)";

        if(e1 != 0)
        {
            constraints.push_back(e1);
            constraints.push_back(e2);
        }
        else
        {
            float downweightFac = 5;
            const float kernelDelta = 5 * sqrt(6000*loopclosureStrictness) / downweightFac;
            LOG(WARNING) << "warning: reciprocal tracking on new frame failed badly, added odometry edge (Hacky).";

            _system.poseConsistencyMutex.lock_shared();
            constraints.push_back(new KFConstraintStruct());
            constraints.back()->firstFrame = newKeyFrame;
            constraints.back()->secondFrame = newKeyFrame->frame()->trackingParent();
            constraints.back()->secondToFirst = constraints.back()->firstFrame->frame()->getCamToWorld().inverse() * constraints.back()->secondFrame->frame()->getCamToWorld();
            constraints.back()->information  <<
                    0.8098,-0.1507,-0.0557, 0.1211, 0.7657, 0.0120, 0,
                    -0.1507, 2.1724,-0.1103,-1.9279,-0.1182, 0.1943, 0,
                    -0.0557,-0.1103, 0.2643,-0.0021,-0.0657,-0.0028, 0.0304,
                     0.1211,-1.9279,-0.0021, 2.3110, 0.1039,-0.0934, 0.0005,
                     0.7657,-0.1182,-0.0657, 0.1039, 1.0545, 0.0743,-0.0028,
                     0.0120, 0.1943,-0.0028,-0.0934, 0.0743, 0.4511, 0,
                    0,0, 0.0304, 0.0005,-0.0028, 0, 0.0228;
            constraints.back()->information *= (1e9/(downweightFac*downweightFac));

            constraints.back()->robustKernel = new g2o::RobustKernelHuber();
            constraints.back()->robustKernel->setDelta(kernelDelta);

            constraints.back()->meanResidual = 10;
            constraints.back()->meanResidualD = 10;
            constraints.back()->meanResidualP = 10;
            constraints.back()->usage = 0;

            _system.poseConsistencyMutex.unlock_shared();
        }
    }


    // newConstraintMutex.lock();

    //TODO: Surely something should be locked here...

    {
        // std::lock_guard< std::mutex > lock(_system.keyFrameGraph->newConstraintMutex);
        _system.keyFrameGraph()->addKeyFrame(newKeyFrame);
        for(unsigned int i=0;i<constraints.size();i++)
            _system.keyFrameGraph()->insertConstraint(constraints[i]);
    }

    _system.optThread()->doNewConstraint();

    // newKFTrackingReference->invalidate();
    // candidateTrackingReference->invalidate();

    return constraints.size();
}

float ConstraintSearchThread::tryTrackSim3(
        const std::shared_ptr<KeyFrame> &kfA,
        const std::shared_ptr<KeyFrame> &kfB,
        int lvlStart, int lvlEnd,
        bool useSSE,
        Sim3 &AtoB, Sim3 &BtoA,
        KFConstraintStruct* e1, KFConstraintStruct* e2 )
{

    std::shared_ptr<TrackingReference> &A( kfA->trackingReference() );
    std::shared_ptr<TrackingReference> &B( kfB->trackingReference() );


    BtoA = constraintTracker->trackFrameSim3(
            A,
            B->frame,
            BtoA,
            lvlStart,lvlEnd);
    Matrix7x7 BtoAInfo = constraintTracker->lastSim3Hessian;
    float BtoA_meanResidual = constraintTracker->lastResidual;
    float BtoA_meanDResidual = constraintTracker->lastDepthResidual;
    float BtoA_meanPResidual = constraintTracker->lastPhotometricResidual;
    float BtoA_usage = constraintTracker->pointUsage;


    if (constraintTracker->diverged ||
        BtoA.scale() > 1 / Sophus::SophusConstants<sophusType>::epsilon() ||
        BtoA.scale() < Sophus::SophusConstants<sophusType>::epsilon() ||
        BtoAInfo(0,0) == 0 ||
        BtoAInfo(6,6) == 0)
    {
        return 1e20;
    }


    AtoB = constraintTracker->trackFrameSim3(
            B,
            A->frame,
            AtoB,
            lvlStart,lvlEnd);
    Matrix7x7 AtoBInfo = constraintTracker->lastSim3Hessian;
    float AtoB_meanResidual = constraintTracker->lastResidual;
    float AtoB_meanDResidual = constraintTracker->lastDepthResidual;
    float AtoB_meanPResidual = constraintTracker->lastPhotometricResidual;
    float AtoB_usage = constraintTracker->pointUsage;


    if (constraintTracker->diverged ||
        AtoB.scale() > 1 / Sophus::SophusConstants<sophusType>::epsilon() ||
        AtoB.scale() < Sophus::SophusConstants<sophusType>::epsilon() ||
        AtoBInfo(0,0) == 0 ||
        AtoBInfo(6,6) == 0)
    {
        return 1e20;
    }

    // Propagate uncertainty (with d(a * b) / d(b) = Adj_a) and calculate Mahalanobis norm
    Matrix7x7 datimesb_db = AtoB.cast<float>().Adj();
    Matrix7x7 diffHesse = (AtoBInfo.inverse() + datimesb_db * BtoAInfo.inverse() * datimesb_db.transpose()).inverse();
    Vector7 diff = (AtoB * BtoA).log().cast<float>();


    float reciprocalConsistency = (diffHesse * diff).dot(diff);


    if(e1 != 0 && e2 != 0)
    {
        e1->firstFrame = kfA;
        e1->secondFrame = kfB;
        e1->secondToFirst = BtoA;
        e1->information = BtoAInfo.cast<double>();
        e1->meanResidual = BtoA_meanResidual;
        e1->meanResidualD = BtoA_meanDResidual;
        e1->meanResidualP = BtoA_meanPResidual;
        e1->usage = BtoA_usage;

        e2->firstFrame = kfB;
        e2->secondFrame = kfA;
        e2->secondToFirst = AtoB;
        e2->information = AtoBInfo.cast<double>();
        e2->meanResidual = AtoB_meanResidual;
        e2->meanResidualD = AtoB_meanDResidual;
        e2->meanResidualP = AtoB_meanPResidual;
        e2->usage = AtoB_usage;

        e1->reciprocalConsistency = e2->reciprocalConsistency = reciprocalConsistency;
    }

    return reciprocalConsistency;
}


void ConstraintSearchThread::testConstraint(
        const KeyFrame::SharedPtr &keyframe,
        const KeyFrame::SharedPtr &candidate,
        KFConstraintStruct* e1_out, KFConstraintStruct* e2_out,
        Sim3 candidateToFrame_initialEstimate,
        float strictness)
{
//  candidateTrackingReference->importFrame(candidate);

    Sim3 FtoC = candidateToFrame_initialEstimate.inverse(), CtoF = candidateToFrame_initialEstimate;
    Matrix7x7 FtoCInfo, CtoFInfo;

    float err_level3 = tryTrackSim3(keyframe, candidate,    // A = frame; b = candidate
                                                                    SIM3TRACKING_MAX_LEVEL-1, 3,
                                                                    USESSE,
                                                                    FtoC, CtoF);

    if(err_level3 > 3000*strictness)
    {
        // LOGF_IF(DEBUG,Conf().print.constraintSearchInfo,
        //      "FAILED %d -> %d (lvl %d): errs (%.1f / - / -).",
        //      newKFTrackingReference->frameID, candidateTrackingReference->frameID,
        //      3,
        //      sqrtf(err_level3));

        e1_out = e2_out = 0;

        keyframe->trackingFailed.insert(std::pair< KeyFrame::SharedPtr,Sim3>(candidate, candidateToFrame_initialEstimate));
        return;
    }

    float err_level2 = tryTrackSim3(
            keyframe, candidate,
            2, 2,
            USESSE,
            FtoC, CtoF);

    if(err_level2 > 4000*strictness)
    {
        // LOGF_IF(DEBUG,Conf().print.constraintSearchInfo,
        //  "FAILED %d -> %d (lvl %d): errs (%.1f / %.1f / -).",
        //      newKFTrackingReference->frameID, candidateTrackingReference->frameID,
        //      2,
        //      sqrtf(err_level3), sqrtf(err_level2));

        e1_out = e2_out = 0;
        keyframe->trackingFailed.insert(std::pair<KeyFrame::SharedPtr,Sim3>(candidate, candidateToFrame_initialEstimate));
        return;
    }

    e1_out = new KFConstraintStruct();
    e2_out = new KFConstraintStruct();


    float err_level1 = tryTrackSim3(
            keyframe, candidate,
            1, 1,
            USESSE,
            FtoC, CtoF, e1_out, e2_out);

    if(err_level1 > 6000*strictness)
    {
        // LOGF_IF(DEBUG,Conf().print.constraintSearchInfo,
        //      "FAILED %d -> %d (lvl %d): errs (%.1f / %.1f / %.1f).",
        //          newKFTrackingReference->frameID, candidateTrackingReference->frameID,
        //          1,
        //          sqrtf(err_level3), sqrtf(err_level2), sqrtf(err_level1));

        delete e1_out;
        delete e2_out;
        e1_out = e2_out = 0;
        keyframe->trackingFailed.insert(std::pair<KeyFrame::SharedPtr,Sim3>(candidate, candidateToFrame_initialEstimate));
        return;
    }


    // LOGF_IF(DEBUG,Conf().print.constraintSearchInfo,
    //      "ADDED %d -> %d: errs (%.1f / %.1f / %.1f).",
    //      newKFTrackingReference->frameID, candidateTrackingReference->frameID,
    //      sqrtf(err_level3), sqrtf(err_level2), sqrtf(err_level1));


    const float kernelDelta = 5 * sqrt(6000*loopclosureStrictness);
    e1_out->robustKernel = new g2o::RobustKernelHuber();
    e1_out->robustKernel->setDelta(kernelDelta);
    e2_out->robustKernel = new g2o::RobustKernelHuber();
    e2_out->robustKernel->setDelta(kernelDelta);
}


}