/*M///////////////////////////////////////////////////////////////////////////////////////
 //
 //  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
 //
 //  By downloading, copying, installing or using the software you agree to this license.
 //  If you do not agree to this license, do not download, install,
 //  copy or use the software.
 //
 //
 //                           License Agreement
 //                For Open Source Computer Vision Library
 //
 // Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
 // Copyright (C) 2009, Willow Garage Inc., all rights reserved.
 // Third party copyrights are property of their respective owners.
 //
 // Redistribution and use in source and binary forms, with or without modification,
 // are permitted provided that the following conditions are met:
 //
 //   * Redistribution's of source code must retain the above copyright notice,
 //     this list of conditions and the following disclaimer.
 //
 //   * Redistribution's in binary form must reproduce the above copyright notice,
 //     this list of conditions and the following disclaimer in the documentation
 //     and/or other materials provided with the distribution.
 //
 //   * The name of the copyright holders may not be used to endorse or promote products
 //     derived from this software without specific prior written permission.
 //
 // This software is provided by the copyright holders and contributors "as is" and
 // any express or implied warranties, including, but not limited to, the implied
 // warranties of merchantability and fitness for a particular purpose are disclaimed.
 // In no event shall the Intel Corporation or contributors be liable for any direct,
 // indirect, incidental, special, exemplary, or consequential damages
 // (including, but not limited to, procurement of substitute goods or services;
 // loss of use, data, or profits; or business interruption) however caused
 // and on any theory of liability, whether in contract, strict liability,
 // or tort (including negligence or otherwise) arising in any way out of
 // the use of this software, even if advised of the possibility of such damage.
 //
 //M*/

#include "precomp.hpp"
#include <opencv2/highgui.hpp>

namespace cv
{
namespace optflow
{

class OpticalFlowDeepFlow: public DenseOpticalFlow
{
public:
    OpticalFlowDeepFlow();

    void calc( InputArray I0, InputArray I1, InputOutputArray flow );
    void collectGarbage();

protected:
    float sigma; // Gaussian smoothing parameter
    int minSize; // minimal dimension of an image in the pyramid
    float downscaleFactor; // scaling factor in the pyramid
    int fixedPointIterations; // during each level of the pyramid
    int sorIterations; // iterations of SOR
    float alpha; // smoothness assumption weight
    float delta; // color constancy weight
    float gamma; // gradient constancy weight
    float omega; // relaxation factor in SOR

    float zeta; // added to the denomimnator of theta_0 (normaliation of the data term)
    float epsilon; // robust penalizer const
    int maxLayers; // max amount of layers in the pyramid

private:
    void calcOneLevel( const Mat I0, const Mat I1, Mat W );
    Mat warpImage( const Mat input, const Mat flow );
    void dataTerm( const Mat W, const Mat dW, const Mat Ix, const Mat Iy, const Mat Iz,
            const Mat Ixx, const Mat Ixy, const Mat Iyy, const Mat Ixz, const Mat Iyz,
            Mat a11, Mat a12, Mat a22, Mat b1, Mat b2 );
    void smoothnessWeights( const Mat W, Mat weightsX, Mat weightsY );
    void smoothnessTerm( const Mat W, const Mat weightsX, const Mat weightsY, Mat b1, Mat b2 );
    void sorSolve( const Mat a11, const Mat a12, const Mat a22, const Mat b1, const Mat b2,
            const Mat smoothX, const Mat smoothY, Mat dW );
    void sorUnfolded( const Mat a11, const Mat a12, const Mat a22, const Mat b1, const Mat b2,
            const Mat smoothX, const Mat smoothY, Mat dW );
    std::vector<Mat> buildPyramid( const Mat& src );

