#include "SegmentationHandler.hh"

using namespace cv;
using namespace std;

void SegmentationHandler::regionBased(cv::Mat& src)
{
    int histogram[256] = { 0 };
    int pixelsCount = src.cols * src.rows;

    for (int y = 0; y < src.rows; y++)
    {
        for (int x = 0; x < src.cols; x++)
        {
            uchar value = src.at<uchar>(y, x);
            histogram[value]++;
        }
    }

    //double c = 0;
    double c = 0;
    double Mt = 0;

    double p[256] = { 0 };
    for (int i = 0; i < 256; i++)
    {
        p[i] = (double)histogram[i] / (double)pixelsCount;
        Mt += i * p[i];
    }

    int optimalTreshold1 = 0;
    int optimalTreshold2 = 0;
    int optimalTreshold3 = 0;

    double maxBetweenVar = 0;

    double w0 = 0;
    double m0 = 0;
    double c0 = 0;
    double p0 = 0;

    double w1 = 0;
    double m1 = 0;
    double c1 = 0;
    double p1 = 0;

    double w2 = 0;
    double m2 = 0;
    double c2 = 0;
    double p2 = 0;
    for (int tr1 = 0; tr1 < 256; tr1++)
    {
        p0 += p[tr1];
        w0 += (tr1 * p[tr1]);
        if (p0 != 0)
        {
            m0 = w0 / p0;
        }

        c0 = p0 * (m0 - Mt) * (m0 - Mt);

        c1 = 0;
        w1 = 0;
        m1 = 0;
        p1 = 0;
        for (int tr2 = tr1 + 1; tr2 < 256; tr2++)
        {

            p1 += p[tr2];
            w1 += (tr2 * p[tr2]);
            if (p1 != 0)
            {
                m1 = w1 / p1;
            }

            c1 = p1 * (m1 - Mt) * (m1 - Mt);


            c2 = 0;
            w2 = 0;
            m2 = 0;
            p2 = 0;
            for (int tr3 = tr2 + 1; tr3 < 256; tr3++)
            {

                p2 += p[tr3];
                w2 += (tr3 * p[tr3]);
                if (p2 != 0)
                {
                    m2 = w2 / p2;
                }

                c2 = p2 * (m2 - Mt) * (m2 - Mt);

                double p3 = 1 - (p0 + p1 + p2);
                double w3 = Mt - (w0 + w1 + w2);
                double m3 = w3 / p3;
                double c3 = p3 * (m3 - Mt) * (m3 - Mt);

                double c = c0 + c1 + c2 + c3;

                if (maxBetweenVar < c)
                {
                    maxBetweenVar = c;
                    optimalTreshold1 = tr1;
                    optimalTreshold2 = tr2;
                    optimalTreshold3 = tr3;
                }
            }
        }
    }

    //std::cout << optimalThreshold[0] << " " << optimalThreshold[1] << " " << optimalThreshold[2] << std::endl;
    std::cout << optimalTreshold1 << " " << optimalTreshold2 << " " << optimalTreshold3 << std::endl;
}

void SegmentationHandler::watersched()
{
    Mat src = imread("cards.png");
   
    // Show source image
    imshow("Source Image", src);
    // Change the background from white to black, since that will help later to extract
    // better results during the use of Distance Transform
    for (int i = 0; i < src.rows; i++) {
        for (int j = 0; j < src.cols; j++) {
            if (src.at<Vec3b>(i, j) == Vec3b(255, 255, 255))
            {
                src.at<Vec3b>(i, j)[0] = 0;
                src.at<Vec3b>(i, j)[1] = 0;
                src.at<Vec3b>(i, j)[2] = 0;
            }
        }
    }
    // Show output image
    imshow("Black Background Image", src);
    // Create a kernel that we will use to sharpen our image
    Mat kernel = (Mat_<float>(3, 3) <<
        1, 1, 1,
        1, -8, 1,
        1, 1, 1); // an approximation of second derivative, a quite strong kernel
// do the laplacian filtering as it is
// well, we need to convert everything in something more deeper then CV_8U
// because the kernel has some negative values,
// and we can expect in general to have a Laplacian image with negative values
// BUT a 8bits unsigned int (the one we are working with) can contain values from 0 to 255
// so the possible negative number will be truncated
    Mat imgLaplacian;
    filter2D(src, imgLaplacian, CV_32F, kernel);
    Mat sharp;
    src.convertTo(sharp, CV_32F);
    Mat imgResult = sharp - imgLaplacian;
    // convert back to 8bits gray scale
    imgResult.convertTo(imgResult, CV_8UC3);
    imgLaplacian.convertTo(imgLaplacian, CV_8UC3);
    // imshow( "Laplace Filtered Image", imgLaplacian );
    imshow("New Sharped Image", imgResult);
    // Create binary image from source image
    Mat bw;
    cvtColor(imgResult, bw, COLOR_BGR2GRAY);
    threshold(bw, bw, 40, 255, THRESH_BINARY | THRESH_OTSU);
    imshow("Binary Image", bw);
    // Perform the distance transform algorithm
    Mat dist;
    distanceTransform(bw, dist, DIST_L2, 3);
    // Normalize the distance image for range = {0.0, 1.0}
    // so we can visualize and threshold it
    normalize(dist, dist, 0, 1.0, NORM_MINMAX);
    imshow("Distance Transform Image", dist);
    // Threshold to obtain the peaks
    // This will be the markers for the foreground objects
    threshold(dist, dist, 0.4, 1.0, THRESH_BINARY);
    // Dilate a bit the dist image
    Mat kernel1 = Mat::ones(3, 3, CV_8U);
    dilate(dist, dist, kernel1);
    imshow("Peaks", dist);
    // Create the CV_8U version of the distance image
    // It is needed for findContours()
    Mat dist_8u;
    dist.convertTo(dist_8u, CV_8U);
    // Find total markers
    vector<vector<Point> > contours;
    findContours(dist_8u, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    // Create the marker image for the watershed algorithm
    Mat markers = Mat::zeros(dist.size(), CV_32S);
    // Draw the foreground markers
    for (size_t i = 0; i < contours.size(); i++)
    {
        drawContours(markers, contours, static_cast<int>(i), Scalar(static_cast<int>(i) + 1), -1);
    }
    // Draw the background marker
    circle(markers, Point(5, 5), 3, Scalar(255), -1);
    //imshow("Markers", markers * 10000);
    // Perform the watershed algorithm
    watershed(imgResult, markers);
    Mat mark;
    markers.convertTo(mark, CV_8U);
    bitwise_not(mark, mark);
    //    imshow("Markers_v2", mark); // uncomment this if you want to see how the mark
    // image looks like at that point
    // Generate random colors
    vector<Vec3b> colors;
    for (size_t i = 0; i < contours.size(); i++)
    {
        int b = theRNG().uniform(0, 256);
        int g = theRNG().uniform(0, 256);
        int r = theRNG().uniform(0, 256);
        colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
    }
    // Create the result image
    Mat dst = Mat::zeros(markers.size(), CV_8UC3);
    // Fill labeled objects with random colors
    for (int i = 0; i < markers.rows; i++)
    {
        for (int j = 0; j < markers.cols; j++)
        {
            int index = markers.at<int>(i, j);
            if (index > 0 && index <= static_cast<int>(contours.size()))
            {
                dst.at<Vec3b>(i, j) = colors[index - 1];
            }
        }
    }
    // Visualize the final image
    imshow("Final Result", dst);
    waitKey();
}