void
TrackerNodePrivate::computeCornerParamsFromTracksEnd(double refTime,
                                                     double maxFittingError,
                                                     const QList<CornerPinData>& results)
{
    // Make sure we get only valid results
    QList<CornerPinData> validResults;
    for (QList<CornerPinData>::const_iterator it = results.begin(); it != results.end(); ++it) {
        if (it->valid) {
            validResults.push_back(*it);
        }
    }

    // Get all knobs that we are going to write to and block any value changes on them
    KnobIntPtr smoothCornerPinKnob = smoothCornerPin.lock();
    int smoothJitter = smoothCornerPinKnob->getValue();
    int halfJitter = smoothJitter / 2;
    KnobDoublePtr fittingErrorKnob = fittingError.lock();
    KnobDoublePtr fromPointsKnob[4];
    KnobDoublePtr toPointsKnob[4];
    KnobBoolPtr enabledPointsKnob[4];
    KnobStringPtr fittingWarningKnob = fittingErrorWarning.lock();

    for (int i = 0; i < 4; ++i) {
        fromPointsKnob[i] = fromPoints[i].lock();
        toPointsKnob[i] = toPoints[i].lock();
        enabledPointsKnob[i] = enableToPoint[i].lock();
    }

    std::list<KnobIPtr> animatedKnobsChanged;

    fittingErrorKnob->blockValueChanges();
    animatedKnobsChanged.push_back(fittingErrorKnob);

    for (int i = 0; i < 4; ++i) {
        toPointsKnob[i]->blockValueChanges();
        animatedKnobsChanged.push_back(toPointsKnob[i]);
    }

    // Get reference corner pin
    CornerPinPoints refFrom;
    for (int c = 0; c < 4; ++c) {
        refFrom.pts[c].x = fromPointsKnob[c]->getValueAtTime(refTime);
        refFrom.pts[c].y = fromPointsKnob[c]->getValueAtTime(refTime, DimIdx(1));
    }

    // Create temporary curves and clone the toPoint internal curves at once because setValueAtTime will be slow since it emits
    // signals to create keyframes in keyframeSet
    Curve tmpToPointsCurveX[4], tmpToPointsCurveY[4];
    Curve tmpFittingErrorCurve;
    bool mustShowFittingWarn = false;
    for (QList<CornerPinData>::const_iterator itResults = validResults.begin(); itResults != validResults.end(); ++itResults) {
        const CornerPinData& dataAtTime = *itResults;

        // Add the error to the curve and check if we need to turn on the RMS warning
        {
            KeyFrame kf(dataAtTime.time, dataAtTime.rms);
            if (dataAtTime.rms >= maxFittingError) {
                mustShowFittingWarn = true;
            }
            tmpFittingErrorCurve.addKeyFrame(kf);
        }


        if (smoothJitter <= 1) {
            for (int c = 0; c < 4; ++c) {
                Point toPoint;
                toPoint = TrackerHelper::applyHomography(refFrom.pts[c], dataAtTime.h);
                KeyFrame kx(dataAtTime.time, toPoint.x);
                KeyFrame ky(dataAtTime.time, toPoint.y);
                tmpToPointsCurveX[c].addKeyFrame(kx);
                tmpToPointsCurveY[c].addKeyFrame(ky);
                //toPoints[c]->setValuesAtTime(dataAtTime[i].time, toPoint.x, toPoint.y, ViewSpec::all(), eValueChangedReasonNatronInternalEdited);
            }
        } else {
            // Average to points before and after if using jitter
            CornerPinPoints avgTos;
            averageDataFunctor<QList<CornerPinData>::const_iterator, CornerPinPoints, CornerPinPoints>(validResults.begin(), validResults.end(), itResults, halfJitter, &refFrom, &avgTos, 0);

            for (int c = 0; c < 4; ++c) {
                KeyFrame kx(dataAtTime.time, avgTos.pts[c].x);
                KeyFrame ky(dataAtTime.time, avgTos.pts[c].y);
                tmpToPointsCurveX[c].addKeyFrame(kx);
                tmpToPointsCurveY[c].addKeyFrame(ky);
            }


        } // use jitter

    } // for each result



    // If user wants a post-smooth, apply it
    if (smoothJitter > 1) {

        int halfSmoothJitter = smoothJitter / 2;


        KeyFrameSet xSet[4], ySet[4];
        KeyFrameSet newXSet[4], newYSet[4];
        for (int c = 0; c < 4; ++c) {
            xSet[c] = tmpToPointsCurveX[c].getKeyFrames_mt_safe();
            ySet[c] = tmpToPointsCurveY[c].getKeyFrames_mt_safe();
        }
        for (int c = 0; c < 4; ++c) {

            for (KeyFrameSet::const_iterator it = xSet[c].begin(); it != xSet[c].end(); ++it) {
                double avg;
                averageDataFunctor<KeyFrameSet::const_iterator, void, double>(xSet[c].begin(), xSet[c].end(), it, halfSmoothJitter, 0, &avg, 0);
                KeyFrame k(*it);
                k.setValue(avg);
                newXSet[c].insert(k);
            }
            for (KeyFrameSet::const_iterator it = ySet[c].begin(); it != ySet[c].end(); ++it) {
                double avg;
                averageDataFunctor<KeyFrameSet::const_iterator, void, double>(ySet[c].begin(), ySet[c].end(), it, halfSmoothJitter, 0, &avg, 0);
                KeyFrame k(*it);
                k.setValue(avg);
                newYSet[c].insert(k);
            }

        }

        for (int c = 0; c < 4; ++c) {
            tmpToPointsCurveX[c].setKeyframes(newXSet[c], true);
            tmpToPointsCurveY[c].setKeyframes(newYSet[c], true);
        }
    }

    fittingWarningKnob->setSecret(!mustShowFittingWarn);
    fittingErrorKnob->cloneCurve(ViewIdx(0), DimIdx(0), tmpFittingErrorCurve, 0 /*offset*/, 0 /*range*/, 0 /*stringAnim*/);
    for (int c = 0; c < 4; ++c) {
        toPointsKnob[c]->cloneCurve(ViewIdx(0), DimIdx(0), tmpToPointsCurveX[c], 0 /*offset*/, 0 /*range*/, 0 /*stringAnim*/);
        toPointsKnob[c]->cloneCurve(ViewIdx(0), DimIdx(1), tmpToPointsCurveY[c], 0 /*offset*/, 0 /*range*/, 0 /*stringAnim*/);
    }
    for (std::list<KnobIPtr>::iterator it = animatedKnobsChanged.begin(); it != animatedKnobsChanged.end(); ++it) {
        (*it)->unblockValueChanges();
        (*it)->evaluateValueChange(DimSpec::all(), refTime, ViewSetSpec::all(), eValueChangedReasonNatronInternalEdited);
    }

    endSolve();
} // TrackerNodePrivate::computeCornerParamsFromTracksEnd