/*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.
//
//
//                        Intel License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000, Intel Corporation, 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 Intel Corporation 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.
//
// 2011 Jason Newton <nevion@gmail.com>
// 2016 Costantino Grama <costantino.grana@unimore.it>
// 2016 Federico Bolelli <federico.bolelli@hotmail.com>
// 2016 Lorenzo Baraldi <lorenzo.baraldi@unimore.it>
// 2016 Roberto Vezzani <roberto.vezzani@unimore.it>
// 2016 Michele Cancilla <cancilla.michele@gmail.com>
//M*/
//
#include "precomp.hpp"
#include <vector>

namespace cv{
    namespace connectedcomponents{

    struct NoOp{
        NoOp(){
        }

        inline
        void init(int /*labels*/){
        }

        inline
        void initElement(const int /*nlabels*/){
        }

        inline
        void operator()(int r, int c, int l){
            CV_UNUSED(r);
            CV_UNUSED(c);
            CV_UNUSED(l);
        }

        void finish(){
        }

        inline
        void setNextLoc(const int /*nextLoc*/){
        }

        inline static
        void mergeStats(const cv::Mat& /*imgLabels*/, NoOp * /*sopArray*/, NoOp& /*sop*/, const int& /*nLabels*/){
        }

    };
    struct Point2ui64{
        uint64 x, y;
        Point2ui64(uint64 _x, uint64 _y) :x(_x), y(_y){}
        };

    struct CCStatsOp{
        const _OutputArray *_mstatsv;
        cv::Mat statsv;
        const _OutputArray *_mcentroidsv;
        cv::Mat centroidsv;
        std::vector<Point2ui64> integrals;
        int _nextLoc;

        CCStatsOp() : _mstatsv(0), _mcentroidsv(0), _nextLoc(0) {}
        CCStatsOp(OutputArray _statsv, OutputArray _centroidsv) : _mstatsv(&_statsv), _mcentroidsv(&_centroidsv), _nextLoc(0){}

        inline
        void init(int nlabels){
            _mstatsv->create(cv::Size(CC_STAT_MAX, nlabels), cv::DataType<int>::type);
            statsv = _mstatsv->getMat();
            _mcentroidsv->create(cv::Size(2, nlabels), cv::DataType<double>::type);
            centroidsv = _mcentroidsv->getMat();

            for (int l = 0; l < (int)nlabels; ++l){
                int *row = (int *)&statsv.at<int>(l, 0);
                row[CC_STAT_LEFT] = INT_MAX;
                row[CC_STAT_TOP] = INT_MAX;
                row[CC_STAT_WIDTH] = INT_MIN;
                row[CC_STAT_HEIGHT] = INT_MIN;
                row[CC_STAT_AREA] = 0;
            }
            integrals.resize(nlabels, Point2ui64(0, 0));
        }

        inline
        void initElement(const int nlabels){
            statsv = cv::Mat(nlabels, CC_STAT_MAX, cv::DataType<int>::type);
            for (int l = 0; l < (int)nlabels; ++l){
                int *row = (int *)statsv.ptr(l);
                row[CC_STAT_LEFT] = INT_MAX;
                row[CC_STAT_TOP] = INT_MAX;
                row[CC_STAT_WIDTH] = INT_MIN;
                row[CC_STAT_HEIGHT] = INT_MIN;
                row[CC_STAT_AREA] = 0;
            }
            integrals.resize(nlabels, Point2ui64(0, 0));
        }

        void operator()(int r, int c, int l){
            int *row =& statsv.at<int>(l, 0);
            row[CC_STAT_LEFT] = MIN(row[CC_STAT_LEFT], c);
            row[CC_STAT_WIDTH] = MAX(row[CC_STAT_WIDTH], c);
            row[CC_STAT_TOP] = MIN(row[CC_STAT_TOP], r);
            row[CC_STAT_HEIGHT] = MAX(row[CC_STAT_HEIGHT], r);
            row[CC_STAT_AREA]++;
            Point2ui64& integral = integrals[l];
            integral.x += c;
            integral.y += r;
        }

        void finish(){
            for (int l = 0; l < statsv.rows; ++l){
                int *row =& statsv.at<int>(l, 0);
                double area = ((unsigned*)row)[CC_STAT_AREA];
                double *centroid = &centroidsv.at<double>(l, 0);
                if (area > 0){
                    row[CC_STAT_WIDTH] = row[CC_STAT_WIDTH] - row[CC_STAT_LEFT] + 1;
                    row[CC_STAT_HEIGHT] = row[CC_STAT_HEIGHT] - row[CC_STAT_TOP] + 1;
                    Point2ui64& integral = integrals[l];
                    centroid[0] = double(integral.x) / area;
                    centroid[1] = double(integral.y) / area;
                } else {
                    row[CC_STAT_WIDTH] = 0;
                    row[CC_STAT_HEIGHT] = 0;
                    row[CC_STAT_LEFT] = -1;
                    centroid[0] = std::numeric_limits<double>::quiet_NaN();
                    centroid[1] = std::numeric_limits<double>::quiet_NaN();
                }
            }
        }

        inline
        void setNextLoc(const int nextLoc){
            _nextLoc = nextLoc;
        }

        inline static
        void mergeStats(const cv::Mat& imgLabels, CCStatsOp *sopArray, CCStatsOp& sop, const int& nLabels){
            const int  h = imgLabels.rows;

            if (sop._nextLoc != h){
                for (int nextLoc = sop._nextLoc; nextLoc < h; nextLoc = sopArray[nextLoc]._nextLoc){
                    //merge between sopNext and sop
                    for (int l = 0; l < nLabels; ++l){
                        int *rowNext = (int*)sopArray[nextLoc].statsv.ptr(l);
                        if (rowNext[CC_STAT_AREA] > 0){ //if changed merge all the stats
                            int *rowMerged = (int*)sop.statsv.ptr(l);
                            rowMerged[CC_STAT_LEFT] = MIN(rowMerged[CC_STAT_LEFT], rowNext[CC_STAT_LEFT]);
                            rowMerged[CC_STAT_WIDTH] = MAX(rowMerged[CC_STAT_WIDTH], rowNext[CC_STAT_WIDTH]);
                            rowMerged[CC_STAT_TOP] = MIN(rowMerged[CC_STAT_TOP], rowNext[CC_STAT_TOP]);
                            rowMerged[CC_STAT_HEIGHT] = MAX(rowMerged[CC_STAT_HEIGHT], rowNext[CC_STAT_HEIGHT]);
                            rowMerged[CC_STAT_AREA] += rowNext[CC_STAT_AREA];

                            sop.integrals[l].x += sopArray[nextLoc].integrals[l].x;
                            sop.integrals[l].y += sopArray[nextLoc].integrals[l].y;
                        }
                    }
                }
            }
        }
    };

    //Find the root of the tree of node i
    template<typename LabelT>
    inline static
    LabelT findRoot(const LabelT *P, LabelT i){
        LabelT root = i;
        while (P[root] < root){
            root = P[root];
        }
        return root;
    }

    //Make all nodes in the path of node i point to root
    template<typename LabelT>
    inline static
    void setRoot(LabelT *P, LabelT i, LabelT root){
        while (P[i] < i){
            LabelT j = P[i];
            P[i] = root;
            i = j;
        }
        P[i] = root;
    }

    //Find the root of the tree of the node i and compress the path in the process
    template<typename LabelT>
    inline static
    LabelT find(LabelT *P, LabelT i){
        LabelT root = findRoot(P, i);
        setRoot(P, i, root);
        return root;
    }

    //unite the two trees containing nodes i and j and return the new root
    template<typename LabelT>
    inline static
    LabelT set_union(LabelT *P, LabelT i, LabelT j){
        LabelT root = findRoot(P, i);
        if (i != j){
            LabelT rootj = findRoot(P, j);
            if (root > rootj){
                root = rootj;
            }
            setRoot(P, j, root);
        }
        setRoot(P, i, root);
        return root;
    }

    //Flatten the Union Find tree and relabel the components
    template<typename LabelT>
    inline static
    LabelT flattenL(LabelT *P, LabelT length){
        LabelT k = 1;
        for (LabelT i = 1; i < length; ++i){
            if (P[i] < i){
                P[i] = P[P[i]];
            }
            else{
                P[i] = k; k = k + 1;
            }
        }
        return k;
    }

    template<typename LabelT>
    inline static
    void flattenL(LabelT *P, const int start, const int nElem, LabelT& k){
        for (int i = start; i < start + nElem; ++i){
            if (P[i] < i){//node that point to root
                P[i] = P[P[i]];
            }
            else{ //for root node
                P[i] = k;
                k = k + 1;
            }
        }
    }

    template <typename LT> static inline
    LT stripeFirstLabel4Connectivity(int y, int w)
    {
        CV_DbgAssert((y & 1) == 0);
        return (LT(y) * LT(w) /*+ 1*/) / 2 + 1;
    }

    template <typename LT> static inline
    LT stripeFirstLabel8Connectivity(int y, int w)
    {
        CV_DbgAssert((y & 1) == 0);
        return LT((y /*+ 1*/) / 2) * LT((w + 1) / 2) + 1;
    }


    //Based on "Two Strategies to Speed up Connected Components Algorithms", the SAUF (Scan array union find) variant
        //using decision trees
        //Kesheng Wu, et al
    template<typename LabelT, typename PixelT, typename StatsOp = NoOp >
    struct LabelingWuParallel{

        class FirstScan8Connectivity : public cv::ParallelLoopBody{
            const cv::Mat& img_;
            cv::Mat& imgLabels_;
            LabelT *P_;
            int *chunksSizeAndLabels_;

        public:
            FirstScan8Connectivity(const cv::Mat& img, cv::Mat& imgLabels, LabelT *P, int *chunksSizeAndLabels)
                : img_(img), imgLabels_(imgLabels), P_(P), chunksSizeAndLabels_(chunksSizeAndLabels){}

            FirstScan8Connectivity&  operator=(const FirstScan8Connectivity& ) { return *this; }

            void operator()(const cv::Range& range2) const CV_OVERRIDE
            {
                const Range range(range2.start * 2, std::min(range2.end * 2, img_.rows));
                int r = range.start;

                chunksSizeAndLabels_[r] = range.end;

                LabelT label = stripeFirstLabel8Connectivity<LabelT>(r, imgLabels_.cols);

                const LabelT firstLabel = label;
                const int w = img_.cols;
                const int limitLine = r, startR = r;

                // Rosenfeld Mask
                // +-+-+-+
                // |p|q|r|
                // +-+-+-+
                // |s|x|
                // +-+-+
                for (; r != range.end; ++r)
                {
                    PixelT const * const img_row = img_.ptr<PixelT>(r);
                    PixelT const * const img_row_prev = (PixelT *)(((char *)img_row) - img_.step.p[0]);
                    LabelT * const imgLabels_row = imgLabels_.ptr<LabelT>(r);
                    LabelT * const imgLabels_row_prev = (LabelT *)(((char *)imgLabels_row) - imgLabels_.step.p[0]);
                    for (int c = 0; c < w; ++c) {

#define condition_p c > 0 && r > limitLine && img_row_prev[c - 1] > 0
#define condition_q r > limitLine && img_row_prev[c] > 0
#define condition_r c < w - 1 && r > limitLine && img_row_prev[c + 1] > 0
#define condition_s c > 0 && img_row[c - 1] > 0
#define condition_x img_row[c] > 0

                        if (condition_x){
                            if (condition_q){
                                //copy q
                                imgLabels_row[c] = imgLabels_row_prev[c];
                            }
                            else{
                                //not q
                                if (condition_r){
                                    if (condition_p){
                                        //concavity p->x->r. Merge
                                        imgLabels_row[c] = set_union(P_, imgLabels_row_prev[c - 1], imgLabels_row_prev[c + 1]);
                                    }
                                    else{ //not p and q
                                        if (condition_s){
                                            //step s->x->r. Merge
                                            imgLabels_row[c] = set_union(P_, imgLabels_row[c - 1], imgLabels_row_prev[c + 1]);
                                        }
                                        else{ //not p, q and s
                                            //copy r
                                            imgLabels_row[c] = imgLabels_row_prev[c + 1];
                                        }
                                    }
                                }
                                else{
                                    //not r and q
                                    if (condition_p){
                                        //copy p
                                        imgLabels_row[c] = imgLabels_row_prev[c - 1];
                                    }
                                    else{//not r,q and p
                                        if (condition_s){
                                            imgLabels_row[c] = imgLabels_row[c - 1];
                                        }
                                        else{
                                            //new label
                                            imgLabels_row[c] = label;
                                            P_[label] = label;
                                            label = label + 1;
                                        }
                                    }
                                }
                            }
                        }
                        else{
                            //x is a background pixel
                            imgLabels_row[c] = 0;
                        }
                    }
                }
                //write in the follower memory location
                chunksSizeAndLabels_[startR + 1] = label - firstLabel;
            }
#undef condition_p
#undef condition_q
#undef condition_r
#undef condition_s
#undef condition_x
        };

        class FirstScan4Connectivity : public cv::ParallelLoopBody{
            const cv::Mat& img_;
            cv::Mat& imgLabels_;
            LabelT *P_;
            int *chunksSizeAndLabels_;

        public:
            FirstScan4Connectivity(const cv::Mat& img, cv::Mat& imgLabels, LabelT *P, int *chunksSizeAndLabels)
                : img_(img), imgLabels_(imgLabels), P_(P), chunksSizeAndLabels_(chunksSizeAndLabels){}

            FirstScan4Connectivity&  operator=(const FirstScan4Connectivity& ) { return *this; }

            void operator()(const cv::Range& range2) const CV_OVERRIDE
            {
                const Range range(range2.start * 2, std::min(range2.end * 2, img_.rows));
                int r = range.start;

                chunksSizeAndLabels_[r] = range.end;

                LabelT label = stripeFirstLabel4Connectivity<LabelT>(r, imgLabels_.cols);

                const LabelT firstLabel = label;
                const int w = img_.cols;
                const int limitLine = r, startR = r;

                // Rosenfeld Mask
                // +-+-+-+
                // |-|q|-|
                // +-+-+-+
                // |s|x|
                // +-+-+
                for (; r != range.end; ++r){
                    PixelT const * const img_row = img_.ptr<PixelT>(r);
                    PixelT const * const img_row_prev = (PixelT *)(((char *)img_row) - img_.step.p[0]);
                    LabelT * const imgLabels_row = imgLabels_.ptr<LabelT>(r);
                    LabelT * const imgLabels_row_prev = (LabelT *)(((char *)imgLabels_row) - imgLabels_.step.p[0]);
                    for (int c = 0; c < w; ++c) {

#define condition_q r > limitLine && img_row_prev[c] > 0
#define condition_s c > 0 && img_row[c - 1] > 0
#define condition_x img_row[c] > 0

                        if (condition_x){
                            if (condition_q){
                                if (condition_s){
                                    //step s->x->q. Merge
                                    imgLabels_row[c] = set_union(P_, imgLabels_row[c - 1], imgLabels_row_prev[c]);
                                }
                                else{
                                    //copy q
                                    imgLabels_row[c] = imgLabels_row_prev[c];
                                }
                            }
                            else{
                                if (condition_s){ // copy s
                                    imgLabels_row[c] = imgLabels_row[c - 1];
                                }
                                else{
                                    //new label
                                    imgLabels_row[c] = label;
                                    P_[label] = label;
                                    label = label + 1;
                                }
                            }
                        }
                        else{
                            //x is a background pixel
                            imgLabels_row[c] = 0;
                        }
                    }
                }
                //write in the following memory location
                chunksSizeAndLabels_[startR + 1] = label - firstLabel;
            }
#undef condition_q
#undef condition_s
#undef condition_x
        };