    int interpolationType;

};

OpticalFlowDeepFlow::OpticalFlowDeepFlow()
{
    // parameters
    sigma = 0.6f;
    minSize = 25;
    downscaleFactor = 0.95f;
    fixedPointIterations = 5;
    sorIterations = 25;
    alpha = 1.0f;
    delta = 0.5f;
    gamma = 5.0f;
    omega = 1.6f;

    //consts
    interpolationType = INTER_LINEAR;
    zeta = 0.1f;
    epsilon = 0.001f;
    maxLayers = 200;
}

std::vector<Mat> OpticalFlowDeepFlow::buildPyramid( const Mat& src )
{
    std::vector<Mat> pyramid;
    pyramid.push_back(src);
    Mat prev = pyramid[0];
    int i = 0;
    while ( i < this->maxLayers )
    {
        Mat next; //TODO: filtering at each level?
        Size nextSize((int) (prev.cols * downscaleFactor + 0.5f),
                        (int) (prev.rows * downscaleFactor + 0.5f));
        if( nextSize.height <= minSize || nextSize.width <= minSize)
            break;
        resize(prev, next,
                nextSize, 0, 0,
                interpolationType);
        pyramid.push_back(next);
        prev = next;
    }
    return pyramid;
}
Mat OpticalFlowDeepFlow::warpImage( const Mat input, const Mat flow )
{
    // warps the image "backwards"
    // if flow = computeFlow( I0, I1 ), then
    // I0 = warpImage( I1, flow ) - approx.

    Mat output;
    Mat mapX = Mat(flow.size(), CV_32FC1);
    Mat mapY = Mat(flow.size(), CV_32FC1);
    const float *pFlow;
    float *pMapX, *pMapY;
    for ( int j = 0; j < flow.rows; ++j )
    {
        pFlow = flow.ptr<float>(j);
        pMapX = mapX.ptr<float>(j);
        pMapY = mapY.ptr<float>(j);
        for ( int i = 0; i < flow.cols; ++i )
        {
            pMapX[i] = i + pFlow[2 * i];
            pMapY[i] = j + pFlow[2 * i + 1];
        }
    }
    remap(input, output, mapX, mapY, interpolationType, BORDER_TRANSPARENT);
    return output;
}
void OpticalFlowDeepFlow::calc( InputArray _I0, InputArray _I1, InputOutputArray _flow )
{
    Mat I0temp = _I0.getMat();
    Mat I1temp = _I1.getMat();

    CV_Assert(I0temp.size() == I1temp.size());
    CV_Assert(I0temp.type() == I1temp.type());
    CV_Assert(I0temp.channels() == 1);
    // TODO: currently only grayscale - data term could be computed in color version as well...

    Mat I0, I1;

    I0temp.convertTo(I0, CV_32F);
    I1temp.convertTo(I1, CV_32F);

    _flow.create(I0.size(), CV_32FC2);
    Mat W = _flow.getMat(); // if any data present - will be discarded

    // pre-smooth images
    int kernelLen = ((int)floor(3 * sigma) * 2) + 1;
    Size kernelSize(kernelLen, kernelLen);
    GaussianBlur(I0, I0, kernelSize, sigma);
    GaussianBlur(I1, I1, kernelSize, sigma);
    // build down-sized pyramids
    std::vector<Mat> pyramid_I0 = buildPyramid(I0);
    std::vector<Mat> pyramid_I1 = buildPyramid(I1);
    int levelCount = (int) pyramid_I0.size();

    // initialize the first version of flow estimate to zeros
    Size smallestSize = pyramid_I0[levelCount - 1].size();
    W = Mat::zeros(smallestSize, CV_32FC2);

    for ( int level = levelCount - 1; level >= 0; --level )
    { //iterate through  all levels, beginning with the most coarse
        calcOneLevel(pyramid_I0[level], pyramid_I1[level], W);
        if ( level > 0 ) //not the last level
        {
            Mat temp;
            Size newSize = pyramid_I0[level - 1].size();
            resize(W, temp, newSize, 0, 0, interpolationType); //resize calculated flow
            W = temp * (1.0f / downscaleFactor); //scale values
        }
    }
    W.copyTo(_flow);
}

void OpticalFlowDeepFlow::calcOneLevel( const Mat I0, const Mat I1, Mat W )
{
    CV_DbgAssert( I0.size() == I1.size() );CV_DbgAssert( I0.type() == I1.type() );CV_DbgAssert( W.size() == I0.size() );

    // linear equation systems
    Size s = I0.size();
    int t = CV_32F; // data type
    Mat a11, a12, a22, b1, b2;
    a11.create(s, t);
    a12.create(s, t);
    a22.create(s, t);
    b1.create(s, t);
    b2.create(s, t);
    // diffusivity coeffs
    Mat weightsX, weightsY;
    weightsX.create(s, t);
    weightsY.create(s, t);

    Mat warpedI1 = warpImage(I1, W); // warped second image
    Mat averageFrame = 0.5 * (I0 + warpedI1); // mean value of 2 frames - to compute derivatives on

    //computing derivatives, notation as in Brox's paper
    Mat Ix, Iy, Iz, Ixx, Ixy, Iyy, Ixz, Iyz;
    int ddepth = -1; //as source image
    int kernel_size = 1;

    Sobel(averageFrame, Ix, ddepth, 1, 0, kernel_size, 1, 0.00, BORDER_REPLICATE);
    Sobel(averageFrame, Iy, ddepth, 0, 1, kernel_size, 1, 0.00, BORDER_REPLICATE);
    Iz.create(I1.size(), I1.type());
    Iz = warpedI1 - I0;
    Sobel(Ix, Ixx, ddepth, 1, 0, kernel_size, 1, 0.00, BORDER_REPLICATE);
    Sobel(Ix, Ixy, ddepth, 0, 1, kernel_size, 1, 0.00, BORDER_REPLICATE);
    Sobel(Iy, Iyy, ddepth, 0, 1, kernel_size, 1, 0.00, BORDER_REPLICATE);
    Sobel(Iz, Ixz, ddepth, 1, 0, kernel_size, 1, 0.00, BORDER_REPLICATE);
    Sobel(Iz, Iyz, ddepth, 0, 1, kernel_size, 1, 0.00, BORDER_REPLICATE);

    Mat tempW = W.clone(); // flow version to be modified in each iteration
    Mat dW = Mat::zeros(W.size(), W.type()); // flow increment

    //fixed-point iterations
    for ( int i = 0; i < fixedPointIterations; ++i )
    {
        dataTerm(W, dW, Ix, Iy, Iz, Ixx, Ixy, Iyy, Ixz, Iyz, a11, a12, a22, b1, b2);
        smoothnessWeights(tempW, weightsX, weightsY);
        smoothnessTerm(W, weightsX, weightsY, b1, b2);
        sorSolve(a11, a12, a22, b1, b2, weightsX, weightsY, dW);
        tempW = W + dW;
    }
    tempW.copyTo(W);
}
void OpticalFlowDeepFlow::dataTerm( const Mat W, const Mat dW, const Mat Ix, const Mat Iy,
        const Mat Iz, const Mat Ixx, const Mat Ixy, const Mat Iyy, const Mat Ixz,
        const Mat Iyz, Mat a11, Mat a12, Mat a22, Mat b1, Mat b2 )
{
    const float zeta_squared = zeta * zeta; // added in normalization factor to be non-zero
    const float epsilon_squared = epsilon * epsilon;

    const float *pIx, *pIy, *pIz;
    const float *pIxx, *pIxy, *pIyy, *pIxz, *pIyz;
    const float *pdU, *pdV; // accessing 2 layers of dW. Succesive columns interleave u and v
    float *pa11, *pa12, *pa22, *pb1, *pb2; // linear equation sys. coeffs for each pixel

    float derivNorm; //denominator of the spatial-derivative normalizing factor (theta_0)
    float derivNorm2;
    float Ik1z, Ik1zx, Ik1zy; // approximations of I^(k+1) values by Taylor expansions
    float temp;
    for ( int j = 0; j < W.rows; j++ ) //for each row
    {
        pIx = Ix.ptr<float>(j);
        pIy = Iy.ptr<float>(j);
        pIz = Iz.ptr<float>(j);
        pIxx = Ixx.ptr<float>(j);
        pIxy = Ixy.ptr<float>(j);
        pIyy = Iyy.ptr<float>(j);
        pIxz = Ixz.ptr<float>(j);
        pIyz = Iyz.ptr<float>(j);

        pa11 = a11.ptr<float>(j);
        pa12 = a12.ptr<float>(j);
        pa22 = a22.ptr<float>(j);
        pb1 = b1.ptr<float>(j);
        pb2 = b2.ptr<float>(j);

        pdU = dW.ptr<float>(j);
        pdV = pdU + 1;
        for ( int i = 0; i < W.cols; i++ ) //for each pixel in the row
        { // TODO: implement masking of points warped out of the image
          //color constancy component
            derivNorm = (*pIx) * (*pIx) + (*pIy) * (*pIy) + zeta_squared;
            Ik1z = *pIz + (*pIx * *pdU) + (*pIy * *pdV);
            temp = (0.5f*delta/3) / sqrt(Ik1z * Ik1z / derivNorm + epsilon_squared);
            *pa11 = *pIx * *pIx * temp / derivNorm;
            *pa12 = *pIx * *pIy * temp / derivNorm;
            *pa22 = *pIy * *pIy * temp / derivNorm;
            *pb1 = -*pIz * *pIx * temp / derivNorm;
            *pb2 = -*pIz * *pIy * temp / derivNorm;

            // gradient constancy component

            derivNorm = *pIxx * *pIxx + *pIxy * *pIxy + zeta_squared;
            derivNorm2 = *pIyy * *pIyy + *pIxy * *pIxy + zeta_squared;
            Ik1zx = *pIxz + *pIxx * *pdU + *pIxy * *pdV;
            Ik1zy = *pIyz + *pIxy * *pdU + *pIyy * *pdV;

            temp = (0.5f*gamma/3)
                    / sqrt(
                            Ik1zx * Ik1zx / derivNorm + Ik1zy * Ik1zy / derivNorm2
                                    + epsilon_squared);
            *pa11 += temp * (*pIxx * *pIxx / derivNorm + *pIxy * *pIxy / derivNorm2);
            *pa12 += temp * (*pIxx * *pIxy / derivNorm + *pIxy * *pIyy / derivNorm2);
            *pa22 += temp * (*pIxy * *pIxy / derivNorm + *pIyy * *pIyy / derivNorm2);
            *pb1 += -temp * (*pIxx * *pIxz / derivNorm + *pIxy * *pIyz / derivNorm2);
            *pb2 += -temp * (*pIxy * *pIxz / derivNorm + *pIyy * *pIyz / derivNorm2);

            ++pIx;
            ++pIy;
            ++pIz;
            ++pIxx;
            ++pIxy;
            ++pIyy;
            ++pIxz;
            ++pIyz;
            pdU += 2;
            pdV += 2;
            ++pa11;
            ++pa12;
            ++pa22;
            ++pb1;
            ++pb2;

        }
    }



}
void OpticalFlowDeepFlow::smoothnessWeights( const Mat W, Mat weightsX, Mat weightsY )
{
    float k[] = { -0.5, 0, 0.5 };
    const float epsilon_squared = epsilon * epsilon;
    Mat kernel_h = Mat(1, 3, CV_32FC1, k);
    Mat kernel_v = Mat(3, 1, CV_32FC1, k);
    Mat Wx, Wy; // partial derivatives of the flow
    Mat S = Mat(W.size(), CV_32FC1); // sum of squared derivatives
    weightsX = Mat::zeros(W.size(), CV_32FC1); //output - weights of smoothness terms in x and y directions
    weightsY = Mat::zeros(W.size(), CV_32FC1);

    filter2D(W, Wx, CV_32FC2, kernel_h);
    filter2D(W, Wy, CV_32FC2, kernel_v);

    const float * ux, *uy, *vx, *vy;
    float * pS, *pWeight, *temp;

    for ( int j = 0; j < S.rows; ++j )
    {
        ux = Wx.ptr<float>(j);
        vx = ux + 1;
        uy = Wy.ptr<float>(j);
        vy = uy + 1;
        pS = S.ptr<float>(j);
        for ( int i = 0; i < S.cols; ++i )
        {
            *pS = alpha / sqrt(*ux * *ux + *vx * *vx + *uy * *uy + *vy * *vy + epsilon_squared);
            ux += 2;
            vx += 2;
            uy += 2;
            vy += 2;
            ++pS;
        }
    }
    // horizontal weights
    for ( int j = 0; j < S.rows; ++j )
    {
        pWeight = weightsX.ptr<float>(j);
        pS = S.ptr<float>(j);
        for ( int i = 0; i < S.cols - 1; ++i )
        {
            *pWeight = *pS + *(pS + 1);
            ++pS;
            ++pWeight;
        }
    }
    //vertical weights
    for ( int j = 0; j < S.rows - 1; ++j )
    {
        pWeight = weightsY.ptr<float>(j);
        pS = S.ptr<float>(j);
        temp = S.ptr<float>(j + 1); // next row pointer for easy access
        for ( int i = 0; i < S.cols; ++i )
        {
            *pWeight = *(pS++) + *(temp++);
            ++pWeight;
        }
    }
}
void OpticalFlowDeepFlow::smoothnessTerm( const Mat W, const Mat weightsX, const Mat weightsY,
        Mat b1, Mat b2 )
{
    float *pB1, *pB2;
    const float *pU, *pV, *pWeight;
    float iB1, iB2; // increments of b1 and b2
    //horizontal direction - both U and V (b1 and b2)
    for ( int j = 0; j < W.rows; j++ )
    {
        pB1 = b1.ptr<float>(j);
        pB2 = b2.ptr<float>(j);
        pU = W.ptr<float>(j);
        pV = pU + 1;
        pWeight = weightsX.ptr<float>(j);
        for ( int i = 0; i < W.cols - 1; i++ )
        {
            iB1 = (*(pU + 2) - *pU) * *pWeight;
            iB2 = (*(pV + 2) - *pV) * *pWeight;
            *pB1 += iB1;
            *(pB1 + 1) -= iB1;
            *pB2 += iB2;
            *(pB2 + 1) -= iB2;

            pB1++;
            pB2++;
            pU += 2;
            pV += 2;
            pWeight++;
        }
    }
    const float *pUnext, *pVnext; // temp pointers for next row
    float *pB1next, *pB2next;
    //vertical direction - both U and V
    for ( int j = 0; j < W.rows - 1; j++ )
    {
        pB1 = b1.ptr<float>(j);
        pB2 = b2.ptr<float>(j);
        pU = W.ptr<float>(j);
        pV = pU + 1;
        pUnext = W.ptr<float>(j + 1);
        pVnext = pUnext + 1;
        pB1next = b1.ptr<float>(j + 1);
        pB2next = b2.ptr<float>(j + 1);
        pWeight = weightsY.ptr<float>(j);
        for ( int i = 0; i < W.cols; i++ )
        {
            iB1 = (*pUnext - *pU) * *pWeight;
            iB2 = (*pVnext - *pV) * *pWeight;
            *pB1 += iB1;
            *pB1next -= iB1;
            *pB2 += iB2;
            *pB2next -= iB2;

            pB1++;
            pB2++;
            pU += 2;
            pV += 2;
            pWeight++;
            pUnext += 2;
            pVnext += 2;
            pB1next++;
            pB2next++;
        }
    }
}

void OpticalFlowDeepFlow::sorSolve( const Mat a11, const Mat a12, const Mat a22, const Mat b1,
        const Mat b2, const Mat smoothX, const Mat smoothY, Mat dW )
{
    CV_Assert(a11.isContinuous());
    CV_Assert(a12.isContinuous());
    CV_Assert(a22.isContinuous());
    CV_Assert(b1.isContinuous());
    CV_Assert(b2.isContinuous());
    CV_Assert(smoothX.isContinuous());
    CV_Assert(smoothY.isContinuous());

    if(dW.cols > 2 && dW.rows > 2)
    {
        sorUnfolded(a11, a12, a22, b1, b2, smoothX, smoothY, dW );
        //more efficient version - this one is mostly for future reference and readability
        return;
    }
    std::vector<Mat> dWChannels(2);
    split(dW, dWChannels);

    Mat *du = &(dWChannels[0]);
    Mat *dv = &(dWChannels[1]);

    CV_Assert(du->isContinuous());
    CV_Assert(dv->isContinuous());

    const float *pa11, *pa12, *pa22, *pb1, *pb2, *psmoothX, *psmoothY;
    float *pdu, *pdv;
    psmoothX = smoothX.ptr<float>(0);
    psmoothY = smoothY.ptr<float>(0);
    pdu = du->ptr<float>(0);
    pdv = dv->ptr<float>(0);

    float sigmaU, sigmaV, dPsi, A11, A22, A12, B1, B2, det;

    int cols = dW.cols;
    int rows = dW.rows;

    int s = dW.cols; // step between rows

    for ( int iter = 0; iter < sorIterations; ++iter )
    {
        pa11 = a11.ptr<float>(0);
        pa12 = a12.ptr<float>(0);
        pa22 = a22.ptr<float>(0);
        pb1 = b1.ptr<float>(0);
        pb2 = b2.ptr<float>(0);
        for ( int j = 0; j < rows; ++j )
        {
            for ( int i = 0; i < cols; ++i )
            {
                int o = j * s + i;
                if ( i == 0 && j == 0 )
                {
                    dPsi = psmoothX[o] + psmoothY[o];
                    sigmaU = psmoothX[o] * pdu[o + 1] + psmoothY[o] * pdu[o + s];
                    sigmaV = psmoothX[o] * pdv[o + 1] + psmoothY[o] * pdv[o + s];
                } else if ( i == cols - 1 && j == 0 )
                {
                    dPsi = psmoothX[o - 1] + psmoothY[o];
                    sigmaU = psmoothX[o - 1] * pdu[o - 1]
                            + psmoothY[o] * pdu[o + s];
                    sigmaV = psmoothX[o - 1] * pdv[o - 1]
                            + psmoothY[o] * pdv[o + s];
                } else if ( j == 0 )
                {
                    dPsi = psmoothX[o - 1] + psmoothX[o] + psmoothY[o];
                    sigmaU = psmoothX[o - 1] * pdu[o - 1]
                            + psmoothX[o] * pdu[o + 1] + psmoothY[o] * pdu[o + s];
                    sigmaV = psmoothX[o - 1] * pdv[o - 1]
                            + psmoothX[o] * pdv[o + 1] + psmoothY[o] * pdv[o + s];
                } else if ( i == 0 && j == rows - 1 )
                {
                    dPsi = psmoothX[o] + psmoothY[o - s];
                    sigmaU = psmoothX[o] * pdu[o + 1]
                            + psmoothY[o - s] * pdu[o - s];
                    sigmaV = psmoothX[o] * pdv[o + 1]
                            + psmoothY[o - s] * pdv[o - s];
                } else if ( i == cols - 1 && j == rows - 1 )
                {
                    dPsi = psmoothX[o - 1] + psmoothY[o - s];
                    sigmaU = psmoothX[o - 1] * pdu[o - 1]
                            + psmoothY[o - s] * pdu[o - s];
                    sigmaV = psmoothX[o - 1] * pdv[o - 1]
                            + psmoothY[o - s] * pdv[o - s];
                } else if ( j == rows - 1 )
                {
                    dPsi = psmoothX[o - 1] + psmoothX[o] + psmoothY[o - s];
                    sigmaU = psmoothX[o - 1] * pdu[o - 1]
                            + psmoothX[o] * pdu[o + 1]
                            + psmoothY[o - s] * pdu[o - s];
                    sigmaV = psmoothX[o - 1] * pdv[o - 1]
                            + psmoothX[o] * pdv[o + 1]
                            + psmoothY[o - s] * pdv[o - s];
                } else if ( i == 0 )
                {
                    dPsi = psmoothX[o] + psmoothY[o - s] + psmoothY[o];
                    sigmaU = psmoothX[o] * pdu[o + 1]
                            + psmoothY[o - s] * pdu[o - s]
                            + psmoothY[o] * pdu[o + s];
                    sigmaV = psmoothX[o] * pdv[o + 1]
                            + psmoothY[o - s] * pdv[o - s]
                            + psmoothY[o] * pdv[o + s];
                } else if ( i == cols - 1 )
                {
                    dPsi = psmoothX[o - 1] + psmoothY[o - s] + psmoothY[o];
                    sigmaU = psmoothX[o - 1] * pdu[o - 1]
                            + psmoothY[o - s] * pdu[o - s]
                            + psmoothY[o] * pdu[o + s];
                    sigmaV = psmoothX[o - 1] * pdv[o - 1]
                            + psmoothY[o - s] * pdv[o - s]
                            + psmoothY[o] * pdv[o + s];
                } else
                {
                    dPsi = psmoothX[o - 1] + psmoothX[o] + psmoothY[o - s]
                            + psmoothY[o];
                    sigmaU = psmoothX[o - 1] * pdu[o - 1]
                            + psmoothX[o] * pdu[o + 1]
                            + psmoothY[o - s] * pdu[o - s]
                            + psmoothY[o] * pdu[o + s];
                    sigmaV = psmoothX[o - 1] * pdv[o - 1]
                            + psmoothX[o] * pdv[o + 1]
                            + psmoothY[o - s] * pdv[o - s]
                            + psmoothY[o] * pdv[o + s];
                }
                A11 = *pa22 + dPsi;
                A12 = -*pa12;
                A22 = *pa11 + dPsi;
                det = A11 * A22 - A12 * A12;
                A11 /= det;
                A12 /= det;
                A22 /= det;
                B1 = *pb1 + sigmaU;
                B2 = *pb2 + sigmaV;
                pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
                pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
                ++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
            }
        }
    }
    merge(dWChannels, dW);
}


void OpticalFlowDeepFlow::sorUnfolded( const Mat a11, const Mat a12, const Mat a22, const Mat b1, const Mat b2,
        const Mat smoothX, const Mat smoothY, Mat dW )
{
    // the same effect as sorSolve(), but written more efficiently
    std::vector<Mat> dWChannels(2);
    split(dW, dWChannels);

    Mat *du = &(dWChannels[0]);
    Mat *dv = &(dWChannels[1]);

    CV_Assert(du->isContinuous());
    CV_Assert(dv->isContinuous());

    const float *pa11, *pa12, *pa22, *pb1, *pb2, *psmoothX, *psmoothY;
    float *pdu, *pdv;


    float sigmaU, sigmaV, dPsi, A11, A22, A12, B1, B2, det;

    int cols = dW.cols;
    int rows = dW.rows;

    int s = dW.cols; // step between rows
    int j, i, o; //row, column, offset

    for ( int iter = 0; iter < sorIterations; ++iter )
    {
        pa11 = a11.ptr<float>(0);
        pa12 = a12.ptr<float>(0);
        pa22 = a22.ptr<float>(0);
        pb1 = b1.ptr<float>(0);
        pb2 = b2.ptr<float>(0);
        psmoothX = smoothX.ptr<float>(0);
        psmoothY = smoothY.ptr<float>(0);
        pdu = du->ptr<float>(0);
        pdv = dv->ptr<float>(0);

        // first row
        // first column
        o=0;
        dPsi = psmoothX[o] + psmoothY[o];
        sigmaU = psmoothX[o] * pdu[o + 1] + psmoothY[o] * pdu[o + s];
        sigmaV = psmoothX[o] * pdv[o + 1] + psmoothY[o] * pdv[o + s];
        A11 = *pa22 + dPsi;
        A12 = -*pa12;
        A22 = *pa11 + dPsi;
        det = A11 * A22 - A12 * A12;
        A11 /= det;
        A12 /= det;
        A22 /= det;
        B1 = *pb1 + sigmaU;
        B2 = *pb2 + sigmaV;
        pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
        pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
        ++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
        // middle rows
        for ( o = 1; o < cols-1; ++o )
        {
            dPsi = psmoothX[o - 1] + psmoothX[o] + psmoothY[o];
            sigmaU = psmoothX[o - 1] * pdu[o - 1]
                    + psmoothX[o] * pdu[o + 1] + psmoothY[o] * pdu[o + s];
            sigmaV = psmoothX[o - 1] * pdv[o - 1]
                    + psmoothX[o] * pdv[o + 1] + psmoothY[o] * pdv[o + s];
            A11 = *pa22 + dPsi;
            A12 = -*pa12;
            A22 = *pa11 + dPsi;
            det = A11 * A22 - A12 * A12;
            A11 /= det;
            A12 /= det;
            A22 /= det;
            B1 = *pb1 + sigmaU;
            B2 = *pb2 + sigmaV;
            pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
            pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
            ++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
        }
        // last column
        dPsi = psmoothX[o - 1] + psmoothY[o];
        sigmaU = psmoothX[o - 1] * pdu[o - 1]
                + psmoothY[o] * pdu[o + s];
        sigmaV = psmoothX[o - 1] * pdv[o - 1]
                + psmoothY[o] * pdv[o + s];
        A11 = *pa22 + dPsi;
        A12 = -*pa12;
        A22 = *pa11 + dPsi;
        det = A11 * A22 - A12 * A12;
        A11 /= det;
        A12 /= det;
        A22 /= det;
        B1 = *pb1 + sigmaU;
        B2 = *pb2 + sigmaV;
        pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
        pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
        ++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
        ++o;
        //middle rows
        for ( j = 1; j < rows - 1; ++j)
        {
            // first column
            dPsi = psmoothX[o] + psmoothY[o - s] + psmoothY[o];
            sigmaU = psmoothX[o] * pdu[o + 1]
                    + psmoothY[o - s] * pdu[o - s]
                    + psmoothY[o] * pdu[o + s];
            sigmaV = psmoothX[o] * pdv[o + 1]
                    + psmoothY[o - s] * pdv[o - s]
                    + psmoothY[o] * pdv[o + s];
            A11 = *pa22 + dPsi;
            A12 = -*pa12;
            A22 = *pa11 + dPsi;
            det = A11 * A22 - A12 * A12;
            A11 /= det;
            A12 /= det;
            A22 /= det;
            B1 = *pb1 + sigmaU;
            B2 = *pb2 + sigmaV;
            pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
            pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
            ++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
            ++o;
            // middle columns
            for ( i = 1; i < cols - 1; ++i)
            {
                dPsi = psmoothX[o - 1] + psmoothX[o] + psmoothY[o - s]
                        + psmoothY[o];
                sigmaU = psmoothX[o - 1] * pdu[o - 1]
                        + psmoothX[o] * pdu[o + 1]
                        + psmoothY[o - s] * pdu[o - s]
                        + psmoothY[o] * pdu[o + s];
                sigmaV = psmoothX[o - 1] * pdv[o - 1]
                        + psmoothX[o] * pdv[o + 1]
                        + psmoothY[o - s] * pdv[o - s]
                        + psmoothY[o] * pdv[o + s];
                A11 = *pa22 + dPsi;
                A12 = -*pa12;
                A22 = *pa11 + dPsi;
                det = A11 * A22 - A12 * A12;
                A11 /= det;
                A12 /= det;
                A22 /= det;
                B1 = *pb1 + sigmaU;
                B2 = *pb2 + sigmaV;
                pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
                pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
                ++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
                ++o;
            }
            //last column
            dPsi = psmoothX[o - 1] + psmoothY[o - s] + psmoothY[o];
            sigmaU = psmoothX[o - 1] * pdu[o - 1]
                    + psmoothY[o - s] * pdu[o - s]
                    + psmoothY[o] * pdu[o + s];
            sigmaV = psmoothX[o - 1] * pdv[o - 1]
                    + psmoothY[o - s] * pdv[o - s]
                    + psmoothY[o] * pdv[o + s];
            A11 = *pa22 + dPsi;
            A12 = -*pa12;
            A22 = *pa11 + dPsi;
            det = A11 * A22 - A12 * A12;
            A11 /= det;
            A12 /= det;
            A22 /= det;
            B1 = *pb1 + sigmaU;
            B2 = *pb2 + sigmaV;
            pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
            pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
            ++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
            ++o;
        }
        //last row
        //first column
        dPsi = psmoothX[o] + psmoothY[o - s];
        sigmaU = psmoothX[o] * pdu[o + 1]
                + psmoothY[o - s] * pdu[o - s];
        sigmaV = psmoothX[o] * pdv[o + 1]
                + psmoothY[o - s] * pdv[o - s];
        A11 = *pa22 + dPsi;
        A12 = -*pa12;
        A22 = *pa11 + dPsi;
        det = A11 * A22 - A12 * A12;
        A11 /= det;
        A12 /= det;
        A22 /= det;
        B1 = *pb1 + sigmaU;
        B2 = *pb2 + sigmaV;
        pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
        pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
        ++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
        ++o;
        //middle columns
        for ( i = 1; i < cols - 1; ++i)
        {
            dPsi = psmoothX[o - 1] + psmoothX[o] + psmoothY[o - s];
            sigmaU = psmoothX[o - 1] * pdu[o - 1]
                    + psmoothX[o] * pdu[o + 1]
                    + psmoothY[o - s] * pdu[o - s];
            sigmaV = psmoothX[o - 1] * pdv[o - 1]
                    + psmoothX[o] * pdv[o + 1]
                    + psmoothY[o - s] * pdv[o - s];
            A11 = *pa22 + dPsi;
            A12 = -*pa12;
            A22 = *pa11 + dPsi;
            det = A11 * A22 - A12 * A12;
            A11 /= det;
            A12 /= det;
            A22 /= det;
            B1 = *pb1 + sigmaU;
            B2 = *pb2 + sigmaV;
            pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
            pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
            ++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
            ++o;
        }
        //last column
        dPsi = psmoothX[o - 1] + psmoothY[o - s];
        sigmaU = psmoothX[o - 1] * pdu[o - 1]
                + psmoothY[o - s] * pdu[o - s];
        sigmaV = psmoothX[o - 1] * pdv[o - 1]
                + psmoothY[o - s] * pdv[o - s];
        A11 = *pa22 + dPsi;
        A12 = -*pa12;
        A22 = *pa11 + dPsi;
        det = A11 * A22 - A12 * A12;
        A11 /= det;
        A12 /= det;
        A22 /= det;
        B1 = *pb1 + sigmaU;
        B2 = *pb2 + sigmaV;
        pdu[o] += omega * (A11 * B1 + A12 * B2 - pdu[o]);
        pdv[o] += omega * (A12 * B1 + A22 * B2 - pdv[o]);
        ++pa11; ++pa12; ++pa22; ++pb1; ++pb2;
    }
    merge(dWChannels, dW);

}
void OpticalFlowDeepFlow::collectGarbage()
{

}
//
//CV_INIT_ALGORITHM(OpticalFlowDeepFlow, "DenseOpticalFlow.DeepFlow",
//        obj.info()->addParam(obj, "sigma", obj.sigma, false, 0, 0, "Gaussian blur parameter");
//        obj.info()->addParam(obj, "alpha", obj.alpha, false, 0, 0, "Smoothness assumption weight");
//        obj.info()->addParam(obj, "delta", obj.delta, false, 0, 0, "Color constancy weight");
//        obj.info()->addParam(obj, "gamma", obj.gamma, false, 0, 0, "Gradient constancy weight");
//        obj.info()->addParam(obj, "omega", obj.omega, false, 0, 0, "Relaxation factor in SOR");
//        obj.info()->addParam(obj, "minSize", obj.minSize, false, 0, 0, "Min. image size in the pyramid");
//        obj.info()->addParam(obj, "fixedPointIterations", obj.fixedPointIterations, false, 0, 0, "Fixed point iterations");
//        obj.info()->addParam(obj, "sorIterations", obj.sorIterations, false, 0, 0, "SOR iterations");
//        obj.info()->addParam(obj, "downscaleFactor", obj.downscaleFactor, false, 0, 0,"Downscale factor"))


Ptr<DenseOpticalFlow> createOptFlow_DeepFlow()
{
    return makePtr<OpticalFlowDeepFlow>();
}

}//optflow
}//cv
