// Outliers ===============================================================
void ProgClassifyCL2DCore::computeStableCores()
{
    if (verbose && node->rank==0)
        std::cerr << "Computing stable cores ...\n";
    MetaData thisClass, anotherClass, commonImages, thisClassCore;
    MDRow row;
    size_t first, last;
    Matrix2D<unsigned char> coocurrence;
    Matrix1D<unsigned char> maximalCoocurrence;
    int Nblocks=blocks.size();
    taskDistributor->reset();
    std::vector<size_t> commonIdx;
    std::map<String,size_t> thisClassOrder;
    String fnImg;
    while (taskDistributor->getTasks(first, last))
        for (size_t idx=first; idx<=last; ++idx)
        {
            // Read block
            CL2DBlock &thisBlock=blocks[idx];
            if (thisBlock.level<=tolerance)
                continue;
            if (!existsBlockInMetaDataFile(thisBlock.fnLevelCore, thisBlock.block))
                continue;
            thisClass.read(thisBlock.block+"@"+thisBlock.fnLevelCore);
            thisClassCore.clear();

            // Add MDL_ORDER
            if (thisClass.size()>0)
            {
                size_t order=0;
                thisClassOrder.clear();
                FOR_ALL_OBJECTS_IN_METADATA(thisClass)
                {
                    thisClass.getValue(MDL_IMAGE,fnImg,__iter.objId);
                    thisClassOrder[fnImg]=order++;
                }

                // Calculate coocurrence within all blocks whose level is inferior to this
                size_t NthisClass=thisClass.size();
                if (NthisClass>0)
                {
                    try {
                       coocurrence.initZeros(NthisClass,NthisClass);
                    } catch (XmippError e)
                    {
                       std::cerr << e << std::endl;
                       std::cerr << "There is a memory allocation error. Most likely there are too many images in this class ("
                                 << NthisClass << " images). Consider increasing the number of initial and final classes\n";
                       REPORT_ERROR(ERR_MEM_NOTENOUGH,"While computing stable class");
                    }
                    for (int n=0; n<Nblocks; n++)
                    {
                        CL2DBlock &anotherBlock=blocks[n];
                        if (anotherBlock.level>=thisBlock.level)
                            break;
                        if (!existsBlockInMetaDataFile(anotherBlock.fnLevelCore, anotherBlock.block))
                            continue;
                        anotherClass.read(anotherBlock.block+"@"+anotherBlock.fnLevelCore);
                        anotherClass.intersection(thisClass,MDL_IMAGE);
                        commonImages.join1(anotherClass, thisClass, MDL_IMAGE,LEFT);
                        commonIdx.resize(commonImages.size());
                        size_t idx=0;
                        FOR_ALL_OBJECTS_IN_METADATA(commonImages)
                        {
                            commonImages.getValue(MDL_IMAGE,fnImg,__iter.objId);
                            commonIdx[idx++]=thisClassOrder[fnImg];
                        }
                        size_t Ncommon=commonIdx.size();
                        for (size_t i=0; i<Ncommon; i++)
                        {
                            size_t idx_i=commonIdx[i];
                            for (size_t j=i+1; j<Ncommon; j++)
                            {
                                size_t idx_j=commonIdx[j];
                                MAT_ELEM(coocurrence,idx_i,idx_j)+=1;
                            }
                        }
                    }
                }

                // Take only those elements whose coocurrence is maximal
                maximalCoocurrence.initZeros(NthisClass);
                int aimedCoocurrence=thisBlock.level-tolerance;
                FOR_ALL_ELEMENTS_IN_MATRIX2D(coocurrence)
                if (MAT_ELEM(coocurrence,i,j)==aimedCoocurrence)
                    VEC_ELEM(maximalCoocurrence,i)=VEC_ELEM(maximalCoocurrence,j)=1;

                // Now compute core
                FOR_ALL_OBJECTS_IN_METADATA(thisClass)
                {
                    thisClass.getValue(MDL_IMAGE,fnImg,__iter.objId);
                    size_t idx=thisClassOrder[fnImg];
                    if (VEC_ELEM(maximalCoocurrence,idx))
                    {
                        thisClass.getRow(row,__iter.objId);
                        thisClassCore.addRow(row);
                    }
                }
            }
            thisClassCore.write(thisBlock.fnLevel.insertBeforeExtension((String)"_stable_core_"+thisBlock.block),MD_APPEND);
        }
    int main2()
    {

        MultidimArray<double> preImg, avgCurr, mappedImg;
        MultidimArray<double> outputMovie;
        Matrix1D<double> meanStdev;
        ImageGeneric movieStack;
        Image<double> II;
        MetaData MD; // To save plot information
        FileName motionInfFile, flowFileName, flowXFileName, flowYFileName;
        ArrayDim aDim;

        // For measuring times (both for whole process and for each level of the pyramid)
        clock_t tStart, tStart2;

#ifdef GPU
        // Matrix that we required in GPU part
        GpuMat d_flowx, d_flowy, d_dest;
        GpuMat d_avgcurr, d_preimg;
#endif

        // Matrix required by Opencv
        cv::Mat flow, dest, flowx, flowy;
        cv::Mat flowxPre, flowyPre;
        cv::Mat avgcurr, avgstep, preimg, preimg8, avgcurr8;
        cv::Mat planes[]={flowxPre, flowyPre};

        int imagenum, cnt=2, div=0, flowCounter;
        int h, w, levelNum, levelCounter=1;

        motionInfFile=foname.replaceExtension("xmd");
        std::string extension=fname.getExtension();
        if (extension=="mrc")
            fname+=":mrcs";
        movieStack.read(fname,HEADER);
        movieStack.getDimensions(aDim);
        imagenum = aDim.ndim;
        h = aDim.ydim;
        w = aDim.xdim;
        if (darkImageCorr)
        {
            II.read(darkRefFilename);
            darkImage=II();
        }
        if (gainImageCorr)
        {
            II.read(gianRefFilename);
            gainImage=II();
        }
        meanStdev.initZeros(4);
        //avgcurr=cv::Mat::zeros(h, w,CV_32FC1);
        avgCurr.initZeros(h, w);
        flowxPre=cv::Mat::zeros(h, w,CV_32FC1);
        flowyPre=cv::Mat::zeros(h, w,CV_32FC1);
#ifdef GPU

        // Object for optical flow
        FarnebackOpticalFlow d_calc;
        setDevice(gpuDevice);

        // Initialize the parameters for optical flow structure
        d_calc.numLevels=6;
        d_calc.pyrScale=0.5;
        d_calc.fastPyramids=true;
        d_calc.winSize=winSize;
        d_calc.numIters=1;
        d_calc.polyN=5;
        d_calc.polySigma=1.1;
        d_calc.flags=0;
#endif
        // Initialize the stack for the output movie
        if (saveCorrMovie)
            outputMovie.initZeros(imagenum, 1, h, w);
        // Correct for global motion from a cross-correlation based algorithms
        if (globalShiftCorr)
        {
            Matrix1D<double> shiftMatrix(2);
            shiftVector.reserve(imagenum);
            shiftMD.read(globalShiftFilename);
            FOR_ALL_OBJECTS_IN_METADATA(shiftMD)
            {
                shiftMD.getValue(MDL_SHIFT_X, XX(shiftMatrix), __iter.objId);
                shiftMD.getValue(MDL_SHIFT_Y, YY(shiftMatrix), __iter.objId);
                shiftVector.push_back(shiftMatrix);
            }
        }
        tStart2=clock();
        // Compute the average of the whole stack
        fstFrame++; // Just to adapt to Li algorithm
        lstFrame++; // Just to adapt to Li algorithm
        if (lstFrame>=imagenum || lstFrame==1)
            lstFrame=imagenum;
        imagenum=lstFrame-fstFrame+1;
        levelNum=sqrt(double(imagenum));
        computeAvg(fname, fstFrame, lstFrame, avgCurr);
        // if the user want to save the PSD
        if (doAverage)
        {
            II()=avgCurr;
            II.write(foname);
            return 0;
        }
        xmipp2Opencv(avgCurr, avgcurr);
        cout<<"Frames "<<fstFrame<<" to "<<lstFrame<<" under processing ..."<<std::endl;
        while (div!=groupSize)
        {
            div=int(imagenum/cnt);
            // avgStep to hold the sum of aligned frames of each group at each step
            avgstep=cv::Mat::zeros(h, w,CV_32FC1);

            cout<<"Level "<<levelCounter<<"/"<<levelNum<<" of the pyramid is under processing"<<std::endl;
            // Compute time for each level
            tStart = clock();

            // Check if we are in the final step
            if (div==1)
                cnt=imagenum;
            flowCounter=1;
            for (int i=0;i<cnt;i++)
            {
                //Just compute the average in the last step
                if (div==1)
                {
                    if (globalShiftCorr)
                    {
                        Matrix1D<double> shiftMatrix(2);
                        MultidimArray<double> frameImage;
                        movieStack.readMapped(fname,i+1);
                        movieStack().getImage(frameImage);
                        if (darkImageCorr)
                        	frameImage-=darkImage;
                        if (gainImageCorr)
                        	frameImage/=gainImage;
                        XX(shiftMatrix)=XX(shiftVector[i]);
                        YY(shiftMatrix)=YY(shiftVector[i]);
                        translate(BSPLINE3, preImg, frameImage, shiftMatrix, WRAP);
                    }
                    else
                    {
                        movieStack.readMapped(fname,fstFrame+i);
                        movieStack().getImage(preImg);
                        if (darkImageCorr)
                            preImg-=darkImage;
                        if (gainImageCorr)
                            preImg/=gainImage;
                    }
                    xmipp2Opencv(preImg, preimg);
                }
                else
                {
                    if (i==cnt-1)
                        computeAvg(fname, i*div+fstFrame, lstFrame, preImg);
                    else
                        computeAvg(fname, i*div+fstFrame, (i+1)*div+fstFrame-1, preImg);
                }
                xmipp2Opencv(preImg, preimg);
                // Note: we should use the OpenCV conversion to use it in optical flow
                convert2Uint8(avgcurr,avgcurr8);
                convert2Uint8(preimg,preimg8);
#ifdef GPU

                d_avgcurr.upload(avgcurr8);
                d_preimg.upload(preimg8);
                if (cnt==2)
                    d_calc(d_avgcurr, d_preimg, d_flowx, d_flowy);
                else
                {
                    flowXFileName=foname.removeLastExtension()+formatString("flowx%d%d.txt",div*2,flowCounter);
                    flowYFileName=foname.removeLastExtension()+formatString("flowy%d%d.txt",div*2,flowCounter);
                    readMat(flowXFileName.c_str(), flowx);
                    readMat(flowYFileName.c_str(), flowy);
                    d_flowx.upload(flowx);
                    d_flowy.upload(flowy);
                    d_calc.flags=cv::OPTFLOW_USE_INITIAL_FLOW;
                    d_calc(d_avgcurr, d_preimg, d_flowx, d_flowy);
                }
                d_flowx.download(planes[0]);
                d_flowy.download(planes[1]);
                d_avgcurr.release();
                d_preimg.release();
                d_flowx.release();
                d_flowy.release();
#else

                if (cnt==2)
                    calcOpticalFlowFarneback(avgcurr8, preimg8, flow, 0.5, 6, winSize, 1, 5, 1.1, 0);
                else
                {
                    flowFileName=foname.removeLastExtension()+formatString("flow%d%d.txt",div*2,flowCounter);
                    readMat(flowFileName.c_str(), flow);
                    calcOpticalFlowFarneback(avgcurr8, preimg8, flow, 0.5, 6, winSize, 1, 5, 1.1, cv::OPTFLOW_USE_INITIAL_FLOW);
                }
                split(flow, planes);

#endif
                // Save the flows if we are in the last step
                if (div==groupSize)
                {
                    if (i > 0)
                    {
                        std_dev2(planes,flowxPre,flowyPre,meanStdev);
                        size_t id=MD.addObject();
                        MD.setValue(MDL_OPTICALFLOW_MEANX, double(meanStdev(0)), id);
                        MD.setValue(MDL_OPTICALFLOW_MEANY, double(meanStdev(2)), id);
                        MD.setValue(MDL_OPTICALFLOW_STDX, double(meanStdev(1)), id);
                        MD.setValue(MDL_OPTICALFLOW_STDY, double(meanStdev(3)), id);
                        MD.write(motionInfFile, MD_APPEND);
                    }
                    planes[0].copyTo(flowxPre);
                    planes[1].copyTo(flowyPre);
                }
                else
                {
#ifdef GPU
                    flowXFileName=foname.removeLastExtension()+formatString("flowx%d%d.txt",div,i+1);
                    flowYFileName=foname.removeLastExtension()+formatString("flowy%d%d.txt",div,i+1);
                    saveMat(flowXFileName.c_str(), planes[0]);
                    saveMat(flowYFileName.c_str(), planes[1]);
#else

                    flowFileName=foname.removeLastExtension()+formatString("flow%d%d.txt",div,i+1);
                    saveMat(flowFileName.c_str(), flow);
#endif

                    if ((i+1)%2==0)
                        flowCounter++;
                }
                for( int row = 0; row < planes[0].rows; row++ )
                    for( int col = 0; col < planes[0].cols; col++ )
                    {
                        planes[0].at<float>(row,col) += (float)col;
                        planes[1].at<float>(row,col) += (float)row;
                    }
                cv::remap(preimg, dest, planes[0], planes[1], cv::INTER_CUBIC);
                if (div==1 && saveCorrMovie)
                {
                    mappedImg.aliasImageInStack(outputMovie, i);
                    opencv2Xmipp(dest, mappedImg);
                }
                avgstep+=dest;
            }
            avgcurr=avgstep/cnt;
            cout<<"Processing level "<<levelCounter<<"/"<<levelNum<<" has been finished"<<std::endl;
            printf("Processing time: %.2fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
            cnt*=2;
            levelCounter++;
        }
        opencv2Xmipp(avgcurr, avgCurr);
        II() = avgCurr;
        II.write(foname);
        printf("Total Processing time: %.2fs\n", (double)(clock() - tStart2)/CLOCKS_PER_SEC);
        if (saveCorrMovie)
        {
            II()=outputMovie;
            II.write(foname.replaceExtension("mrcs"));
        }

        // Release the memory
        avgstep.release();
        preimg.release();
        avgcurr8.release();
        preimg8.release();
        flow.release();
        planes[0].release();
        planes[1].release();
        flowxPre.release();
        flowyPre.release();
        movieStack.clear();
        preImg.clear();
        avgCurr.clear();
        II.clear();
        return 0;
    }