// Description.cpp - Obsahuje definice funkcí sloužících k segmentaci a popisu objektů
// Autor:   Jan Benda
// Datum:   11.11.2020

#include "Description.h"

namespace Description
    {
    int getImageInfo(std::string iFilename, std::string iValue)
        {
        std::ifstream file(iFilename, std::ifstream::binary);
        std::string str;
        
        file.seekg(0, std::ios::end);
        str.reserve(file.tellg());
        file.seekg(0, std::ios::beg);
        str.assign((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());

        size_t index = str.find(iValue);
        if(std::string::npos != index)
            {
            size_t end = str.find("|", index);
            return std::stoi(str.substr(index + iValue.length(), end - index - iValue.length()));
            }
        return -1;
        }

    int percentile95(cv::Mat& iImage, int iThreshold)
        {
        int hist[256] = { 0, };
        int sum = 0;
        iImage.convertTo(iImage, CV_8UC1);
        for(int y = 0; y < iImage.rows; y++)
            for(int x = 0; x < iImage.cols; x++)
                if(iImage.at<uchar>(y, x) > iThreshold)
                    {
                    hist[iImage.at<uchar>(y, x)]++;
                    sum++;
                    }

        int temp = 0;
        for(int i = 255; i >= 0; i--)
            {
            temp = temp + hist[i];
            if(temp >= (int) (sum * 0.95))
                return i;
            }
        return 0;
        }

    bool myfunction(std::vector<cv::Point> i, std::vector<cv::Point> j) { return (i.size() < j.size()); }

    bool imageSegmentation(short& iID, const std::string iFilename, cv::Mat& oCroopedGrayscale, cv::Mat& oCroopedBinary)
        {
        cv::Mat input_grayscale = cv::imread(iFilename, cv::IMREAD_GRAYSCALE);
        cv::Mat input_color = cv::imread(iFilename, cv::IMREAD_COLOR);

        cv::Mat first_segmentation = cv::Mat::zeros(input_grayscale.size(), CV_8UC1);
        int QMIN = 0, limit = 0, QMAX = 0, QAVG = 0, threshold = 0, posX1 = 0, posX2 = 0, posY1 = 0, posY2 = 0;

        const size_t last_dot_idx = iFilename.find_last_of(".");
        if(input_grayscale.rows == 0 || input_grayscale.cols == 0 || iFilename.substr(last_dot_idx) != ".jpg")
            return false;

        bool test = false;
        if(iID == 0)
            test = true;

        if(iID != 0 && iID != getImageInfo(iFilename, "resultID:"))
            throw std::exception(("Soubor: " + General::getFilename(iFilename) + " nepatří do skupiny " + std::to_string(iID)).c_str());
        else
            iID = getImageInfo(iFilename, "resultID:");

        if((QMIN = getImageInfo(iFilename, "QMIN:")) < 0)
            throw std::exception(("Parametr QMIN nebyl v souboru: " + General::getFilename(iFilename) + " nalezen").c_str());
        if((QMAX = getImageInfo(iFilename, "QMAX:")) < 0)
            throw std::exception(("Parametr QMAX nebyl v souboru: " + General::getFilename(iFilename) + " nalezen").c_str());
        if((QAVG = getImageInfo(iFilename, "QAVG:")) < 0)
            throw std::exception(("Parametr QAVG nebyl v souboru: " + General::getFilename(iFilename) + " nalezen").c_str());
        if((limit = getImageInfo(iFilename, "limit1:")) < 0)
            throw std::exception(("Parametr limit1 nebyl v souboru: " + General::getFilename(iFilename) + " nalezen").c_str());

        // tmavé vady
        if(iID == 9)
            {
            threshold = QMIN + 2;
            limit = percentile95(input_grayscale, QMAX + 10);
            if(limit < 15)
                limit = 15;
            }
        // světlé vady
        if(iID == 10)
            {
            threshold = QMIN;// -15;
            //limit = Percentile95(input_grayscale, threshold - 5) - 10;
            }
        // tmavé vady
        if(iID == 11)
            {
            threshold = QAVG;
            limit = percentile95(input_grayscale, QAVG + 5);
            if(limit < 15)
                limit = 15;
            }
        // světlé vady
        if(iID == 12)
            {
            threshold = QMAX - 20;
            limit -= 55;
            }

        cv::Mat object_points;
        while(object_points.rows <= 0)
            {
            // tmavé vady
            if(iID == 9 || iID == 11)
                {
                cv::threshold(input_grayscale, first_segmentation, threshold, 255, cv::THRESH_BINARY_INV);
                threshold += 1;
                }
            // světlé vady
            if(iID == 10 || iID == 12)
                {
                cv::threshold(input_grayscale, first_segmentation, threshold, 255, cv::THRESH_BINARY);
                threshold -= 1;
                }
            cv::findNonZero(first_segmentation, object_points);
            }

        cv::Mat second_segmentation = cv::Mat::zeros(first_segmentation.size(), CV_8UC1);
        // tmavé vady
        if(iID == 9 || iID == 11)
            cv::threshold(input_grayscale, second_segmentation, limit, 255, cv::THRESH_BINARY_INV);
        // světlé vady
        if(iID == 10 || iID == 12)
            cv::threshold(input_grayscale, second_segmentation, limit, 255, cv::THRESH_BINARY);

        cv::Mat kernel = cv::Mat::ones(3, 1, CV_8UC1);
        if(iID == 9)
            cv::morphologyEx(second_segmentation, second_segmentation, cv::MORPH_OPEN, kernel, cv::Point(-1, -1), 1, cv::BORDER_DEFAULT);

        std::vector<std::vector<cv::Point>> first_contours;
        cv::findContours(first_segmentation, first_contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

        std::vector<std::vector<cv::Point>> second_contours;
        cv::findContours(second_segmentation, second_contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

        bool temp_prom = false;
        for(size_t i = 0; i < first_contours.size(); i++)
            {
            temp_prom = false;
            for(size_t j = 0; j < second_contours.size() && !temp_prom; j++)
                {
                if((cv::contourArea(second_contours.at(j)) > 30 && iID == 9) || (cv::contourArea(second_contours.at(j)) > 5 && iID == 11) || iID == 10 || iID == 12)
                    {
                    if(cv::pointPolygonTest(second_contours.at(j), first_contours.at(i).at(0), false) >= 0)
                        {
                        cv::drawContours(first_segmentation, second_contours, (int)j, 255, -1);
                        cv::drawContours(input_color, second_contours, (int)j, (0, 0, 255), -1);
                        temp_prom = true;
                        second_contours.erase(second_contours.begin() + j);
                        }
                    }
                }
            }
        object_points = cv::Mat::zeros(1, 1, CV_8UC1);
        cv::findNonZero(first_segmentation, object_points);
        cv::Rect R = boundingRect(object_points);

        cv::getRectSubPix(input_grayscale, { R.width + 40,R.height + 40 }, cv::Point(R.x + R.width / 2, R.y + R.height / 2), oCroopedGrayscale);
        cv::getRectSubPix(first_segmentation, { R.width + 40,R.height + 40 }, cv::Point(R.x + R.width / 2, R.y + R.height / 2), oCroopedBinary);
        
        if(test)
            cv::imwrite("segmentation\\" + General::getFilename(iFilename), input_color);
        return true;
        }

        bool getFeatures(short& iID, const std::string iFilename, std::vector<std::string>& iFeatureNames, std::vector<float>& iFeatureValues)
        {
        cv::Mat croopedGrayscale, croopedBinary;

        if(!imageSegmentation(iID, iFilename, croopedGrayscale, croopedBinary))
            return false;

        int histogram[256] = { 0, }, histogram_out[256] = { 0, };
        int area_out = 0;

        std::vector<std::vector<cv::Point>> contours;
        cv::findContours(croopedBinary, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);

        cv::Mat surroundings = cv::Mat::zeros(croopedGrayscale.size(), CV_8UC1);
        for(size_t c = 0; c < contours.size(); c++)
            cv::drawContours(surroundings, contours, (int)c, 255, 20);

        surroundings = surroundings & (255 - croopedBinary);
        cv::Mat gl = cv::Mat::zeros(256, 256, CV_32FC1);
        for(int y = 0; y < croopedGrayscale.rows; y++)
            for(int x = 0; x < croopedGrayscale.cols; x++)
                {
                if(croopedBinary.at<uchar>(y, x) > 0)
                    histogram[croopedGrayscale.at<uchar>(y, x)]++;
                else if(surroundings.at<uchar>(y, x) > 0)
                    {
                    histogram_out[croopedGrayscale.at<uchar>(y, x)]++;
                    area_out++;
                    }
                if(x < croopedGrayscale.cols - 1)
                    gl.at<float>(croopedGrayscale.at<uchar>(y, x), croopedGrayscale.at<uchar>(y, x + 1)) = gl.at<float>(croopedGrayscale.at<uchar>(y, x), croopedGrayscale.at<uchar>(y, x + 1)) + 1;
                }

        //Geometric:

        // area;
        cv::Mat object_points;
        cv::findNonZero(croopedBinary, object_points);
        int area = object_points.rows;
        iFeatureNames.push_back("Area");
        iFeatureValues.push_back((float)area);

        // Convexity (solidity) - konvexnost
        std::vector<cv::Point> hull;
        cv::Mat hull_area_points;
        convexHull(object_points, hull, false, true);
        cv::Mat hull_image = cv::Mat::zeros(croopedBinary.size(), CV_8UC1);
        std::vector<std::vector<cv::Point>> temp_hull;
        temp_hull.push_back(hull);
        cv::drawContours(hull_image, temp_hull, 0, 255, -1);
        cv::findNonZero(hull_image, hull_area_points);
        float solidity = float((float)area / (float)hull_area_points.rows);
        iFeatureNames.push_back("Solidity");
        iFeatureValues.push_back(solidity);

        //Extent - rozsah: obsah / obsahu obdelnika
        cv::RotatedRect rotated_rect = cv::minAreaRect(object_points);
        float extent = float(area) / float(rotated_rect.size.width * rotated_rect.size.height);
        iFeatureNames.push_back("Extent");
        iFeatureValues.push_back(extent);
       
        float aspect_ratio = 0;
        float angle = 0;

        if(rotated_rect.angle < -25 && rotated_rect.angle > 25)
            {
            aspect_ratio = float(rotated_rect.size.height) / float(rotated_rect.size.width);
            if(rotated_rect.angle < 0)
                angle = rotated_rect.angle + 90;
            else
                angle = rotated_rect.angle - 90;

            iFeatureNames.push_back("Max_width");
            iFeatureValues.push_back(rotated_rect.size.height);
            iFeatureNames.push_back("Max_height");
            iFeatureValues.push_back(rotated_rect.size.width);
            }
        else
            {
            aspect_ratio = float(rotated_rect.size.width) / float(rotated_rect.size.height);
            angle = rotated_rect.angle;

            iFeatureNames.push_back("Max_width");
            iFeatureValues.push_back(rotated_rect.size.width);
            iFeatureNames.push_back("Max_height");
            iFeatureValues.push_back(rotated_rect.size.height);
            }

        iFeatureNames.push_back("Aspect_ratio");
        iFeatureValues.push_back(aspect_ratio);
        
        iFeatureNames.push_back("Angle");
        iFeatureValues.push_back(angle);

        
        // Radiometric:

        // QUANTILES
        int temp_count = 0;
        int temp_q50_out = 0;
        bool bool_qmin = false;
        bool bool_qmax = false;
        bool bool_q25 = false;
        bool bool_q50 = false;
        bool bool_q75 = false;
        bool bool_50out = false;
        for(int i = 0; i < 256; i++)
            {
            temp_count = temp_count + histogram[i];

            if(temp_count > 0 && !bool_qmin)
                {
                bool_qmin = true;
                iFeatureNames.push_back("QMIN");
                iFeatureValues.push_back((float) i);
                }
            if(temp_count >= (int) area && !bool_qmax)
                {
                iFeatureNames.push_back("QMAX");
                bool_qmax = true;
                iFeatureValues.push_back((float) i);
                }
            if(temp_count >= (int) (area * 0.25) && !bool_q25)
                {
                bool_q25 = true;
                iFeatureNames.push_back("Q25");
                iFeatureValues.push_back((float) i);
                }
            if(temp_count >= (int) (area * 0.50) && !bool_q50)
                {
                bool_q50 = true;
                iFeatureNames.push_back("Q50");
                iFeatureValues.push_back((float) i);
                }
            if(temp_count >= (int) (area * 0.75) && !bool_q75)
                {
                bool_q75 = true;
                iFeatureNames.push_back("Q75");
                iFeatureValues.push_back((float) i);
                }

            temp_q50_out = temp_q50_out + histogram_out[i];
            if(temp_q50_out >= (int) (area_out * 0.50) && !bool_50out)
                {
                bool_50out = true;
                iFeatureNames.push_back("Q50_out");
                iFeatureValues.push_back((float) i);
                }
            
            }

        // homogenity 
        float homogenity = glcm(croopedBinary & croopedGrayscale, gl);
        iFeatureNames.push_back("Homogenity");
        iFeatureValues.push_back(homogenity);

        if(iFeatureNames.size() != iFeatureValues.size())
            return false;
        else
            return true;
        }

        float glcm(const cv::Mat& img, cv::Mat& gl)
            {
            float homogenity = 0;
           
            // normalizing glcm matrix for parameter determination
            gl = gl + gl.t();
            gl = gl / sum(gl)[0];


            for(int i = 0; i < 256; i++)
                for(int j = 0; j < 256; j++)
                    homogenity = homogenity + gl.at<float>(i, j) / (1 + abs(i - j));

            return homogenity;
            }

    }