        class SecondScan : public cv::ParallelLoopBody{
            cv::Mat& imgLabels_;
            const LabelT *P_;
            StatsOp& sop_;
            StatsOp *sopArray_;
            LabelT& nLabels_;
        public:
            SecondScan(cv::Mat& imgLabels, const LabelT *P, StatsOp& sop, StatsOp *sopArray, LabelT& nLabels)
                : imgLabels_(imgLabels), P_(P), sop_(sop), sopArray_(sopArray), nLabels_(nLabels){}

            SecondScan&  operator=(const SecondScan& ) { return *this; }

            void operator()(const cv::Range& range2) const CV_OVERRIDE
            {
                const Range range(range2.start * 2, std::min(range2.end * 2, imgLabels_.rows));
                int r = range.start;
                const int rowBegin = r;
                const int rowEnd = range.end;

                if (rowBegin > 0){
                    sopArray_[rowBegin].initElement(nLabels_);
                    sopArray_[rowBegin].setNextLoc(rowEnd); //_nextLoc = rowEnd;

                    for (; r < rowEnd; ++r) {
                        LabelT * img_row_start = imgLabels_.ptr<LabelT>(r);
                        LabelT * const img_row_end = img_row_start + imgLabels_.cols;
                        for (int c = 0; img_row_start != img_row_end; ++img_row_start, ++c){
                            *img_row_start = P_[*img_row_start];
                            sopArray_[rowBegin](r, c, *img_row_start);
                        }
                    }
                }
                else{
                    //the first thread uses sop in order to make less merges
                    sop_.setNextLoc(rowEnd);
                    for (; r < rowEnd; ++r) {
                        LabelT * img_row_start = imgLabels_.ptr<LabelT>(r);
                        LabelT * const img_row_end = img_row_start + imgLabels_.cols;
                        for (int c = 0; img_row_start != img_row_end; ++img_row_start, ++c){
                            *img_row_start = P_[*img_row_start];
                            sop_(r, c, *img_row_start);
                        }
                    }
                }
            }
        };

        inline static
        void mergeLabels8Connectivity(cv::Mat& imgLabels, LabelT *P, const int *chunksSizeAndLabels){

            // Merge Mask
            // +-+-+-+
            // |p|q|r|
            // +-+-+-+
            //   |x|
            //   +-+
            const int w = imgLabels.cols, h = imgLabels.rows;

            for (int r = chunksSizeAndLabels[0]; r < h; r = chunksSizeAndLabels[r]){

                LabelT * const imgLabels_row = imgLabels.ptr<LabelT>(r);
                LabelT * const imgLabels_row_prev = (LabelT *)(((char *)imgLabels_row) - imgLabels.step.p[0]);

                for (int c = 0; c < w; ++c){

#define condition_p c > 0 && imgLabels_row_prev[c - 1] > 0
#define condition_q imgLabels_row_prev[c] > 0
#define condition_r c < w - 1 && imgLabels_row_prev[c + 1] > 0
#define condition_x imgLabels_row[c] > 0

                    if (condition_x){
                        if (condition_p){
                            //merge of two label
                            imgLabels_row[c] = set_union(P, imgLabels_row_prev[c - 1], imgLabels_row[c]);
                        }
                        if (condition_r){
                            //merge of two label
                            imgLabels_row[c] = set_union(P, imgLabels_row_prev[c + 1], imgLabels_row[c]);
                        }
                        if (condition_q){
                            //merge of two label
                            imgLabels_row[c] = set_union(P, imgLabels_row_prev[c], imgLabels_row[c]);
                        }
                    }
                }
            }
#undef condition_p
#undef condition_q
#undef condition_r
#undef condition_x
        }

        inline static
        void mergeLabels4Connectivity(cv::Mat& imgLabels, LabelT *P, const int *chunksSizeAndLabels){

            // Merge Mask
            // +-+-+-+
            // |-|q|-|
            // +-+-+-+
            //   |x|
            //   +-+
            const int w = imgLabels.cols, h = imgLabels.rows;

            for (int r = chunksSizeAndLabels[0]; r < h; r = chunksSizeAndLabels[r]){

                LabelT * const imgLabels_row = imgLabels.ptr<LabelT>(r);
                LabelT * const imgLabels_row_prev = (LabelT *)(((char *)imgLabels_row) - imgLabels.step.p[0]);

                for (int c = 0; c < w; ++c){

#define condition_q imgLabels_row_prev[c] > 0
#define condition_x imgLabels_row[c] > 0

                    if (condition_x){
                        if (condition_q){
                            //merge of two label
                            imgLabels_row[c] = set_union(P, imgLabels_row_prev[c], imgLabels_row[c]);
                        }
                    }
                }
            }
#undef condition_q
#undef condition_x
        }

        LabelT operator()(const cv::Mat& img, cv::Mat& imgLabels, int connectivity, StatsOp& sop){
            CV_Assert(img.rows == imgLabels.rows);
            CV_Assert(img.cols == imgLabels.cols);
            CV_Assert(connectivity == 8 || connectivity == 4);

            const int h = img.rows;
            const int w = img.cols;

            //A quick and dirty upper bound for the maximum number of labels.
            //Following formula comes from the fact that a 2x2 block in 4-way connectivity
            //labeling can never have more than 2 new labels and 1 label for background.
            //Worst case image example pattern:
            //1 0 1 0 1...
            //0 1 0 1 0...
            //1 0 1 0 1...
            //............
            //Obviously, 4-way connectivity upper bound is also good for 8-way connectivity labeling
            const size_t Plength = (size_t(h) * size_t(w) + 1) / 2 + 1;

            //Array used to store info and labeled pixel by each thread.
            //Different threads affect different memory location of chunksSizeAndLabels
            std::vector<int> chunksSizeAndLabels(roundUp(h, 2));

            //Tree of labels
            std::vector<LabelT> P_(Plength, 0);
            LabelT *P = P_.data();
            //First label is for background
            //P[0] = 0;

            cv::Range range2(0, divUp(h, 2));
            const double nParallelStripes = std::max(1, std::min(h / 2, getNumThreads()*4));

            LabelT nLabels = 1;

            if (connectivity == 8){
                //First scan
                cv::parallel_for_(range2, FirstScan8Connectivity(img, imgLabels, P, chunksSizeAndLabels.data()), nParallelStripes);

                //merge labels of different chunks
                mergeLabels8Connectivity(imgLabels, P, chunksSizeAndLabels.data());

                for (int i = 0; i < h; i = chunksSizeAndLabels[i]){
                    flattenL(P, stripeFirstLabel8Connectivity<int>(i, w), chunksSizeAndLabels[i + 1], nLabels);
                }
            }
            else{
                //First scan
                cv::parallel_for_(range2, FirstScan4Connectivity(img, imgLabels, P, chunksSizeAndLabels.data()), nParallelStripes);

                //merge labels of different chunks
                mergeLabels4Connectivity(imgLabels, P, chunksSizeAndLabels.data());

                for (int i = 0; i < h; i = chunksSizeAndLabels[i]){
                    flattenL(P, stripeFirstLabel4Connectivity<int>(i, w), chunksSizeAndLabels[i + 1], nLabels);
                }
            }

            //Array for statistics dataof threads
            std::vector<StatsOp> sopArray(h);

            sop.init(nLabels);
            //Second scan
            cv::parallel_for_(range2, SecondScan(imgLabels, P, sop, sopArray.data(), nLabels), nParallelStripes);
            StatsOp::mergeStats(imgLabels, sopArray.data(), sop, nLabels);
            sop.finish();

            return nLabels;
        }
    };//End struct LabelingWuParallel


    //Based on "Two Strategies to Speed up Connected Components Algorithms", the SAUF (Scan array union find) variant
    //using decision trees
    //Kesheng Wu, et al
    template<typename LabelT, typename PixelT, typename StatsOp = NoOp >
    struct LabelingWu{
        LabelT operator()(const cv::Mat& img, cv::Mat& imgLabels, int connectivity, StatsOp& sop){
            CV_Assert(imgLabels.rows == img.rows);
            CV_Assert(imgLabels.cols == img.cols);
            CV_Assert(connectivity == 8 || connectivity == 4);

            const int h = img.rows;
            const int w = img.cols;

            //A quick and dirty upper bound for the maximum number of labels.
            //Following formula comes from the fact that a 2x2 block in 4-way connectivity
            //labeling can never have more than 2 new labels and 1 label for background.
            //Worst case image example pattern:
            //1 0 1 0 1...
            //0 1 0 1 0...
            //1 0 1 0 1...
            //............
            //Obviously, 4-way connectivity upper bound is also good for 8-way connectivity labeling
            const size_t Plength = (size_t(h) * size_t(w) + 1) / 2 + 1;
            //array P for equivalences resolution
            std::vector<LabelT> P_(Plength, 0);
            LabelT *P = P_.data();
            //first label is for background pixels
            //P[0] = 0;
            LabelT lunique = 1;

            if (connectivity == 8){
                for (int r = 0; r < h; ++r){
                    // Get row pointers
                    PixelT const * const img_row = img.ptr<PixelT>(r);
                    PixelT const * const img_row_prev = (PixelT *)(((char *)img_row) - img.step.p[0]);
                    LabelT * const imgLabels_row = imgLabels.ptr<LabelT>(r);
                    LabelT * const imgLabels_row_prev = (LabelT *)(((char *)imgLabels_row) - imgLabels.step.p[0]);

                    for (int c = 0; c < w; ++c){

#define condition_p c>0 && r>0 && img_row_prev[c - 1]>0
#define condition_q r>0 && img_row_prev[c]>0
#define condition_r c < w - 1 && r > 0 && img_row_prev[c + 1] > 0
#define condition_s c > 0 && img_row[c - 1] > 0
#define condition_x img_row[c] > 0

                        if (condition_x){
                            if (condition_q){
                                //x <- q
                                imgLabels_row[c] = imgLabels_row_prev[c];
                            }
                            else{
                                // q = 0
                                if (condition_r){
                                    if (condition_p){
                                        // x <- merge(p,r)
                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev[c - 1], imgLabels_row_prev[c + 1]);
                                    }
                                    else{
                                        // p = q = 0
                                        if (condition_s){
                                            // x <- merge(s,r)
                                            imgLabels_row[c] = set_union(P, imgLabels_row[c - 1], imgLabels_row_prev[c + 1]);
                                        }
                                        else{
                                            // p = q = s = 0
                                            // x <- r
                                            imgLabels_row[c] = imgLabels_row_prev[c + 1];
                                        }
                                    }
                                }
                                else{
                                    // r = q = 0
                                    if (condition_p){
                                        // x <- p
                                        imgLabels_row[c] = imgLabels_row_prev[c - 1];
                                    }
                                    else{
                                        // r = q = p = 0
                                        if (condition_s){
                                            imgLabels_row[c] = imgLabels_row[c - 1];
                                        }
                                        else{
                                            //new label
                                            imgLabels_row[c] = lunique;
                                            P[lunique] = lunique;
                                            lunique = lunique + 1;
                                        }
                                    }
                                }
                            }
                        }
                        else{
                            //x is a background pixel
                            imgLabels_row[c] = 0;
                        }
                    }
                }
#undef condition_p
#undef condition_q
#undef condition_r
#undef condition_s
#undef condition_x
            }
            else{
                for (int r = 0; r < h; ++r){
                    PixelT const * const img_row = img.ptr<PixelT>(r);
                    PixelT const * const img_row_prev = (PixelT *)(((char *)img_row) - img.step.p[0]);
                    LabelT * const imgLabels_row = imgLabels.ptr<LabelT>(r);
                    LabelT * const imgLabels_row_prev = (LabelT *)(((char *)imgLabels_row) - imgLabels.step.p[0]);
                    for (int c = 0; c < w; ++c) {

#define condition_q r > 0 && img_row_prev[c] > 0
#define condition_s c > 0 && img_row[c - 1] > 0
#define condition_x img_row[c] > 0

                        if (condition_x){
                            if (condition_q){
                                if (condition_s){
                                    //Merge s->x->q
                                    imgLabels_row[c] = set_union(P, imgLabels_row[c - 1], imgLabels_row_prev[c]);
                                }
                                else{
                                    //copy q
                                    imgLabels_row[c] = imgLabels_row_prev[c];
                                }
                            }
                            else{
                                if (condition_s){
                                    // copy s
                                    imgLabels_row[c] = imgLabels_row[c - 1];
                                }
                                else{
                                    //new label
                                    imgLabels_row[c] = lunique;
                                    P[lunique] = lunique;
                                    lunique = lunique + 1;
                                }
                            }
                        }
                        else{
                            //x is a background pixel
                            imgLabels_row[c] = 0;
                        }
                    }
                }
#undef condition_q
#undef condition_s
#undef condition_x
            }

            //analysis
            LabelT nLabels = flattenL(P, lunique);
            sop.init(nLabels);

            for (int r = 0; r < h; ++r) {
                LabelT * img_row_start = imgLabels.ptr<LabelT>(r);
                LabelT * const img_row_end = img_row_start + w;
                for (int c = 0; img_row_start != img_row_end; ++img_row_start, ++c){
                    *img_row_start = P[*img_row_start];
                    sop(r, c, *img_row_start);
                }
            }

            sop.finish();

            return nLabels;
        }//End function LabelingWu operator()
    };//End struct LabelingWu


    // Based on "Optimized  Block-based Connected Components Labeling with Decision Trees", Costantino Grana et al
    // Only for 8-connectivity
    template<typename LabelT, typename PixelT, typename StatsOp = NoOp >
    struct LabelingGranaParallel{

        class FirstScan : public cv::ParallelLoopBody{
        private:
            const cv::Mat& img_;
            cv::Mat& imgLabels_;
            LabelT *P_;
            int *chunksSizeAndLabels_;

        public:
            FirstScan(const cv::Mat& img, cv::Mat& imgLabels, LabelT *P, int *chunksSizeAndLabels)
                : img_(img), imgLabels_(imgLabels), P_(P), chunksSizeAndLabels_(chunksSizeAndLabels){}

            FirstScan&  operator=(const FirstScan&) { return *this; }

