// Computes an Lidfaces model with images in src and corresponding labels
// in labels.
void Lidfaces::train(cv::InputArrayOfArrays src, cv::InputArray labels)
{
    std::vector<std::vector<cv::KeyPoint> > allKeyPoints;
    cv::Mat descriptors;

    // Get SIFT keypoints and LID descriptors
    detectKeypointsAndDescriptors(src, allKeyPoints, descriptors);

    // kmeans function requires points to be CV_32F
    descriptors.convertTo(descriptors, CV_32FC1);

    // Do k-means clustering
    const int CLUSTER_COUNT = params::lidFace::clustersAsPercentageOfKeypoints*descriptors.rows;
    cv::Mat histogramLabels;

    // This function populates histogram bin labels
    // The nth element of histogramLabels is an integer which represents the cluster that the
    // nth element of allKeyPoints is a member of.
    kmeans(
        descriptors, // The points we are clustering are the descriptors
        CLUSTER_COUNT, // The number of clusters (K)
        histogramLabels, // The label of the corresponding keypoint
        params::kmeans::termCriteria,
        params::kmeans::attempts,
        params::kmeans::flags,
        mCenters);

    // Convert to single channel 32 bit float as the matrix needs to be in a form supported
    // by calcHist
    histogramLabels.convertTo(histogramLabels, CV_32FC1);

    // We end up with a histogram for each image
    const size_t NUM_IMAGES = getSize(src);
    std::vector<cv::Mat> hists(NUM_IMAGES);
    // mCodebook.resize(NUM_IMAGES);

    // The histogramLabels vector contains ALL the points from EVERY image. We need to split
    // it up into groups of points for each image.
    // Because there are the same number of points in each image, and the points were put
    // into the labels vector in order, we can simply divide the labels vector evenly to get
    // the individual image's points.
    std::vector<cv::Mat> separatedLabels;
    for (unsigned int i = 0, startRow = 0; i < NUM_IMAGES; ++i)
    {
        separatedLabels.push_back(
            histogramLabels.rowRange(
                startRow,
                startRow + allKeyPoints[i].size()));
        startRow += allKeyPoints[i].size();
    }

    // Populate the hists vector
    generateHistograms(hists, separatedLabels, CLUSTER_COUNT);

    // Make the magnitude of each histogram equal to 1
    normalizeHistograms(hists);

    mCodebook = hists;
    mLabels = labels.getMat();
}
void StemmedFileProcessingDialog::processFiles(const QString &inFile, const QString &outFile)
{
    ui->cbxReading->setText(ui->cbxReading->text() + " " + inFile);
    ui->cbxWriting->setText(ui->cbxWriting->text() + " " + outFile);
    this->in = inFile;
    this->out = outFile;

    QThread* t = new QThread(this);
    StemmedFileParserController* controller = new StemmedFileParserController(this);
    controller->moveToThread(t);
    t->start();
    QObject::connect(this, SIGNAL(read(QString)), controller, SLOT(onRead(QString)), Qt::QueuedConnection);
    QObject::connect(this, SIGNAL(process()), controller, SLOT(onProcess()), Qt::QueuedConnection);
    QObject::connect(this, SIGNAL(write(QString)), controller, SLOT(onWrite(QString)), Qt::QueuedConnection);
    QObject::connect(this, SIGNAL(generateHistograms(QString)), controller, SLOT(onGenerateHistograms(QString)), Qt::QueuedConnection);
    QObject::connect(controller, SIGNAL(readDone(bool)), this, SLOT(onReadDone(bool)), Qt::QueuedConnection);
    QObject::connect(controller, SIGNAL(processDone()), this, SLOT(onProcessDone()), Qt::QueuedConnection);
    QObject::connect(controller, SIGNAL(writeDone(bool)), this, SLOT(onWriteDone(bool)), Qt::QueuedConnection);
    QObject::connect(controller, SIGNAL(generateHistogramsDone(bool, QString)), this, SLOT(onGenerateHistogramsDone(bool, QString)), Qt::QueuedConnection);
    emit read(this->in);

    this->show();
}
// Predicts the label and confidence for a given sample.
void Lidfaces::predict(cv::InputArray src, int& label, double& dist) const
{
    label = -1;
    dist = DBL_MAX;
    std::vector<std::vector<cv::KeyPoint> > keyPoints;
    cv::Mat descriptors;

    std::vector<cv::Mat> imageVector; // A vector containing just one image (this is so we can use the same detectKeypointsAndDescriptors function
    imageVector.push_back(src.getMat());

    // Get SIFT keypoints and LID descriptors
    detectKeypointsAndDescriptors(imageVector, keyPoints, descriptors);


    // Cluster the image using the training centres
    int closestCentroidIndex = 0;
    mCenters.convertTo(mCenters, CV_32FC1);
    descriptors.convertTo(descriptors, CV_32FC1);

    cv::Mat histogramLabels(descriptors.rows, 1, CV_32F);

    // For each descriptor
    for (int descriptorIndex = 0; descriptorIndex < descriptors.rows; ++descriptorIndex)
    {
        // (Give it a classification)
        double smallestDist = DBL_MAX;

        // For each centroid
        for (int centroidIndex = 0; centroidIndex < mCenters.rows; ++centroidIndex)
        {
            // Calculate the distance from the descriptor to the centroid
            double currentDist = cv::norm(
                descriptors.row(descriptorIndex) - mCenters.row(centroidIndex));

            // If it is the smallest distance, remember it and the centroid
            if (currentDist < smallestDist)
            {
                smallestDist = currentDist;
                closestCentroidIndex = centroidIndex;
            }
        }
        histogramLabels.at<float>(descriptorIndex) = closestCentroidIndex;
    }

    assert(histogramLabels.rows == descriptors.rows);

    std::vector<cv::Mat> separatedLabels;
    std::vector<cv::Mat> hists(1);
    separatedLabels.push_back(histogramLabels);

    generateHistograms(hists, separatedLabels, mCenters.rows);
    normalizeHistograms(hists);

    dist = DBL_MAX;
    std::multimap<int, double> distances; // Maps label to distance
    // Compare this histogram against all the other histograms
    for (size_t codebookIndex = 0; codebookIndex < mCodebook.size(); ++codebookIndex)
    {
        // Get dist hist
        double currentDist = cv::compareHist(hists[0], mCodebook[codebookIndex], CV_COMP_CHISQR);
        distances.insert(std::pair<int, double>(mLabels.at<int>(codebookIndex), currentDist));
    }

    // Calculate the smallest average distance
    double smallestAverageDist = DBL_MAX;
    int closestLabel = -1;
    double curDist = 0;
    for (int curLabel = 0; curLabel < mCenters.rows; ++curLabel) // For each curLabel
    {
        if (distances.count(curLabel) == 0) // If this histogram has none of this label
            continue; // Don't bother calculating it
        double totalDist = 0;
        std::pair<std::multimap<int, double>::const_iterator, std::multimap<int, double>::const_iterator> itRange = distances.equal_range(curLabel);
        for (std::multimap<int, double>::const_iterator it = itRange.first; it != itRange.second; ++it)
        {
            totalDist += it->second;
        }
        curDist = totalDist/distances.count(curLabel);
        if (curDist < smallestAverageDist)
        {
            smallestAverageDist = curDist;
            closestLabel = curLabel;
        }
    }

    label = closestLabel;
    dist = smallestAverageDist;
}
void StemmedFileProcessingDialog::on_btnGenerateHistograms_clicked()
{
    QString out = QFileDialog::getSaveFileName(this, "Under which name save histogram files?", "");
    emit generateHistograms(out);
}