            void operator()(const cv::Range& range2) const CV_OVERRIDE
            {
                const Range range(range2.start * 2, std::min(range2.end * 2, img_.rows));
                int r = range.start;

                chunksSizeAndLabels_[r] = range.end;

                LabelT label = stripeFirstLabel8Connectivity<LabelT>(r, imgLabels_.cols);

                const LabelT firstLabel = label;
                const int h = img_.rows, w = img_.cols;
                const int limitLine = r + 1, startR = r;

                for (; r < range.end; r += 2){
                    // Get rows pointer
                    const PixelT * const img_row = img_.ptr<uchar>(r);
                    const PixelT * const img_row_prev = (PixelT *)(((char *)img_row) - img_.step.p[0]);
                    const PixelT * const img_row_prev_prev = (PixelT *)(((char *)img_row_prev) - img_.step.p[0]);
                    const PixelT * const img_row_fol = (PixelT *)(((char *)img_row) + img_.step.p[0]);
                    LabelT * const imgLabels_row = imgLabels_.ptr<LabelT>(r);
                    LabelT * const imgLabels_row_prev_prev = (LabelT *)(((char *)imgLabels_row) - imgLabels_.step.p[0] - imgLabels_.step.p[0]);
                    for (int c = 0; c < w; c += 2) {

                        // We work with 2x2 blocks
                        // +-+-+-+
                        // |P|Q|R|
                        // +-+-+-+
                        // |S|X|
                        // +-+-+

                        // The pixels are named as follows
                        // +---+---+---+
                        // |a b|c d|e f|
                        // |g h|i j|k l|
                        // +---+---+---+
                        // |m n|o p|
                        // |q r|s t|
                        // +---+---+

                        // Pixels a, f, l, q are not needed, since we need to understand the
                        // the connectivity between these blocks and those pixels only metter
                        // when considering the outer connectivities

                        // A bunch of defines used to check if the pixels are foreground,
                        // without going outside the image limits.

#define condition_b c-1>=0 && r > limitLine && img_row_prev_prev[c-1]>0
#define condition_c r > limitLine && img_row_prev_prev[c]>0
#define condition_d c+1<w && r > limitLine && img_row_prev_prev[c+1]>0
#define condition_e c+2<w && r > limitLine && img_row_prev_prev[c+2]>0

#define condition_g c-2>=0 && r > limitLine - 1 && img_row_prev[c-2]>0
#define condition_h c-1>=0 && r > limitLine - 1 && img_row_prev[c-1]>0
#define condition_i r > limitLine - 1 && img_row_prev[c]>0
#define condition_j c+1<w && r > limitLine - 1 && img_row_prev[c+1]>0
#define condition_k c+2<w && r > limitLine - 1 && img_row_prev[c+2]>0

#define condition_m c-2>=0 && img_row[c-2]>0
#define condition_n c-1>=0 && img_row[c-1]>0
#define condition_o img_row[c]>0
#define condition_p c+1<w && img_row[c+1]>0

#define condition_r c-1>=0 && r+1<h && img_row_fol[c-1]>0
#define condition_s r+1<h && img_row_fol[c]>0
#define condition_t c+1<w && r+1<h && img_row_fol[c+1]>0

                        // This is a decision tree which allows to choose which action to
                        // perform, checking as few conditions as possible.
                        // Actions are available after the tree.

                        if (condition_o) {
                            if (condition_n) {
                                if (condition_j) {
                                    if (condition_i) {
                                        //Action_6: Assign label of block S
                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                        continue;
                                    }
                                    else {
                                        if (condition_c) {
                                            if (condition_h) {
                                                //Action_6: Assign label of block S
                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                continue;
                                            }
                                            else {
                                                if (condition_g) {
                                                    if (condition_b) {
                                                        //Action_6: Assign label of block S
                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                        continue;
                                                    }
                                                    else {
                                                        //Action_11: Merge labels of block Q and S
                                                        imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                                else {
                                                    //Action_11: Merge labels of block Q and S
                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                    continue;
                                                }
                                            }
                                        }
                                        else {
                                            //Action_11: Merge labels of block Q and S
                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                            continue;
                                        }
                                    }
                                }
                                else {
                                    if (condition_p) {
                                        if (condition_k) {
                                            if (condition_d) {
                                                if (condition_i) {
                                                    //Action_6: Assign label of block S
                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                    continue;
                                                }
                                                else {
                                                    if (condition_c) {
                                                        if (condition_h) {
                                                            //Action_6: Assign label of block S
                                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                                            continue;
                                                        }
                                                        else {
                                                            if (condition_g) {
                                                                if (condition_b) {
                                                                    //Action_6: Assign label of block S
                                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                                    continue;
                                                                }
                                                                else {
                                                                    //Action_12: Merge labels of block R and S
                                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                            else {
                                                                //Action_12: Merge labels of block R and S
                                                                imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                    }
                                                    else {
                                                        //Action_12: Merge labels of block R and S
                                                        imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                            }
                                            else {
                                                //Action_12: Merge labels of block R and S
                                                imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                continue;
                                            }
                                        }
                                        else {
                                            //Action_6: Assign label of block S
                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                            continue;
                                        }
                                    }
                                    else {
                                        //Action_6: Assign label of block S
                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                        continue;
                                    }
                                }
                            }
                            else {
                                if (condition_r) {
                                    if (condition_j) {
                                        if (condition_m) {
                                            if (condition_h) {
                                                if (condition_i) {
                                                    //Action_6: Assign label of block S
                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                    continue;
                                                }
                                                else {
                                                    if (condition_c) {
                                                        //Action_6: Assign label of block S
                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                        continue;
                                                    }
                                                    else {
                                                        //Action_11: Merge labels of block Q and S
                                                        imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                            }
                                            else {
                                                if (condition_g) {
                                                    if (condition_b) {
                                                        if (condition_i) {
                                                            //Action_6: Assign label of block S
                                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                                            continue;
                                                        }
                                                        else {
                                                            if (condition_c) {
                                                                //Action_6: Assign label of block S
                                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                                continue;
                                                            }
                                                            else {
                                                                //Action_11: Merge labels of block Q and S
                                                                imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                    }
                                                    else {
                                                        //Action_11: Merge labels of block Q and S
                                                        imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                                else {
                                                    //Action_11: Merge labels of block Q and S
                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                    continue;
                                                }
                                            }
                                        }
                                        else {
                                            if (condition_i) {
                                                //Action_11: Merge labels of block Q and S
                                                imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                continue;
                                            }
                                            else {
                                                if (condition_h) {
                                                    if (condition_c) {
                                                        //Action_11: Merge labels of block Q and S
                                                        imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                    else {
                                                        //Action_14: Merge labels of block P_, Q and S
                                                        imgLabels_row[c] = set_union(P_, set_union(P_, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c]), imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                                else {
                                                    //Action_11: Merge labels of block Q and S
                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                    continue;
                                                }
                                            }
                                        }
                                    }
                                    else {
                                        if (condition_p) {
                                            if (condition_k) {
                                                if (condition_m) {
                                                    if (condition_h) {
                                                        if (condition_d) {
                                                            if (condition_i) {
                                                                //Action_6: Assign label of block S
                                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                                continue;
                                                            }
                                                            else {
                                                                if (condition_c) {
                                                                    //Action_6: Assign label of block S
                                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                                    continue;
                                                                }
                                                                else {
                                                                    //Action_12: Merge labels of block R and S
                                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                        }
                                                        else {
                                                            //Action_12: Merge labels of block R and S
                                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        if (condition_d) {
                                                            if (condition_g) {
                                                                if (condition_b) {
                                                                    if (condition_i) {
                                                                        //Action_6: Assign label of block S
                                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                                        continue;
                                                                    }
                                                                    else {
                                                                        if (condition_c) {
                                                                            //Action_6: Assign label of block S
                                                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                                                            continue;
                                                                        }
                                                                        else {
                                                                            //Action_12: Merge labels of block R and S
                                                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                            continue;
                                                                        }
                                                                    }
                                                                }
                                                                else {
                                                                    //Action_12: Merge labels of block R and S
                                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                            else {
                                                                //Action_12: Merge labels of block R and S
                                                                imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                        else {
                                                            if (condition_i) {
                                                                if (condition_g) {
                                                                    if (condition_b) {
                                                                        //Action_12: Merge labels of block R and S
                                                                        imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                        continue;
                                                                    }
                                                                    else {
                                                                        //Action_16: labels of block Q, R and S
                                                                        imgLabels_row[c] = set_union(P_, set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                                        continue;
                                                                    }
                                                                }
                                                                else {
                                                                    //Action_16: labels of block Q, R and S
                                                                    imgLabels_row[c] = set_union(P_, set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                            else {
                                                                //Action_12: Merge labels of block R and S
                                                                imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                    }
                                                }
                                                else {
                                                    if (condition_i) {
                                                        if (condition_d) {
                                                            //Action_12: Merge labels of block R and S
                                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                        else {
                                                            //Action_16: labels of block Q, R and S
                                                            imgLabels_row[c] = set_union(P_, set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        if (condition_h) {
                                                            if (condition_d) {
                                                                if (condition_c) {
                                                                    //Action_12: Merge labels of block R and S
                                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                                else {
                                                                    //Action_15: Merge labels of block P_, R and S
                                                                    imgLabels_row[c] = set_union(P_, set_union(P_, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                            else {
                                                                //Action_15: Merge labels of block P_, R and S
                                                                imgLabels_row[c] = set_union(P_, set_union(P_, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                        else {
                                                            //Action_12: Merge labels of block R and S
                                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                }
                                            }
                                            else {
                                                if (condition_h) {
                                                    if (condition_m) {
                                                        //Action_6: Assign label of block S
                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                        continue;
                                                    }
                                                    else {
                                                        // ACTION_9 Merge labels of block P_ and S
                                                        imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c - 2], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                                else {
                                                    if (condition_i) {
                                                        if (condition_m) {
                                                            if (condition_g) {
                                                                if (condition_b) {
                                                                    //Action_6: Assign label of block S
                                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                                    continue;
                                                                }
                                                                else {
                                                                    //Action_11: Merge labels of block Q and S
                                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                            else {
                                                                //Action_11: Merge labels of block Q and S
                                                                imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                        else {
                                                            //Action_11: Merge labels of block Q and S
                                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        //Action_6: Assign label of block S
                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                        continue;
                                                    }
                                                }
                                            }
                                        }
                                        else {
                                            if (condition_h) {
                                                if (condition_m) {
                                                    //Action_6: Assign label of block S
                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                    continue;
                                                }
                                                else {
                                                    // ACTION_9 Merge labels of block P_ and S
                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c - 2], imgLabels_row[c - 2]);
                                                    continue;
                                                }
                                            }
                                            else {
                                                if (condition_i) {
                                                    if (condition_m) {
                                                        if (condition_g) {
                                                            if (condition_b) {
                                                                //Action_6: Assign label of block S
                                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                                continue;
                                                            }
                                                            else {
                                                                //Action_11: Merge labels of block Q and S
                                                                imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                        else {
                                                            //Action_11: Merge labels of block Q and S
                                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        //Action_11: Merge labels of block Q and S
                                                        imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                                else {
                                                    //Action_6: Assign label of block S
                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                    continue;
                                                }
                                            }
                                        }
                                    }
                                }
                                else {
                                    if (condition_j) {
                                        if (condition_i) {
                                            //Action_4: Assign label of block Q
                                            imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                            continue;
                                        }
                                        else {
                                            if (condition_h) {
                                                if (condition_c) {
                                                    //Action_4: Assign label of block Q
                                                    imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                                    continue;
                                                }
                                                else {
                                                    //Action_7: Merge labels of block P_ and Q
                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c]);
                                                    continue;
                                                }
                                            }
                                            else {
                                                //Action_4: Assign label of block Q
                                                imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                                continue;
                                            }
                                        }
                                    }
                                    else {
                                        if (condition_p) {
                                            if (condition_k) {
                                                if (condition_i) {
                                                    if (condition_d) {
                                                        //Action_5: Assign label of block R
                                                        imgLabels_row[c] = imgLabels_row_prev_prev[c + 2];
                                                        continue;
                                                    }
                                                    else {
                                                        // ACTION_10 Merge labels of block Q and R
                                                        imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]);
                                                        continue;
                                                    }
                                                }
                                                else {
                                                    if (condition_h) {
                                                        if (condition_d) {
                                                            if (condition_c) {
                                                                //Action_5: Assign label of block R
                                                                imgLabels_row[c] = imgLabels_row_prev_prev[c + 2];
                                                                continue;
                                                            }
                                                            else {
                                                                //Action_8: Merge labels of block P_ and R
                                                                imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c + 2]);
                                                                continue;
                                                            }
                                                        }
                                                        else {
                                                            //Action_8: Merge labels of block P_ and R
                                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c + 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        //Action_5: Assign label of block R
                                                        imgLabels_row[c] = imgLabels_row_prev_prev[c + 2];
                                                        continue;
                                                    }
                                                }
                                            }
                                            else {
                                                if (condition_i) {
                                                    //Action_4: Assign label of block Q
                                                    imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                                    continue;
                                                }
                                                else {
                                                    if (condition_h) {
                                                        //Action_3: Assign label of block P_
                                                        imgLabels_row[c] = imgLabels_row_prev_prev[c - 2];
                                                        continue;
                                                    }
                                                    else {
                                                        //Action_2: New label (the block has foreground pixels and is not connected to anything else)
                                                        imgLabels_row[c] = label;
                                                        P_[label] = label;
                                                        label = label + 1;
                                                        continue;
                                                    }
                                                }
                                            }
                                        }
                                        else {
                                            if (condition_i) {
                                                //Action_4: Assign label of block Q
                                                imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                                continue;
                                            }
                                            else {
                                                if (condition_h) {
                                                    //Action_3: Assign label of block P_
                                                    imgLabels_row[c] = imgLabels_row_prev_prev[c - 2];
                                                    continue;
                                                }
                                                else {
                                                    //Action_2: New label (the block has foreground pixels and is not connected to anything else)
                                                    imgLabels_row[c] = label;
                                                    P_[label] = label;
                                                    label = label + 1;
                                                    continue;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        else {
                            if (condition_s) {
                                if (condition_p) {
                                    if (condition_n) {
                                        if (condition_j) {
                                            if (condition_i) {
                                                //Action_6: Assign label of block S
                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                continue;
                                            }
                                            else {
                                                if (condition_c) {
                                                    if (condition_h) {
                                                        //Action_6: Assign label of block S
                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                        continue;
                                                    }
                                                    else {
                                                        if (condition_g) {
                                                            if (condition_b) {
                                                                //Action_6: Assign label of block S
                                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                                continue;
                                                            }
                                                            else {
                                                                //Action_11: Merge labels of block Q and S
                                                                imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                        else {
                                                            //Action_11: Merge labels of block Q and S
                                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                }
                                                else {
                                                    //Action_11: Merge labels of block Q and S
                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                    continue;
                                                }
                                            }
                                        }
                                        else {
                                            if (condition_k) {
                                                if (condition_d) {
                                                    if (condition_i) {
                                                        //Action_6: Assign label of block S
                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                        continue;
                                                    }
                                                    else {
                                                        if (condition_c) {
                                                            if (condition_h) {
                                                                //Action_6: Assign label of block S
                                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                                continue;
                                                            }
                                                            else {
                                                                if (condition_g) {
                                                                    if (condition_b) {
                                                                        //Action_6: Assign label of block S
                                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                                        continue;
                                                                    }
                                                                    else {
                                                                        //Action_12: Merge labels of block R and S
                                                                        imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                        continue;
                                                                    }
                                                                }
                                                                else {
                                                                    //Action_12: Merge labels of block R and S
                                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                        }
                                                        else {
                                                            //Action_12: Merge labels of block R and S
                                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                }
                                                else {
                                                    //Action_12: Merge labels of block R and S
                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                    continue;
                                                }
                                            }
                                            else {
                                                //Action_6: Assign label of block S
                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                continue;
                                            }
                                        }
                                    }
                                    else {
                                        if (condition_r) {
                                            if (condition_j) {
                                                if (condition_m) {
                                                    if (condition_h) {
                                                        if (condition_i) {
                                                            //Action_6: Assign label of block S
                                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                                            continue;
                                                        }
                                                        else {
                                                            if (condition_c) {
                                                                //Action_6: Assign label of block S
                                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                                continue;
                                                            }
                                                            else {
                                                                //Action_11: Merge labels of block Q and S
                                                                imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                    }
                                                    else {
                                                        if (condition_g) {
                                                            if (condition_b) {
                                                                if (condition_i) {
                                                                    //Action_6: Assign label of block S
                                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                                    continue;
                                                                }
                                                                else {
                                                                    if (condition_c) {
                                                                        //Action_6: Assign label of block S
                                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                                        continue;
                                                                    }
                                                                    else {
                                                                        //Action_11: Merge labels of block Q and S
                                                                        imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                                        continue;
                                                                    }
                                                                }
                                                            }
                                                            else {
                                                                //Action_11: Merge labels of block Q and S
                                                                imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                        else {
                                                            //Action_11: Merge labels of block Q and S
                                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                }
                                                else {
                                                    //Action_11: Merge labels of block Q and S
                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                    continue;
                                                }
                                            }
                                            else {
                                                if (condition_k) {
                                                    if (condition_d) {
                                                        if (condition_m) {
                                                            if (condition_h) {
                                                                if (condition_i) {
                                                                    //Action_6: Assign label of block S
                                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                                    continue;
                                                                }
                                                                else {
                                                                    if (condition_c) {
                                                                        //Action_6: Assign label of block S
                                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                                        continue;
                                                                    }
                                                                    else {
                                                                        //Action_12: Merge labels of block R and S
                                                                        imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                        continue;
                                                                    }
                                                                }
                                                            }
                                                            else {
                                                                if (condition_g) {
                                                                    if (condition_b) {
                                                                        if (condition_i) {
                                                                            //Action_6: Assign label of block S
                                                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                                                            continue;
                                                                        }
                                                                        else {
                                                                            if (condition_c) {
                                                                                //Action_6: Assign label of block S
                                                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                                                continue;
                                                                            }
                                                                            else {
                                                                                //Action_12: Merge labels of block R and S
                                                                                imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                                continue;
                                                                            }
                                                                        }
                                                                    }
                                                                    else {
                                                                        //Action_12: Merge labels of block R and S
                                                                        imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                        continue;
                                                                    }
                                                                }
                                                                else {
                                                                    //Action_12: Merge labels of block R and S
                                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                        }
                                                        else {
                                                            //Action_12: Merge labels of block R and S
                                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        if (condition_i) {
                                                            if (condition_m) {
                                                                if (condition_h) {
                                                                    //Action_12: Merge labels of block R and S
                                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                                else {
                                                                    if (condition_g) {
                                                                        if (condition_b) {
                                                                            //Action_12: Merge labels of block R and S
                                                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                            continue;
                                                                        }
                                                                        else {
                                                                            //Action_16: labels of block Q, R and S
                                                                            imgLabels_row[c] = set_union(P_, set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                                            continue;
                                                                        }
                                                                    }
                                                                    else {
                                                                        //Action_16: labels of block Q, R and S
                                                                        imgLabels_row[c] = set_union(P_, set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                                        continue;
                                                                    }
                                                                }
                                                            }
                                                            else {
                                                                //Action_16: labels of block Q, R and S
                                                                imgLabels_row[c] = set_union(P_, set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                        else {
                                                            //Action_12: Merge labels of block R and S
                                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                }
                                                else {
                                                    if (condition_i) {
                                                        if (condition_m) {
                                                            if (condition_h) {
                                                                //Action_6: Assign label of block S
                                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                                continue;
                                                            }
                                                            else {
                                                                if (condition_g) {
                                                                    if (condition_b) {
                                                                        //Action_6: Assign label of block S
                                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                                        continue;
                                                                    }
                                                                    else {
                                                                        //Action_11: Merge labels of block Q and S
                                                                        imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                                        continue;
                                                                    }
                                                                }
                                                                else {
                                                                    //Action_11: Merge labels of block Q and S
                                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                        }
                                                        else {
                                                            //Action_11: Merge labels of block Q and S
                                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        //Action_6: Assign label of block S
                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                        continue;
                                                    }
                                                }
                                            }
                                        }
                                        else {
                                            if (condition_j) {
                                                //Action_4: Assign label of block Q
                                                imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                                continue;
                                            }
                                            else {
                                                if (condition_k) {
                                                    if (condition_i) {
                                                        if (condition_d) {
                                                            //Action_5: Assign label of block R
                                                            imgLabels_row[c] = imgLabels_row_prev_prev[c + 2];
                                                            continue;
                                                        }
                                                        else {
                                                            // ACTION_10 Merge labels of block Q and R
                                                            imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        //Action_5: Assign label of block R
                                                        imgLabels_row[c] = imgLabels_row_prev_prev[c + 2];
                                                        continue;
                                                    }
                                                }
                                                else {
                                                    if (condition_i) {
                                                        //Action_4: Assign label of block Q
                                                        imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                                        continue;
                                                    }
                                                    else {
                                                        //Action_2: New label (the block has foreground pixels and is not connected to anything else)
                                                        imgLabels_row[c] = label;
                                                        P_[label] = label;
                                                        label = label + 1;
                                                        continue;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                                else {
                                    if (condition_r) {
                                        //Action_6: Assign label of block S
                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                        continue;
                                    }
                                    else {
                                        if (condition_n) {
                                            //Action_6: Assign label of block S
                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                            continue;
                                        }
                                        else {
                                            //Action_2: New label (the block has foreground pixels and is not connected to anything else)
                                            imgLabels_row[c] = label;
                                            P_[label] = label;
                                            label = label + 1;
                                            continue;
                                        }
                                    }
                                }
                            }
                            else {
                                if (condition_p) {
                                    if (condition_j) {
                                        //Action_4: Assign label of block Q
                                        imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                        continue;
                                    }
                                    else {
                                        if (condition_k) {
                                            if (condition_i) {
                                                if (condition_d) {
                                                    //Action_5: Assign label of block R
                                                    imgLabels_row[c] = imgLabels_row_prev_prev[c + 2];
                                                    continue;
                                                }
                                                else {
                                                    // ACTION_10 Merge labels of block Q and R
                                                    imgLabels_row[c] = set_union(P_, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]);
                                                    continue;
                                                }
                                            }
                                            else {
                                                //Action_5: Assign label of block R
                                                imgLabels_row[c] = imgLabels_row_prev_prev[c + 2];
                                                continue;
                                            }
                                        }
                                        else {
                                            if (condition_i) {
                                                //Action_4: Assign label of block Q
                                                imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                                continue;
                                            }
                                            else {
                                                //Action_2: New label (the block has foreground pixels and is not connected to anything else)
                                                imgLabels_row[c] = label;
                                                P_[label] = label;
                                                label = label + 1;
                                                continue;
                                            }
                                        }
                                    }
                                }
                                else {
                                    if (condition_t) {
                                        //Action_2: New label (the block has foreground pixels and is not connected to anything else)
                                        imgLabels_row[c] = label;
                                        P_[label] = label;
                                        label = label + 1;
                                        continue;
                                    }
                                    else {
                                        // Action_1: No action (the block has no foreground pixels)
                                        imgLabels_row[c] = 0;
                                        continue;
                                    }
                                }
                            }
                        }
                    }
                }
                //write in the follower memory location
                chunksSizeAndLabels_[startR + 1] = label - firstLabel;
            }
#undef condition_k
#undef condition_j
#undef condition_i
#undef condition_h
#undef condition_g
#undef condition_e
#undef condition_d
#undef condition_c
#undef condition_b
        };

        class SecondScan : public cv::ParallelLoopBody{
        private:
            const cv::Mat& img_;
            cv::Mat& imgLabels_;
            LabelT *P_;
            StatsOp& sop_;
            StatsOp *sopArray_;
            LabelT& nLabels_;

        public:
            SecondScan(const cv::Mat& img, cv::Mat& imgLabels, LabelT *P, StatsOp& sop, StatsOp *sopArray, LabelT& nLabels)
                : img_(img), imgLabels_(imgLabels), P_(P), sop_(sop), sopArray_(sopArray), nLabels_(nLabels){}

            void operator()(const cv::Range& range2) const CV_OVERRIDE
            {
                const Range range(range2.start * 2, std::min(range2.end * 2, img_.rows));
                int r = range.start;

                const int rowBegin = r;
                const int rowEnd = range.end;

                if (rowBegin > 0){
                    sopArray_[rowBegin].initElement(nLabels_);
                    sopArray_[rowBegin].setNextLoc(rowEnd); //_nextLoc = rowEnd;

                    if (imgLabels_.rows&  1){
                        if (imgLabels_.cols&  1){
                            //Case 1: both rows and cols odd
                            for (; r < rowEnd; r += 2){
                                // Get rows pointer
                                const PixelT * const img_row = img_.ptr<PixelT>(r);
                                const PixelT * const img_row_fol = (PixelT *)(((char *)img_row) + img_.step.p[0]);

                                LabelT * const imgLabels_row = imgLabels_.ptr<LabelT>(r);
                                LabelT * const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels_.step.p[0]);
                                // Get rows pointer
                                for (int c = 0; c < imgLabels_.cols; c += 2) {
                                    LabelT iLabel = imgLabels_row[c];
                                    if (iLabel > 0) {
                                        iLabel = P_[iLabel];
                                        if (img_row[c] > 0){
                                            imgLabels_row[c] = iLabel;
                                            sopArray_[rowBegin](r, c, iLabel);
                                        }
                                        else{
                                            imgLabels_row[c] = 0;
                                            sopArray_[rowBegin](r, c, 0);
                                        }
                                        if (c + 1 < imgLabels_.cols) {
                                            if (img_row[c + 1] > 0){
                                                imgLabels_row[c + 1] = iLabel;
                                                sopArray_[rowBegin](r, c + 1, iLabel);
                                            }
                                            else{
                                                imgLabels_row[c + 1] = 0;
                                                sopArray_[rowBegin](r, c + 1, 0);
                                            }
                                            if (r + 1 < imgLabels_.rows) {
                                                if (img_row_fol[c] > 0){
                                                    imgLabels_row_fol[c] = iLabel;
                                                    sopArray_[rowBegin](r + 1, c, iLabel);
                                                }
                                                else{
                                                    imgLabels_row_fol[c] = 0;
                                                    sopArray_[rowBegin](r + 1, c, 0);
                                                }
                                                if (img_row_fol[c + 1] > 0){
                                                    imgLabels_row_fol[c + 1] = iLabel;
                                                    sopArray_[rowBegin](r + 1, c + 1, iLabel);
                                                }
                                                else{
                                                    imgLabels_row_fol[c + 1] = 0;
                                                    sopArray_[rowBegin](r + 1, c + 1, 0);
                                                }
                                            }
                                        }
                                        else if (r + 1 < imgLabels_.rows) {
                                            if (img_row_fol[c] > 0){
                                                imgLabels_row_fol[c] = iLabel;
                                                sopArray_[rowBegin](r + 1, c, iLabel);
                                            }
                                            else{
                                                imgLabels_row_fol[c] = 0;
                                                sopArray_[rowBegin](r + 1, c, 0);
                                            }
                                        }
                                    }
                                    else {
                                        imgLabels_row[c] = 0;
                                        sopArray_[rowBegin](r, c, 0);
                                        if (c + 1 < imgLabels_.cols) {
                                            imgLabels_row[c + 1] = 0;
                                            sopArray_[rowBegin](r, c + 1, 0);
                                            if (r + 1 < imgLabels_.rows) {
                                                imgLabels_row_fol[c] = 0;
                                                imgLabels_row_fol[c + 1] = 0;
                                                sopArray_[rowBegin](r + 1, c, 0);
                                                sopArray_[rowBegin](r + 1, c + 1, 0);
                                            }
                                        }
                                        else if (r + 1 < imgLabels_.rows) {
                                            imgLabels_row_fol[c] = 0;
                                            sopArray_[rowBegin](r + 1, c, 0);
                                        }
                                    }
                                }
                            }
                        }//END Case 1
                        else{
                            //Case 2: only rows odd
                            for (; r < rowEnd; r += 2){
                                // Get rows pointer
                                const PixelT * const img_row = img_.ptr<PixelT>(r);
                                const PixelT * const img_row_fol = (PixelT *)(((char *)img_row) + img_.step.p[0]);
                                LabelT * const imgLabels_row = imgLabels_.ptr<LabelT>(r);
                                LabelT * const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels_.step.p[0]);
                                // Get rows pointer
                                for (int c = 0; c < imgLabels_.cols; c += 2) {
                                    LabelT iLabel = imgLabels_row[c];
                                    if (iLabel > 0) {
                                        iLabel = P_[iLabel];
                                        if (img_row[c] > 0){
                                            imgLabels_row[c] = iLabel;
                                            sopArray_[rowBegin](r, c, iLabel);
                                        }
                                        else{
                                            imgLabels_row[c] = 0;
                                            sopArray_[rowBegin](r, c, 0);
                                        }
                                        if (img_row[c + 1] > 0){
                                            imgLabels_row[c + 1] = iLabel;
                                            sopArray_[rowBegin](r, c + 1, iLabel);
                                        }
                                        else{
                                            imgLabels_row[c + 1] = 0;
                                            sopArray_[rowBegin](r, c + 1, 0);
                                        }
                                        if (r + 1 < imgLabels_.rows) {
                                            if (img_row_fol[c] > 0){
                                                imgLabels_row_fol[c] = iLabel;
                                                sopArray_[rowBegin](r + 1, c, iLabel);
                                            }
                                            else{
                                                imgLabels_row_fol[c] = 0;
                                                sopArray_[rowBegin](r + 1, c, 0);
                                            }
                                            if (img_row_fol[c + 1] > 0){
                                                imgLabels_row_fol[c + 1] = iLabel;
                                                sopArray_[rowBegin](r + 1, c + 1, iLabel);
                                            }
                                            else{
                                                imgLabels_row_fol[c + 1] = 0;
                                                sopArray_[rowBegin](r + 1, c + 1, 0);
                                            }
                                        }
                                    }
                                    else {
                                        imgLabels_row[c] = 0;
                                        imgLabels_row[c + 1] = 0;
                                        sopArray_[rowBegin](r, c, 0);
                                        sopArray_[rowBegin](r, c + 1, 0);
                                        if (r + 1 < imgLabels_.rows) {
                                            imgLabels_row_fol[c] = 0;
                                            imgLabels_row_fol[c + 1] = 0;
                                            sopArray_[rowBegin](r + 1, c, 0);
                                            sopArray_[rowBegin](r + 1, c + 1, 0);
                                        }
                                    }
                                }
                            }
                        }// END Case 2
                    }
                    else{
                        if (imgLabels_.cols&  1){
                            //Case 3: only cols odd
                            for (; r < rowEnd; r += 2){
                                // Get rows pointer
                                const PixelT * const img_row = img_.ptr<PixelT>(r);
                                const PixelT * const img_row_fol = (PixelT *)(((char *)img_row) + img_.step.p[0]);
                                LabelT * const imgLabels_row = imgLabels_.ptr<LabelT>(r);
                                LabelT * const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels_.step.p[0]);
                                // Get rows pointer
                                for (int c = 0; c < imgLabels_.cols; c += 2) {
                                    LabelT iLabel = imgLabels_row[c];
                                    if (iLabel > 0) {
                                        iLabel = P_[iLabel];
                                        if (img_row[c] > 0){
                                            imgLabels_row[c] = iLabel;
                                            sopArray_[rowBegin](r, c, iLabel);
                                        }
                                        else{
                                            imgLabels_row[c] = 0;
                                            sopArray_[rowBegin](r, c, 0);
                                        }
                                        if (img_row_fol[c] > 0){
                                            imgLabels_row_fol[c] = iLabel;
                                            sopArray_[rowBegin](r + 1, c, iLabel);
                                        }
                                        else{
                                            imgLabels_row_fol[c] = 0;
                                            sopArray_[rowBegin](r + 1, c, 0);
                                        }
                                        if (c + 1 < imgLabels_.cols) {
                                            if (img_row[c + 1] > 0){
                                                imgLabels_row[c + 1] = iLabel;
                                                sopArray_[rowBegin](r, c + 1, iLabel);
                                            }
                                            else{
                                                imgLabels_row[c + 1] = 0;
                                                sopArray_[rowBegin](r, c + 1, 0);
                                            }
                                            if (img_row_fol[c + 1] > 0){
                                                imgLabels_row_fol[c + 1] = iLabel;
                                                sopArray_[rowBegin](r + 1, c + 1, iLabel);
                                            }
                                            else{
                                                imgLabels_row_fol[c + 1] = 0;
                                                sopArray_[rowBegin](r + 1, c + 1, 0);
                                            }
                                        }
                                    }
                                    else{
                                        imgLabels_row[c] = 0;
                                        imgLabels_row_fol[c] = 0;
                                        sopArray_[rowBegin](r, c, 0);
                                        sopArray_[rowBegin](r + 1, c, 0);
                                        if (c + 1 < imgLabels_.cols) {
                                            imgLabels_row[c + 1] = 0;
                                            imgLabels_row_fol[c + 1] = 0;
                                            sopArray_[rowBegin](r, c + 1, 0);
                                            sopArray_[rowBegin](r + 1, c + 1, 0);
                                        }
                                    }
                                }
                            }
                        }// END case 3
                        else{
                            //Case 4: nothing odd
                            for (; r < rowEnd; r += 2){
                                // Get rows pointer
                                const PixelT * const img_row = img_.ptr<PixelT>(r);
                                const PixelT * const img_row_fol = (PixelT *)(((char *)img_row) + img_.step.p[0]);
                                LabelT * const imgLabels_row = imgLabels_.ptr<LabelT>(r);
                                LabelT * const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels_.step.p[0]);
                                // Get rows pointer
                                for (int c = 0; c < imgLabels_.cols; c += 2) {
                                    LabelT iLabel = imgLabels_row[c];
                                    if (iLabel > 0) {
                                        iLabel = P_[iLabel];
                                        if (img_row[c] > 0){
                                            imgLabels_row[c] = iLabel;
                                            sopArray_[rowBegin](r, c, iLabel);
                                        }
                                        else{
                                            imgLabels_row[c] = 0;
                                            sopArray_[rowBegin](r, c, 0);
                                        }
                                        if (img_row[c + 1] > 0){
                                            imgLabels_row[c + 1] = iLabel;
                                            sopArray_[rowBegin](r, c + 1, iLabel);
                                        }
                                        else{
                                            imgLabels_row[c + 1] = 0;
                                            sopArray_[rowBegin](r, c + 1, 0);
                                        }
                                        if (img_row_fol[c] > 0){
                                            imgLabels_row_fol[c] = iLabel;
                                            sopArray_[rowBegin](r + 1, c, iLabel);
                                        }
                                        else{
                                            imgLabels_row_fol[c] = 0;
                                            sopArray_[rowBegin](r + 1, c, 0);
                                        }
                                        if (img_row_fol[c + 1] > 0){
                                            imgLabels_row_fol[c + 1] = iLabel;
                                            sopArray_[rowBegin](r + 1, c + 1, iLabel);
                                        }
                                        else{
                                            imgLabels_row_fol[c + 1] = 0;
                                            sopArray_[rowBegin](r + 1, c + 1, 0);
                                        }
                                    }
                                    else {
                                        imgLabels_row[c] = 0;
                                        imgLabels_row[c + 1] = 0;
                                        imgLabels_row_fol[c] = 0;
                                        imgLabels_row_fol[c + 1] = 0;
                                        sopArray_[rowBegin](r, c, 0);
                                        sopArray_[rowBegin](r, c + 1, 0);
                                        sopArray_[rowBegin](r + 1, c, 0);
                                        sopArray_[rowBegin](r + 1, c + 1, 0);
                                    }
                                }
                            }//END case 4
                        }
                    }
                }
                else{
                    //the first thread uses sop in order to make less merges
                    sop_.setNextLoc(rowEnd);
                    if (imgLabels_.rows&  1){
                        if (imgLabels_.cols&  1){
                            //Case 1: both rows and cols odd
                            for (; r < rowEnd; r += 2){
                                // Get rows pointer
                                const PixelT * const img_row = img_.ptr<PixelT>(r);
                                const PixelT * const img_row_fol = (PixelT *)(((char *)img_row) + img_.step.p[0]);

                                LabelT * const imgLabels_row = imgLabels_.ptr<LabelT>(r);
                                LabelT * const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels_.step.p[0]);
                                // Get rows pointer
                                for (int c = 0; c < imgLabels_.cols; c += 2) {
                                    LabelT iLabel = imgLabels_row[c];
                                    if (iLabel > 0) {
                                        iLabel = P_[iLabel];
                                        if (img_row[c] > 0){
                                            imgLabels_row[c] = iLabel;
                                            sop_(r, c, iLabel);
                                        }
                                        else{
                                            imgLabels_row[c] = 0;
                                            sop_(r, c, 0);
                                        }
                                        if (c + 1 < imgLabels_.cols) {
                                            if (img_row[c + 1] > 0){
                                                imgLabels_row[c + 1] = iLabel;
                                                sop_(r, c + 1, iLabel);
                                            }
                                            else{
                                                imgLabels_row[c + 1] = 0;
                                                sop_(r, c + 1, 0);
                                            }
                                            if (r + 1 < imgLabels_.rows) {
                                                if (img_row_fol[c] > 0){
                                                    imgLabels_row_fol[c] = iLabel;
                                                    sop_(r + 1, c, iLabel);
                                                }
                                                else{
                                                    imgLabels_row_fol[c] = 0;
                                                    sop_(r + 1, c, 0);
                                                }
                                                if (img_row_fol[c + 1] > 0){
                                                    imgLabels_row_fol[c + 1] = iLabel;
                                                    sop_(r + 1, c + 1, iLabel);
                                                }
                                                else{
                                                    imgLabels_row_fol[c + 1] = 0;
                                                    sop_(r + 1, c + 1, 0);
                                                }
                                            }
                                        }
                                        else if (r + 1 < imgLabels_.rows) {
                                            if (img_row_fol[c] > 0){
                                                imgLabels_row_fol[c] = iLabel;
                                                sop_(r + 1, c, iLabel);
                                            }
                                            else{
                                                imgLabels_row_fol[c] = 0;
                                                sop_(r + 1, c, 0);
                                            }
                                        }
                                    }
                                    else {
                                        imgLabels_row[c] = 0;
                                        sop_(r, c, 0);
                                        if (c + 1 < imgLabels_.cols) {
                                            imgLabels_row[c + 1] = 0;
                                            sop_(r, c + 1, 0);
                                            if (r + 1 < imgLabels_.rows) {
                                                imgLabels_row_fol[c] = 0;
                                                imgLabels_row_fol[c + 1] = 0;
                                                sop_(r + 1, c, 0);
                                                sop_(r + 1, c + 1, 0);
                                            }
                                        }
                                        else if (r + 1 < imgLabels_.rows) {
                                            imgLabels_row_fol[c] = 0;
                                            sop_(r + 1, c, 0);
                                        }
                                    }
                                }
                            }
                        }//END Case 1
                        else{
                            //Case 2: only rows odd
                            for (; r < rowEnd; r += 2){
                                // Get rows pointer
                                const PixelT * const img_row = img_.ptr<PixelT>(r);
                                const PixelT * const img_row_fol = (PixelT *)(((char *)img_row) + img_.step.p[0]);
                                LabelT * const imgLabels_row = imgLabels_.ptr<LabelT>(r);
                                LabelT * const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels_.step.p[0]);
                                // Get rows pointer
                                for (int c = 0; c < imgLabels_.cols; c += 2) {
                                    LabelT iLabel = imgLabels_row[c];
                                    if (iLabel > 0) {
                                        iLabel = P_[iLabel];
                                        if (img_row[c] > 0){
                                            imgLabels_row[c] = iLabel;
                                            sop_(r, c, iLabel);
                                        }
                                        else{
                                            imgLabels_row[c] = 0;
                                            sop_(r, c, 0);
                                        }
                                        if (img_row[c + 1] > 0){
                                            imgLabels_row[c + 1] = iLabel;
                                            sop_(r, c + 1, iLabel);
                                        }
                                        else{
                                            imgLabels_row[c + 1] = 0;
                                            sop_(r, c + 1, 0);
                                        }
                                        if (r + 1 < imgLabels_.rows) {
                                            if (img_row_fol[c] > 0){
                                                imgLabels_row_fol[c] = iLabel;
                                                sop_(r + 1, c, iLabel);
                                            }
                                            else{
                                                imgLabels_row_fol[c] = 0;
                                                sop_(r + 1, c, 0);
                                            }
                                            if (img_row_fol[c + 1] > 0){
                                                imgLabels_row_fol[c + 1] = iLabel;
                                                sop_(r + 1, c + 1, iLabel);
                                            }
                                            else{
                                                imgLabels_row_fol[c + 1] = 0;
                                                sop_(r + 1, c + 1, 0);
                                            }
                                        }
                                    }
                                    else {
                                        imgLabels_row[c] = 0;
                                        imgLabels_row[c + 1] = 0;
                                        sop_(r, c, 0);
                                        sop_(r, c + 1, 0);
                                        if (r + 1 < imgLabels_.rows) {
                                            imgLabels_row_fol[c] = 0;
                                            imgLabels_row_fol[c + 1] = 0;
                                            sop_(r + 1, c, 0);
                                            sop_(r + 1, c + 1, 0);
                                        }
                                    }
                                }
                            }
                        }// END Case 2
                    }
                    else{
                        if (imgLabels_.cols&  1){
                            //Case 3: only cols odd
                            for (; r < rowEnd; r += 2){
                                // Get rows pointer
                                const PixelT * const img_row = img_.ptr<PixelT>(r);
                                const PixelT * const img_row_fol = (PixelT *)(((char *)img_row) + img_.step.p[0]);
                                LabelT * const imgLabels_row = imgLabels_.ptr<LabelT>(r);
                                LabelT * const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels_.step.p[0]);
                                // Get rows pointer
                                for (int c = 0; c < imgLabels_.cols; c += 2) {
                                    LabelT iLabel = imgLabels_row[c];
                                    if (iLabel > 0) {
                                        iLabel = P_[iLabel];
                                        if (img_row[c] > 0){
                                            imgLabels_row[c] = iLabel;
                                            sop_(r, c, iLabel);
                                        }
                                        else{
                                            imgLabels_row[c] = 0;
                                            sop_(r, c, 0);
                                        }
                                        if (img_row_fol[c] > 0){
                                            imgLabels_row_fol[c] = iLabel;
                                            sop_(r + 1, c, iLabel);
                                        }
                                        else{
                                            imgLabels_row_fol[c] = 0;
                                            sop_(r + 1, c, 0);
                                        }
                                        if (c + 1 < imgLabels_.cols) {
                                            if (img_row[c + 1] > 0){
                                                imgLabels_row[c + 1] = iLabel;
                                                sop_(r, c + 1, iLabel);
                                            }
                                            else{
                                                imgLabels_row[c + 1] = 0;
                                                sop_(r, c + 1, 0);
                                            }
                                            if (img_row_fol[c + 1] > 0){
                                                imgLabels_row_fol[c + 1] = iLabel;
                                                sop_(r + 1, c + 1, iLabel);
                                            }
                                            else{
                                                imgLabels_row_fol[c + 1] = 0;
                                                sop_(r + 1, c + 1, 0);
                                            }
                                        }
                                    }
                                    else{
                                        imgLabels_row[c] = 0;
                                        imgLabels_row_fol[c] = 0;
                                        sop_(r, c, 0);
                                        sop_(r + 1, c, 0);
                                        if (c + 1 < imgLabels_.cols) {
                                            imgLabels_row[c + 1] = 0;
                                            imgLabels_row_fol[c + 1] = 0;
                                            sop_(r, c + 1, 0);
                                            sop_(r + 1, c + 1, 0);
                                        }
                                    }
                                }
                            }
                        }// END case 3
                        else{
                            //Case 4: nothing odd
                            for (; r < rowEnd; r += 2){
                                // Get rows pointer
                                const PixelT * const img_row = img_.ptr<PixelT>(r);
                                const PixelT * const img_row_fol = (PixelT *)(((char *)img_row) + img_.step.p[0]);
                                LabelT * const imgLabels_row = imgLabels_.ptr<LabelT>(r);
                                LabelT * const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels_.step.p[0]);
                                // Get rows pointer
                                for (int c = 0; c < imgLabels_.cols; c += 2) {
                                    LabelT iLabel = imgLabels_row[c];
                                    if (iLabel > 0) {
                                        iLabel = P_[iLabel];
                                        if (img_row[c] > 0){
                                            imgLabels_row[c] = iLabel;
                                            sop_(r, c, iLabel);
                                        }
                                        else{
                                            imgLabels_row[c] = 0;
                                            sop_(r, c, 0);
                                        }
                                        if (img_row[c + 1] > 0){
                                            imgLabels_row[c + 1] = iLabel;
                                            sop_(r, c + 1, iLabel);
                                        }
                                        else{
                                            imgLabels_row[c + 1] = 0;
                                            sop_(r, c + 1, 0);
                                        }
                                        if (img_row_fol[c] > 0){
                                            imgLabels_row_fol[c] = iLabel;
                                            sop_(r + 1, c, iLabel);
                                        }
                                        else{
                                            imgLabels_row_fol[c] = 0;
                                            sop_(r + 1, c, 0);
                                        }
                                        if (img_row_fol[c + 1] > 0){
                                            imgLabels_row_fol[c + 1] = iLabel;
                                            sop_(r + 1, c + 1, iLabel);
                                        }
                                        else{
                                            imgLabels_row_fol[c + 1] = 0;
                                            sop_(r + 1, c + 1, 0);
                                        }
                                    }
                                    else {
                                        imgLabels_row[c] = 0;
                                        imgLabels_row[c + 1] = 0;
                                        imgLabels_row_fol[c] = 0;
                                        imgLabels_row_fol[c + 1] = 0;
                                        sop_(r, c, 0);
                                        sop_(r, c + 1, 0);
                                        sop_(r + 1, c, 0);
                                        sop_(r + 1, c + 1, 0);
                                    }
                                }
                            }//END case 4
                        }
                    }
                }
            }
        };

        inline static
        void mergeLabels(const cv::Mat& img, cv::Mat& imgLabels, LabelT *P, int *chunksSizeAndLabels){

                // Merge Mask
                // +---+---+---+
                // |P -|Q -|R -|
                // |- -|- -|- -|
                // +---+---+---+
                //     |X -|
                //     |- -|
                //     +---+
                const int w = imgLabels.cols, h = imgLabels.rows;

                for (int r = chunksSizeAndLabels[0]; r < h; r = chunksSizeAndLabels[r]){

                    LabelT * const imgLabels_row = imgLabels.ptr<LabelT>(r);
                    LabelT * const  imgLabels_row_prev_prev = (LabelT *)(((char *)imgLabels_row) - imgLabels.step.p[0] - imgLabels.step.p[0]);
                    const PixelT * const img_row = img.ptr<PixelT>(r);
                    const PixelT * const img_row_prev = (PixelT *)(((char *)img_row) - img.step.p[0]);

                    for (int c = 0; c < w; c += 2){

#define condition_x imgLabels_row[c] > 0
#define condition_pppr c > 1 && imgLabels_row_prev_prev[c - 2] > 0
#define condition_qppr imgLabels_row_prev_prev[c] > 0
#define condition_qppr1 c < w - 1
#define condition_qppr2 c < w
#define condition_rppr c < w - 2 && imgLabels_row_prev_prev[c + 2] > 0

                        if (condition_x){
                            if (condition_pppr){
                                //check in img
                                if (img_row[c] > 0 && img_row_prev[c - 1] > 0)
                                    //assign the same label
                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row[c]);
                            }
                            if (condition_qppr){
                                if (condition_qppr1){
                                    if ((img_row[c] > 0 && img_row_prev[c] > 0) || (img_row[c + 1] > 0 && img_row_prev[c] > 0) ||
                                        (img_row[c] > 0 && img_row_prev[c + 1] > 0) || (img_row[c + 1] > 0 && img_row_prev[c + 1] > 0)){
                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c]);
                                    }
                                }
                                else /*if (condition_qppr2)*/{
                                    if (img_row[c] > 0 && img_row_prev[c] > 0)
                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c]);
                                }
                            }
                            if (condition_rppr){
                                if (img_row[c + 1] > 0 && img_row_prev[c + 2] > 0)
                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c]);
                            }
                        }
                    }
                }
            }

        LabelT operator()(const cv::Mat& img, cv::Mat& imgLabels, int connectivity, StatsOp& sop){
            CV_Assert(img.rows == imgLabels.rows);
            CV_Assert(img.cols == imgLabels.cols);
            CV_Assert(connectivity == 8);

            const int h = img.rows;
            const int w = img.cols;

            //A quick and dirty upper bound for the maximum number of labels.
            //Following formula comes from the fact that a 2x2 block in 8-connectivity case
            //can never have more than 1 new label and 1 label for background.
            //Worst case image example pattern:
            //1 0 1 0 1...
            //0 0 0 0 0...
            //1 0 1 0 1...
            //............
            const size_t Plength = size_t(((h + 1) / 2) * size_t((w + 1) / 2)) + 1;

            //Array used to store info and labeled pixel by each thread.
            //Different threads affect different memory location of chunksSizeAndLabels
            const int chunksSizeAndLabelsSize = roundUp(h, 2);
            std::vector<int> chunksSizeAndLabels(chunksSizeAndLabelsSize);

            //Tree of labels
            std::vector<LabelT> P(Plength, 0);
            //First label is for background
            //P[0] = 0;

            cv::Range range2(0, divUp(h, 2));
            const double nParallelStripes = std::max(1, std::min(h / 2, getNumThreads()*4));

            //First scan
            cv::parallel_for_(range2, FirstScan(img, imgLabels, P.data(), chunksSizeAndLabels.data()), nParallelStripes);

            //merge labels of different chunks
            mergeLabels(img, imgLabels, P.data(), chunksSizeAndLabels.data());

            LabelT nLabels = 1;
            for (int i = 0; i < h; i = chunksSizeAndLabels[i]){
                CV_DbgAssert(i + 1 < chunksSizeAndLabelsSize);
                flattenL(P.data(), stripeFirstLabel8Connectivity<LabelT>(i, w), chunksSizeAndLabels[i + 1], nLabels);
            }

            //Array for statistics data
            std::vector<StatsOp> sopArray(h);
            sop.init(nLabels);

            //Second scan
            cv::parallel_for_(range2, SecondScan(img, imgLabels, P.data(), sop, sopArray.data(), nLabels), nParallelStripes);

            StatsOp::mergeStats(imgLabels, sopArray.data(), sop, nLabels);
            sop.finish();

            return nLabels;
        }
    };//End struct LabelingGranaParallel

    // Based on "Optimized  Block-based Connected Components Labeling with Decision Trees", Costantino Grana et al
    // Only for 8-connectivity
    template<typename LabelT, typename PixelT, typename StatsOp = NoOp >
    struct LabelingGrana{
        LabelT operator()(const cv::Mat& img, cv::Mat& imgLabels, int connectivity, StatsOp& sop){
            CV_Assert(img.rows == imgLabels.rows);
            CV_Assert(img.cols == imgLabels.cols);
            CV_Assert(connectivity == 8);

            const int h = img.rows;
            const int w = img.cols;

            //A quick and dirty upper bound for the maximum number of labels.
            //Following formula comes from the fact that a 2x2 block in 8-connectivity case
            //can never have more than 1 new label and 1 label for background.
            //Worst case image example pattern:
            //1 0 1 0 1...
            //0 0 0 0 0...
            //1 0 1 0 1...
            //............
            const size_t Plength = size_t(((h + 1) / 2) * size_t((w + 1) / 2)) + 1;

            std::vector<LabelT> P_(Plength, 0);
            LabelT *P = P_.data();
            //P[0] = 0;
            LabelT lunique = 1;

            // First scan
            for (int r = 0; r < h; r += 2) {
                // Get rows pointer
                const PixelT * const img_row = img.ptr<PixelT>(r);
                const PixelT * const img_row_prev = (PixelT *)(((char *)img_row) - img.step.p[0]);
                const PixelT * const img_row_prev_prev = (PixelT *)(((char *)img_row_prev) - img.step.p[0]);
                const PixelT * const img_row_fol = (PixelT *)(((char *)img_row) + img.step.p[0]);
                LabelT * const imgLabels_row = imgLabels.ptr<LabelT>(r);
                LabelT * const imgLabels_row_prev_prev = (LabelT *)(((char *)imgLabels_row) - imgLabels.step.p[0] - imgLabels.step.p[0]);
                for (int c = 0; c < w; c += 2) {

                    // We work with 2x2 blocks
                    // +-+-+-+
                    // |P|Q|R|
                    // +-+-+-+
                    // |S|X|
                    // +-+-+

                    // The pixels are named as follows
                    // +---+---+---+
                    // |a b|c d|e f|
                    // |g h|i j|k l|
                    // +---+---+---+
                    // |m n|o p|
                    // |q r|s t|
                    // +---+---+

                    // Pixels a, f, l, q are not needed, since we need to understand the
                    // the connectivity between these blocks and those pixels only metter
                    // when considering the outer connectivities

                    // A bunch of defines used to check if the pixels are foreground,
                    // without going outside the image limits.
#define condition_b c-1>=0 && r-2>=0 && img_row_prev_prev[c-1]>0
#define condition_c r-2>=0 && img_row_prev_prev[c]>0
#define condition_d c+1<w&& r-2>=0 && img_row_prev_prev[c+1]>0
#define condition_e c+2<w  && r-1>=0 && img_row_prev[c-1]>0

#define condition_g c-2>=0 && r-1>=0 && img_row_prev[c-2]>0
#define condition_h c-1>=0 && r-1>=0 && img_row_prev[c-1]>0
#define condition_i r-1>=0 && img_row_prev[c]>0
#define condition_j c+1<w && r-1>=0 && img_row_prev[c+1]>0
#define condition_k c+2<w && r-1>=0 && img_row_prev[c+2]>0

#define condition_m c-2>=0 && img_row[c-2]>0
#define condition_n c-1>=0 && img_row[c-1]>0
#define condition_o img_row[c]>0
#define condition_p c+1<w && img_row[c+1]>0

#define condition_r c-1>=0 && r+1<h && img_row_fol[c-1]>0
#define condition_s r+1<h && img_row_fol[c]>0
#define condition_t c+1<w && r+1<h && img_row_fol[c+1]>0

                    // This is a decision tree which allows to choose which action to
                    // perform, checking as few conditions as possible.
                    // Actions: the blocks label are provisionally stored in the top left
                    // pixel of the block in the labels image

                    if (condition_o) {
                        if (condition_n) {
                            if (condition_j) {
                                if (condition_i) {
                                    //Action_6: Assign label of block S
                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                    continue;
                                }
                                else {
                                    if (condition_c) {
                                        if (condition_h) {
                                            //Action_6: Assign label of block S
                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                            continue;
                                        }
                                        else {
                                            if (condition_g) {
                                                if (condition_b) {
                                                    //Action_6: Assign label of block S
                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                    continue;
                                                }
                                                else {
                                                    //Action_11: Merge labels of block Q and S
                                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                    continue;
                                                }
                                            }
                                            else {
                                                //Action_11: Merge labels of block Q and S
                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                continue;
                                            }
                                        }
                                    }
                                    else {
                                        //Action_11: Merge labels of block Q and S
                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                        continue;
                                    }
                                }
                            }
                            else {
                                if (condition_p) {
                                    if (condition_k) {
                                        if (condition_d) {
                                            if (condition_i) {
                                                //Action_6: Assign label of block S
                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                continue;
                                            }
                                            else {
                                                if (condition_c) {
                                                    if (condition_h) {
                                                        //Action_6: Assign label of block S
                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                        continue;
                                                    }
                                                    else {
                                                        if (condition_g) {
                                                            if (condition_b) {
                                                                //Action_6: Assign label of block S
                                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                                continue;
                                                            }
                                                            else {
                                                                //Action_12: Merge labels of block R and S
                                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                        else {
                                                            //Action_12: Merge labels of block R and S
                                                            imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                }
                                                else {
                                                    //Action_12: Merge labels of block R and S
                                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                    continue;
                                                }
                                            }
                                        }
                                        else {
                                            //Action_12: Merge labels of block R and S
                                            imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                            continue;
                                        }
                                    }
                                    else {
                                        //Action_6: Assign label of block S
                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                        continue;
                                    }
                                }
                                else {
                                    //Action_6: Assign label of block S
                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                    continue;
                                }
                            }
                        }
                        else {
                            if (condition_r) {
                                if (condition_j) {
                                    if (condition_m) {
                                        if (condition_h) {
                                            if (condition_i) {
                                                //Action_6: Assign label of block S
                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                continue;
                                            }
                                            else {
                                                if (condition_c) {
                                                    //Action_6: Assign label of block S
                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                    continue;
                                                }
                                                else {
                                                    //Action_11: Merge labels of block Q and S
                                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                    continue;
                                                }
                                            }
                                        }
                                        else {
                                            if (condition_g) {
                                                if (condition_b) {
                                                    if (condition_i) {
                                                        //Action_6: Assign label of block S
                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                        continue;
                                                    }
                                                    else {
                                                        if (condition_c) {
                                                            //Action_6: Assign label of block S
                                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                                            continue;
                                                        }
                                                        else {
                                                            //Action_11: Merge labels of block Q and S
                                                            imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                }
                                                else {
                                                    //Action_11: Merge labels of block Q and S
                                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                    continue;
                                                }
                                            }
                                            else {
                                                //Action_11: Merge labels of block Q and S
                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                continue;
                                            }
                                        }
                                    }
                                    else {
                                        if (condition_i) {
                                            //Action_11: Merge labels of block Q and S
                                            imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                            continue;
                                        }
                                        else {
                                            if (condition_h) {
                                                if (condition_c) {
                                                    //Action_11: Merge labels of block Q and S
                                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                    continue;
                                                }
                                                else {
                                                    //Action_14: Merge labels of block P, Q and S
                                                    imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c]), imgLabels_row[c - 2]);
                                                    continue;
                                                }
                                            }
                                            else {
                                                //Action_11: Merge labels of block Q and S
                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                continue;
                                            }
                                        }
                                    }
                                }
                                else {
                                    if (condition_p) {
                                        if (condition_k) {
                                            if (condition_m) {
                                                if (condition_h) {
                                                    if (condition_d) {
                                                        if (condition_i) {
                                                            //Action_6: Assign label of block S
                                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                                            continue;
                                                        }
                                                        else {
                                                            if (condition_c) {
                                                                //Action_6: Assign label of block S
                                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                                continue;
                                                            }
                                                            else {
                                                                //Action_12: Merge labels of block R and S
                                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                    }
                                                    else {
                                                        //Action_12: Merge labels of block R and S
                                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                                else {
                                                    if (condition_d) {
                                                        if (condition_g) {
                                                            if (condition_b) {
                                                                if (condition_i) {
                                                                    //Action_6: Assign label of block S
                                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                                    continue;
                                                                }
                                                                else {
                                                                    if (condition_c) {
                                                                        //Action_6: Assign label of block S
                                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                                        continue;
                                                                    }
                                                                    else {
                                                                        //Action_12: Merge labels of block R and S
                                                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                        continue;
                                                                    }
                                                                }
                                                            }
                                                            else {
                                                                //Action_12: Merge labels of block R and S
                                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                        else {
                                                            //Action_12: Merge labels of block R and S
                                                            imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        if (condition_i) {
                                                            if (condition_g) {
                                                                if (condition_b) {
                                                                    //Action_12: Merge labels of block R and S
                                                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                                else {
                                                                    //Action_16: labels of block Q, R and S
                                                                    imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                            else {
                                                                //Action_16: labels of block Q, R and S
                                                                imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                        else {
                                                            //Action_12: Merge labels of block R and S
                                                            imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                }
                                            }
                                            else {
                                                if (condition_i) {
                                                    if (condition_d) {
                                                        //Action_12: Merge labels of block R and S
                                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                    else {
                                                        //Action_16: labels of block Q, R and S
                                                        imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                                else {
                                                    if (condition_h) {
                                                        if (condition_d) {
                                                            if (condition_c) {
                                                                //Action_12: Merge labels of block R and S
                                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                            else {
                                                                //Action_15: Merge labels of block P, R and S
                                                                imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                        else {
                                                            //Action_15: Merge labels of block P, R and S
                                                            imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        //Action_12: Merge labels of block R and S
                                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                            }
                                        }
                                        else {
                                            if (condition_h) {
                                                if (condition_m) {
                                                    //Action_6: Assign label of block S
                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                    continue;
                                                }
                                                else {
                                                    // ACTION_9 Merge labels of block P and S
                                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row[c - 2]);
                                                    continue;
                                                }
                                            }
                                            else {
                                                if (condition_i) {
                                                    if (condition_m) {
                                                        if (condition_g) {
                                                            if (condition_b) {
                                                                //Action_6: Assign label of block S
                                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                                continue;
                                                            }
                                                            else {
                                                                //Action_11: Merge labels of block Q and S
                                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                        else {
                                                            //Action_11: Merge labels of block Q and S
                                                            imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        //Action_11: Merge labels of block Q and S
                                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                                else {
                                                    //Action_6: Assign label of block S
                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                    continue;
                                                }
                                            }
                                        }
                                    }
                                    else {
                                        if (condition_h) {
                                            if (condition_m) {
                                                //Action_6: Assign label of block S
                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                continue;
                                            }
                                            else {
                                                // ACTION_9 Merge labels of block P and S
                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row[c - 2]);
                                                continue;
                                            }
                                        }
                                        else {
                                            if (condition_i) {
                                                if (condition_m) {
                                                    if (condition_g) {
                                                        if (condition_b) {
                                                            //Action_6: Assign label of block S
                                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                                            continue;
                                                        }
                                                        else {
                                                            //Action_11: Merge labels of block Q and S
                                                            imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        //Action_11: Merge labels of block Q and S
                                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                                else {
                                                    //Action_11: Merge labels of block Q and S
                                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                    continue;
                                                }
                                            }
                                            else {
                                                //Action_6: Assign label of block S
                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                continue;
                                            }
                                        }
                                    }
                                }
                            }
                            else {
                                if (condition_j) {
                                    if (condition_i) {
                                        //Action_4: Assign label of block Q
                                        imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                        continue;
                                    }
                                    else {
                                        if (condition_h) {
                                            if (condition_c) {
                                                //Action_4: Assign label of block Q
                                                imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                                continue;
                                            }
                                            else {
                                                //Action_7: Merge labels of block P and Q
                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c]);
                                                continue;
                                            }
                                        }
                                        else {
                                            //Action_4: Assign label of block Q
                                            imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                            continue;
                                        }
                                    }
                                }
                                else {
                                    if (condition_p) {
                                        if (condition_k) {
                                            if (condition_i) {
                                                if (condition_d) {
                                                    //Action_5: Assign label of block R
                                                    imgLabels_row[c] = imgLabels_row_prev_prev[c + 2];
                                                    continue;
                                                }
                                                else {
                                                    // ACTION_10 Merge labels of block Q and R
                                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]);
                                                    continue;
                                                }
                                            }
                                            else {
                                                if (condition_h) {
                                                    if (condition_d) {
                                                        if (condition_c) {
                                                            //Action_5: Assign label of block R
                                                            imgLabels_row[c] = imgLabels_row_prev_prev[c + 2];
                                                            continue;
                                                        }
                                                        else {
                                                            //Action_8: Merge labels of block P and R
                                                            imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c + 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        //Action_8: Merge labels of block P and R
                                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c - 2], imgLabels_row_prev_prev[c + 2]);
                                                        continue;
                                                    }
                                                }
                                                else {
                                                    //Action_5: Assign label of block R
                                                    imgLabels_row[c] = imgLabels_row_prev_prev[c + 2];
                                                    continue;
                                                }
                                            }
                                        }
                                        else {
                                            if (condition_i) {
                                                //Action_4: Assign label of block Q
                                                imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                                continue;
                                            }
                                            else {
                                                if (condition_h) {
                                                    //Action_3: Assign label of block P
                                                    imgLabels_row[c] = imgLabels_row_prev_prev[c - 2];
                                                    continue;
                                                }
                                                else {
                                                    //Action_2: New label (the block has foreground pixels and is not connected to anything else)
                                                    imgLabels_row[c] = lunique;
                                                    P[lunique] = lunique;
                                                    lunique = lunique + 1;
                                                    continue;
                                                }
                                            }
                                        }
                                    }
                                    else {
                                        if (condition_i) {
                                            //Action_4: Assign label of block Q
                                            imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                            continue;
                                        }
                                        else {
                                            if (condition_h) {
                                                //Action_3: Assign label of block P
                                                imgLabels_row[c] = imgLabels_row_prev_prev[c - 2];
                                                continue;
                                            }
                                            else {
                                                //Action_2: New label (the block has foreground pixels and is not connected to anything else)
                                                imgLabels_row[c] = lunique;
                                                P[lunique] = lunique;
                                                lunique = lunique + 1;
                                                continue;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    else {
                        if (condition_s) {
                            if (condition_p) {
                                if (condition_n) {
                                    if (condition_j) {
                                        if (condition_i) {
                                            //Action_6: Assign label of block S
                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                            continue;
                                        }
                                        else {
                                            if (condition_c) {
                                                if (condition_h) {
                                                    //Action_6: Assign label of block S
                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                    continue;
                                                }
                                                else {
                                                    if (condition_g) {
                                                        if (condition_b) {
                                                            //Action_6: Assign label of block S
                                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                                            continue;
                                                        }
                                                        else {
                                                            //Action_11: Merge labels of block Q and S
                                                            imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        //Action_11: Merge labels of block Q and S
                                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                            }
                                            else {
                                                //Action_11: Merge labels of block Q and S
                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                continue;
                                            }
                                        }
                                    }
                                    else {
                                        if (condition_k) {
                                            if (condition_d) {
                                                if (condition_i) {
                                                    //Action_6: Assign label of block S
                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                    continue;
                                                }
                                                else {
                                                    if (condition_c) {
                                                        if (condition_h) {
                                                            //Action_6: Assign label of block S
                                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                                            continue;
                                                        }
                                                        else {
                                                            if (condition_g) {
                                                                if (condition_b) {
                                                                    //Action_6: Assign label of block S
                                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                                    continue;
                                                                }
                                                                else {
                                                                    //Action_12: Merge labels of block R and S
                                                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                            else {
                                                                //Action_12: Merge labels of block R and S
                                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                    }
                                                    else {
                                                        //Action_12: Merge labels of block R and S
                                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                            }
                                            else {
                                                //Action_12: Merge labels of block R and S
                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                continue;
                                            }
                                        }
                                        else {
                                            //Action_6: Assign label of block S
                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                            continue;
                                        }
                                    }
                                }
                                else {
                                    if (condition_r) {
                                        if (condition_j) {
                                            if (condition_m) {
                                                if (condition_h) {
                                                    if (condition_i) {
                                                        //Action_6: Assign label of block S
                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                        continue;
                                                    }
                                                    else {
                                                        if (condition_c) {
                                                            //Action_6: Assign label of block S
                                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                                            continue;
                                                        }
                                                        else {
                                                            //Action_11: Merge labels of block Q and S
                                                            imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                }
                                                else {
                                                    if (condition_g) {
                                                        if (condition_b) {
                                                            if (condition_i) {
                                                                //Action_6: Assign label of block S
                                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                                continue;
                                                            }
                                                            else {
                                                                if (condition_c) {
                                                                    //Action_6: Assign label of block S
                                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                                    continue;
                                                                }
                                                                else {
                                                                    //Action_11: Merge labels of block Q and S
                                                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                        }
                                                        else {
                                                            //Action_11: Merge labels of block Q and S
                                                            imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        //Action_11: Merge labels of block Q and S
                                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                            }
                                            else {
                                                //Action_11: Merge labels of block Q and S
                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                continue;
                                            }
                                        }
                                        else {
                                            if (condition_k) {
                                                if (condition_d) {
                                                    if (condition_m) {
                                                        if (condition_h) {
                                                            if (condition_i) {
                                                                //Action_6: Assign label of block S
                                                                imgLabels_row[c] = imgLabels_row[c - 2];
                                                                continue;
                                                            }
                                                            else {
                                                                if (condition_c) {
                                                                    //Action_6: Assign label of block S
                                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                                    continue;
                                                                }
                                                                else {
                                                                    //Action_12: Merge labels of block R and S
                                                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                        }
                                                        else {
                                                            if (condition_g) {
                                                                if (condition_b) {
                                                                    if (condition_i) {
                                                                        //Action_6: Assign label of block S
                                                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                                                        continue;
                                                                    }
                                                                    else {
                                                                        if (condition_c) {
                                                                            //Action_6: Assign label of block S
                                                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                                                            continue;
                                                                        }
                                                                        else {
                                                                            //Action_12: Merge labels of block R and S
                                                                            imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                            continue;
                                                                        }
                                                                    }
                                                                }
                                                                else {
                                                                    //Action_12: Merge labels of block R and S
                                                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                            else {
                                                                //Action_12: Merge labels of block R and S
                                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                    }
                                                    else {
                                                        //Action_12: Merge labels of block R and S
                                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                                else {
                                                    if (condition_i) {
                                                        if (condition_m) {
                                                            if (condition_h) {
                                                                //Action_12: Merge labels of block R and S
                                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                            else {
                                                                if (condition_g) {
                                                                    if (condition_b) {
                                                                        //Action_12: Merge labels of block R and S
                                                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                                        continue;
                                                                    }
                                                                    else {
                                                                        //Action_16: labels of block Q, R and S
                                                                        imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                                        continue;
                                                                    }
                                                                }
                                                                else {
                                                                    //Action_16: labels of block Q, R and S
                                                                    imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                        }
                                                        else {
                                                            //Action_16: labels of block Q, R and S
                                                            imgLabels_row[c] = set_union(P, set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]), imgLabels_row[c - 2]);
                                                            continue;
                                                        }
                                                    }
                                                    else {
                                                        //Action_12: Merge labels of block R and S
                                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c + 2], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                            }
                                            else {
                                                if (condition_i) {
                                                    if (condition_m) {
                                                        if (condition_h) {
                                                            //Action_6: Assign label of block S
                                                            imgLabels_row[c] = imgLabels_row[c - 2];
                                                            continue;
                                                        }
                                                        else {
                                                            if (condition_g) {
                                                                if (condition_b) {
                                                                    //Action_6: Assign label of block S
                                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                                    continue;
                                                                }
                                                                else {
                                                                    //Action_11: Merge labels of block Q and S
                                                                    imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                                    continue;
                                                                }
                                                            }
                                                            else {
                                                                //Action_11: Merge labels of block Q and S
                                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                                continue;
                                                            }
                                                        }
                                                    }
                                                    else {
                                                        //Action_11: Merge labels of block Q and S
                                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row[c - 2]);
                                                        continue;
                                                    }
                                                }
                                                else {
                                                    //Action_6: Assign label of block S
                                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                                    continue;
                                                }
                                            }
                                        }
                                    }
                                    else {
                                        if (condition_j) {
                                            //Action_4: Assign label of block Q
                                            imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                            continue;
                                        }
                                        else {
                                            if (condition_k) {
                                                if (condition_i) {
                                                    if (condition_d) {
                                                        //Action_5: Assign label of block R
                                                        imgLabels_row[c] = imgLabels_row_prev_prev[c + 2];
                                                        continue;
                                                    }
                                                    else {
                                                        // ACTION_10 Merge labels of block Q and R
                                                        imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]);
                                                        continue;
                                                    }
                                                }
                                                else {
                                                    //Action_5: Assign label of block R
                                                    imgLabels_row[c] = imgLabels_row_prev_prev[c + 2];
                                                    continue;
                                                }
                                            }
                                            else {
                                                if (condition_i) {
                                                    //Action_4: Assign label of block Q
                                                    imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                                    continue;
                                                }
                                                else {
                                                    //Action_2: New label (the block has foreground pixels and is not connected to anything else)
                                                    imgLabels_row[c] = lunique;
                                                    P[lunique] = lunique;
                                                    lunique = lunique + 1;
                                                    continue;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            else {
                                if (condition_r) {
                                    //Action_6: Assign label of block S
                                    imgLabels_row[c] = imgLabels_row[c - 2];
                                    continue;
                                }
                                else {
                                    if (condition_n) {
                                        //Action_6: Assign label of block S
                                        imgLabels_row[c] = imgLabels_row[c - 2];
                                        continue;
                                    }
                                    else {
                                        //Action_2: New label (the block has foreground pixels and is not connected to anything else)
                                        imgLabels_row[c] = lunique;
                                        P[lunique] = lunique;
                                        lunique = lunique + 1;
                                        continue;
                                    }
                                }
                            }
                        }
                        else {
                            if (condition_p) {
                                if (condition_j) {
                                    //Action_4: Assign label of block Q
                                    imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                    continue;
                                }
                                else {
                                    if (condition_k) {
                                        if (condition_i) {
                                            if (condition_d) {
                                                //Action_5: Assign label of block R
                                                imgLabels_row[c] = imgLabels_row_prev_prev[c + 2];
                                                continue;
                                            }
                                            else {
                                                // ACTION_10 Merge labels of block Q and R
                                                imgLabels_row[c] = set_union(P, imgLabels_row_prev_prev[c], imgLabels_row_prev_prev[c + 2]);
                                                continue;
                                            }
                                        }
                                        else {
                                            //Action_5: Assign label of block R
                                            imgLabels_row[c] = imgLabels_row_prev_prev[c + 2];
                                            continue;
                                        }
                                    }
                                    else {
                                        if (condition_i) {
                                            //Action_4: Assign label of block Q
                                            imgLabels_row[c] = imgLabels_row_prev_prev[c];
                                            continue;
                                        }
                                        else {
                                            //Action_2: New label (the block has foreground pixels and is not connected to anything else)
                                            imgLabels_row[c] = lunique;
                                            P[lunique] = lunique;
                                            lunique = lunique + 1;
                                            continue;
                                        }
                                    }
                                }
                            }
                            else {
                                if (condition_t) {
                                    //Action_2: New label (the block has foreground pixels and is not connected to anything else)
                                    imgLabels_row[c] = lunique;
                                    P[lunique] = lunique;
                                    lunique = lunique + 1;
                                    continue;
                                }
                                else {
                                    // Action_1: No action (the block has no foreground pixels)
                                    imgLabels_row[c] = 0;
                                    continue;
                                }
                            }
                        }
                    }
                }

            }

            // Second scan + analysis
            LabelT nLabels = flattenL(P, lunique);
            sop.init(nLabels);

            if (imgLabels.rows & 1){
                if (imgLabels.cols & 1){
                    //Case 1: both rows and cols odd
                    for (int r = 0; r < imgLabels.rows; r += 2) {
                        // Get rows pointer
                        const PixelT * const img_row = img.ptr<PixelT>(r);
                        const PixelT * const img_row_fol = (PixelT *)(((char *)img_row) + img.step.p[0]);
                        LabelT * const imgLabels_row = imgLabels.ptr<LabelT>(r);
                        LabelT * const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels.step.p[0]);

                        for (int c = 0; c < imgLabels.cols; c += 2) {
                            LabelT iLabel = imgLabels_row[c];
                            if (iLabel > 0) {
                                iLabel = P[iLabel];
                                if (img_row[c] > 0){
                                    imgLabels_row[c] = iLabel;
                                    sop(r, c, iLabel);
                                }
                                else{
                                    imgLabels_row[c] = 0;
                                    sop(r, c, 0);
                                }
                                if (c + 1 < imgLabels.cols) {
                                    if (img_row[c + 1] > 0){
                                        imgLabels_row[c + 1] = iLabel;
                                        sop(r, c + 1, iLabel);
                                    }
                                    else{
                                        imgLabels_row[c + 1] = 0;
                                        sop(r, c + 1, 0);
                                    }
                                    if (r + 1 < imgLabels.rows) {
                                        if (img_row_fol[c] > 0){
                                            imgLabels_row_fol[c] = iLabel;
                                            sop(r + 1, c, iLabel);
                                        }
                                        else{
                                            imgLabels_row_fol[c] = 0;
                                            sop(r + 1, c, 0);
                                        }
                                        if (img_row_fol[c + 1] > 0){
                                            imgLabels_row_fol[c + 1] = iLabel;
                                            sop(r + 1, c + 1, iLabel);
                                        }
                                        else{
                                            imgLabels_row_fol[c + 1] = 0;
                                            sop(r + 1, c + 1, 0);
                                        }
                                    }
                                }
                                else if (r + 1 < imgLabels.rows) {
                                    if (img_row_fol[c] > 0){
                                        imgLabels_row_fol[c] = iLabel;
                                        sop(r + 1, c, iLabel);
                                    }
                                    else{
                                        imgLabels_row_fol[c] = 0;
                                        sop(r + 1, c, 0);
                                    }
                                }
                            }
                            else {
                                imgLabels_row[c] = 0;
                                sop(r, c, 0);
                                if (c + 1 < imgLabels.cols) {
                                    imgLabels_row[c + 1] = 0;
                                    sop(r, c + 1, 0);
                                    if (r + 1 < imgLabels.rows) {
                                        imgLabels_row_fol[c] = 0;
                                        imgLabels_row_fol[c + 1] = 0;
                                        sop(r + 1, c, 0);
                                        sop(r + 1, c + 1, 0);
                                    }
                                }
                                else if (r + 1 < imgLabels.rows) {
                                    imgLabels_row_fol[c] = 0;
                                    sop(r + 1, c, 0);
                                }
                            }
                        }
                    }
                }//END Case 1
                else{
                    //Case 2: only rows odd
                    for (int r = 0; r < imgLabels.rows; r += 2) {
                        // Get rows pointer
                        const PixelT * const img_row = img.ptr<PixelT>(r);
                        const PixelT * const img_row_fol = (PixelT *)(((char *)img_row) + img.step.p[0]);
                        LabelT * const imgLabels_row = imgLabels.ptr<LabelT>(r);
                        LabelT * const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels.step.p[0]);

                        for (int c = 0; c < imgLabels.cols; c += 2) {
                            LabelT iLabel = imgLabels_row[c];
                            if (iLabel > 0) {
                                iLabel = P[iLabel];
                                if (img_row[c] > 0){
                                    imgLabels_row[c] = iLabel;
                                    sop(r, c, iLabel);
                                }
                                else{
                                    imgLabels_row[c] = 0;
                                    sop(r, c, 0);
                                }
                                if (img_row[c + 1] > 0){
                                    imgLabels_row[c + 1] = iLabel;
                                    sop(r, c + 1, iLabel);
                                }
                                else{
                                    imgLabels_row[c + 1] = 0;
                                    sop(r, c + 1, 0);
                                }
                                if (r + 1 < imgLabels.rows) {
                                    if (img_row_fol[c] > 0){
                                        imgLabels_row_fol[c] = iLabel;
                                        sop(r + 1, c, iLabel);
                                    }
                                    else{
                                        imgLabels_row_fol[c] = 0;
                                        sop(r + 1, c, 0);
                                    }
                                    if (img_row_fol[c + 1] > 0){
                                        imgLabels_row_fol[c + 1] = iLabel;
                                        sop(r + 1, c + 1, iLabel);
                                    }
                                    else{
                                        imgLabels_row_fol[c + 1] = 0;
                                        sop(r + 1, c + 1, 0);
                                    }
                                }
                            }
                            else {
                                imgLabels_row[c] = 0;
                                imgLabels_row[c + 1] = 0;
                                sop(r, c, 0);
                                sop(r, c + 1, 0);
                                if (r + 1 < imgLabels.rows) {
                                    imgLabels_row_fol[c] = 0;
                                    imgLabels_row_fol[c + 1] = 0;
                                    sop(r + 1, c, 0);
                                    sop(r + 1, c + 1, 0);
                                }
                            }
                        }
                    }
                }// END Case 2
            }
            else{
                if (imgLabels.cols & 1){
                    //Case 3: only cols odd
                    for (int r = 0; r < imgLabels.rows; r += 2) {
                        // Get rows pointer
                        const PixelT * const img_row = img.ptr<PixelT>(r);
                        const PixelT * const img_row_fol = (PixelT *)(((char *)img_row) + img.step.p[0]);
                        LabelT * const imgLabels_row = imgLabels.ptr<LabelT>(r);
                        LabelT * const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels.step.p[0]);

                        for (int c = 0; c < imgLabels.cols; c += 2) {
                            LabelT iLabel = imgLabels_row[c];
                            if (iLabel > 0) {
                                iLabel = P[iLabel];
                                if (img_row[c] > 0){
                                    imgLabels_row[c] = iLabel;
                                    sop(r, c, iLabel);
                                }
                                else{
                                    imgLabels_row[c] = 0;
                                    sop(r, c, 0);
                                }
                                if (img_row_fol[c] > 0){
                                    imgLabels_row_fol[c] = iLabel;
                                    sop(r + 1, c, iLabel);
                                }
                                else{
                                    imgLabels_row_fol[c] = 0;
                                    sop(r + 1, c, 0);
                                }
                                if (c + 1 < imgLabels.cols) {
                                    if (img_row[c + 1] > 0){
                                        imgLabels_row[c + 1] = iLabel;
                                        sop(r, c + 1, iLabel);
                                    }
                                    else{
                                        imgLabels_row[c + 1] = 0;
                                        sop(r, c + 1, 0);
                                    }
                                    if (img_row_fol[c + 1] > 0){
                                        imgLabels_row_fol[c + 1] = iLabel;
                                        sop(r + 1, c + 1, iLabel);
                                    }
                                    else{
                                        imgLabels_row_fol[c + 1] = 0;
                                        sop(r + 1, c + 1, 0);
                                    }
                                }
                            }
                            else{
                                imgLabels_row[c] = 0;
                                imgLabels_row_fol[c] = 0;
                                sop(r, c, 0);
                                sop(r + 1, c, 0);
                                if (c + 1 < imgLabels.cols) {
                                    imgLabels_row[c + 1] = 0;
                                    imgLabels_row_fol[c + 1] = 0;
                                    sop(r, c + 1, 0);
                                    sop(r + 1, c + 1, 0);
                                }
                            }
                        }
                    }
                }// END case 3
                else{
                    //Case 4: nothing odd
                    for (int r = 0; r < imgLabels.rows; r += 2) {
                        // Get rows pointer
                        const PixelT * const img_row = img.ptr<PixelT>(r);
                        const PixelT * const img_row_fol = (PixelT *)(((char *)img_row) + img.step.p[0]);
                        LabelT * const imgLabels_row = imgLabels.ptr<LabelT>(r);
                        LabelT * const imgLabels_row_fol = (LabelT *)(((char *)imgLabels_row) + imgLabels.step.p[0]);

                        for (int c = 0; c < imgLabels.cols; c += 2) {
                            LabelT iLabel = imgLabels_row[c];
                            if (iLabel > 0) {
                                iLabel = P[iLabel];
                                if (img_row[c] > 0){
                                    imgLabels_row[c] = iLabel;
                                    sop(r, c, iLabel);
                                }
                                else{
                                    imgLabels_row[c] = 0;
                                    sop(r, c, 0);
                                }
                                if (img_row[c + 1] > 0){
                                    imgLabels_row[c + 1] = iLabel;
                                    sop(r, c + 1, iLabel);
                                }
                                else{
                                    imgLabels_row[c + 1] = 0;
                                    sop(r, c + 1, 0);
                                }
                                if (img_row_fol[c] > 0){
                                    imgLabels_row_fol[c] = iLabel;
                                    sop(r + 1, c, iLabel);
                                }
                                else{
                                    imgLabels_row_fol[c] = 0;
                                    sop(r + 1, c, 0);
                                }
                                if (img_row_fol[c + 1] > 0){
                                    imgLabels_row_fol[c + 1] = iLabel;
                                    sop(r + 1, c + 1, iLabel);
                                }
                                else{
                                    imgLabels_row_fol[c + 1] = 0;
                                    sop(r + 1, c + 1, 0);
                                }
                            }
                            else {
                                imgLabels_row[c] = 0;
                                imgLabels_row[c + 1] = 0;
                                imgLabels_row_fol[c] = 0;
                                imgLabels_row_fol[c + 1] = 0;
                                sop(r, c, 0);
                                sop(r, c + 1, 0);
                                sop(r + 1, c, 0);
                                sop(r + 1, c + 1, 0);
                            }
                        }
                    }
                }//END case 4
            }

            sop.finish();

            return nLabels;

        }   //End function LabelingGrana operator()
    };//End struct LabelingGrana
    }//end namespace connectedcomponents

    //L's type must have an appropriate depth for the number of pixels in I
    template<typename StatsOp>
    static
    int connectedComponents_sub1(const cv::Mat& I, cv::Mat& L, int connectivity, int ccltype, StatsOp& sop){
        CV_Assert(L.channels() == 1 && I.channels() == 1);
        CV_Assert(connectivity == 8 || connectivity == 4);
        CV_Assert(ccltype == CCL_GRANA || ccltype == CCL_WU || ccltype == CCL_DEFAULT);

        int lDepth = L.depth();
        int iDepth = I.depth();
        const char *currentParallelFramework = cv::currentParallelFramework();
        const int nThreads = cv::getNumThreads();

        CV_Assert(iDepth == CV_8U || iDepth == CV_8S);

        //Run parallel labeling only if the rows of the image are at least twice the number of available threads
        const bool is_parallel = currentParallelFramework != NULL && nThreads > 1 && L.rows / nThreads >= 2;

        if (ccltype == CCL_WU || connectivity == 4){
            // Wu algorithm is used
            using connectedcomponents::LabelingWu;
            using connectedcomponents::LabelingWuParallel;
            //warn if L's depth is not sufficient?
            if (lDepth == CV_8U){
                //Not supported yet
            }
            else if (lDepth == CV_16U){
                return (int)LabelingWu<ushort, uchar, StatsOp>()(I, L, connectivity, sop);
            }
            else if (lDepth == CV_32S){
                //note that signed types don't really make sense here and not being able to use unsigned matters for scientific projects
                //OpenCV: how should we proceed?  .at<T> typechecks in debug mode
                if (!is_parallel)
                    return (int)LabelingWu<int, uchar, StatsOp>()(I, L, connectivity, sop);
                else
                    return (int)LabelingWuParallel<int, uchar, StatsOp>()(I, L, connectivity, sop);
            }
        }
        else if ((ccltype == CCL_GRANA || ccltype == CCL_DEFAULT) && connectivity == 8){
            // Grana algorithm is used
            using connectedcomponents::LabelingGrana;
            using connectedcomponents::LabelingGranaParallel;
            //warn if L's depth is not sufficient?
            if (lDepth == CV_8U){
                //Not supported yet
            }
            else if (lDepth == CV_16U){
                return (int)LabelingGrana<ushort, uchar, StatsOp>()(I, L, connectivity, sop);
            }
            else if (lDepth == CV_32S){
                //note that signed types don't really make sense here and not being able to use unsigned matters for scientific projects
                //OpenCV: how should we proceed?  .at<T> typechecks in debug mode
                if (!is_parallel)
                    return (int)LabelingGrana<int, uchar, StatsOp>()(I, L, connectivity, sop);
                else
                    return (int)LabelingGranaParallel<int, uchar, StatsOp>()(I, L, connectivity, sop);
            }
        }

        CV_Error(CV_StsUnsupportedFormat, "unsupported label/image type");
    }

}

// Simple wrapper to ensure binary and source compatibility (ABI)
int cv::connectedComponents(InputArray img_, OutputArray _labels, int connectivity, int ltype){
    return cv::connectedComponents(img_, _labels, connectivity, ltype, CCL_DEFAULT);
}

int cv::connectedComponents(InputArray img_, OutputArray _labels, int connectivity, int ltype, int ccltype){
    const cv::Mat img = img_.getMat();
    _labels.create(img.size(), CV_MAT_DEPTH(ltype));
    cv::Mat labels = _labels.getMat();
    connectedcomponents::NoOp sop;
    if (ltype == CV_16U){
        return connectedComponents_sub1(img, labels, connectivity, ccltype, sop);
    }
    else if (ltype == CV_32S){
        return connectedComponents_sub1(img, labels, connectivity, ccltype, sop);
    }
    else{
        CV_Error(CV_StsUnsupportedFormat, "the type of labels must be 16u or 32s");
    }
}

// Simple wrapper to ensure binary and source compatibility (ABI)
int cv::connectedComponentsWithStats(InputArray img_, OutputArray _labels, OutputArray statsv,
    OutputArray centroids, int connectivity, int ltype)
{
    return cv::connectedComponentsWithStats(img_, _labels, statsv, centroids, connectivity, ltype, CCL_DEFAULT);
}

int cv::connectedComponentsWithStats(InputArray img_, OutputArray _labels, OutputArray statsv,
    OutputArray centroids, int connectivity, int ltype, int ccltype)
{
    const cv::Mat img = img_.getMat();
    _labels.create(img.size(), CV_MAT_DEPTH(ltype));
    cv::Mat labels = _labels.getMat();
    connectedcomponents::CCStatsOp sop(statsv, centroids);
    if (ltype == CV_16U){
        return connectedComponents_sub1(img, labels, connectivity, ccltype, sop);
    }
    else if (ltype == CV_32S){
        return connectedComponents_sub1(img, labels, connectivity, ccltype, sop);
    }
    else{
        CV_Error(CV_StsUnsupportedFormat, "the type of labels must be 16u or 32s");
        return 0;
    }
}
