void CharacterAnalysis::analyze()
  {
    timespec startTime;
    getTimeMonotonic(&startTime);

    pipeline_data->clearThresholds();
    pipeline_data->thresholds = produceThresholds(pipeline_data->crop_gray, config);

    timespec contoursStartTime;
    getTimeMonotonic(&contoursStartTime);

    pipeline_data->textLines.clear();

    for (unsigned int i = 0; i < pipeline_data->thresholds.size(); i++)
    {
      TextContours tc(pipeline_data->thresholds[i]);

      allTextContours.push_back(tc);
    }

    if (config->debugTiming)
    {
      timespec contoursEndTime;
      getTimeMonotonic(&contoursEndTime);
      cout << "  -- Character Analysis Find Contours Time: " << diffclock(contoursStartTime, contoursEndTime) << "ms." << endl;
    }
    //Mat img_equalized = equalizeBrightness(img_gray);

    timespec filterStartTime;
    getTimeMonotonic(&filterStartTime);

    for (unsigned int i = 0; i < pipeline_data->thresholds.size(); i++)
    {
      this->filter(pipeline_data->thresholds[i], allTextContours[i]);

      if (config->debugCharAnalysis)
        cout << "Threshold " << i << " had " << allTextContours[i].getGoodIndicesCount() << " good indices." << endl;
    }

    if (config->debugTiming)
    {
      timespec filterEndTime;
      getTimeMonotonic(&filterEndTime);
      cout << "  -- Character Analysis Filter Time: " << diffclock(filterStartTime, filterEndTime) << "ms." << endl;
    }

    PlateMask plateMask(pipeline_data);
    plateMask.findOuterBoxMask(allTextContours);

    pipeline_data->hasPlateBorder = plateMask.hasPlateMask;
    pipeline_data->plateBorderMask = plateMask.getMask();

    if (plateMask.hasPlateMask)
    {
      // Filter out bad contours now that we have an outer box mask...
      for (unsigned int i = 0; i < pipeline_data->thresholds.size(); i++)
      {
        filterByOuterMask(allTextContours[i]);
      }
    }

    int bestFitScore = -1;
    int bestFitIndex = -1;
    for (unsigned int i = 0; i < pipeline_data->thresholds.size(); i++)
    {

      int segmentCount = allTextContours[i].getGoodIndicesCount();

      if (segmentCount > bestFitScore)
      {
        bestFitScore = segmentCount;
        bestFitIndex = i;
        bestThreshold = pipeline_data->thresholds[i];
        bestContours = allTextContours[i];
      }
    }

    if (this->config->debugCharAnalysis)
      cout << "Best fit score: " << bestFitScore << " Index: " << bestFitIndex << endl;

    if (bestFitScore <= 1)
    {
      pipeline_data->disqualified = true;
      pipeline_data->disqualify_reason = "Low best fit score in characteranalysis";
      return;
    }

    //getColorMask(img, allContours, allHierarchy, charSegments);

    if (this->config->debugCharAnalysis)
    {
      Mat img_contours = bestContours.drawDebugImage(bestThreshold);

      displayImage(config, "Matching Contours", img_contours);
    }

    LineFinder lf(pipeline_data);
    vector<vector<Point> > linePolygons = lf.findLines(pipeline_data->crop_gray, bestContours);

    vector<TextLine> tempTextLines;
    for (unsigned int i = 0; i < linePolygons.size(); i++)
    {
      vector<Point> linePolygon = linePolygons[i];

      LineSegment topLine = LineSegment(linePolygon[0].x, linePolygon[0].y, linePolygon[1].x, linePolygon[1].y);
      LineSegment bottomLine = LineSegment(linePolygon[3].x, linePolygon[3].y, linePolygon[2].x, linePolygon[2].y);

      vector<Point> textArea = getCharArea(topLine, bottomLine);

      TextLine textLine(textArea, linePolygon, pipeline_data->crop_gray.size());

      tempTextLines.push_back(textLine);
    }

    filterBetweenLines(bestThreshold, bestContours, tempTextLines);

    // Sort the lines from top to bottom.
    std::sort(tempTextLines.begin(), tempTextLines.end(), sort_text_line);

    // Now that we've filtered a few more contours, re-do the text area.
    for (unsigned int i = 0; i < tempTextLines.size(); i++)
    {
      vector<Point> updatedTextArea = getCharArea(tempTextLines[i].topLine, tempTextLines[i].bottomLine);
      vector<Point> linePolygon = tempTextLines[i].linePolygon;
      if (updatedTextArea.size() > 0 && linePolygon.size() > 0)
      {
        pipeline_data->textLines.push_back(TextLine(updatedTextArea, linePolygon, pipeline_data->crop_gray.size()));
      }

    }

    pipeline_data->plate_inverted = isPlateInverted();


    if (pipeline_data->textLines.size() > 0)
    {
      int confidenceDrainers = 0;
      int charSegmentCount = this->bestContours.getGoodIndicesCount();
      if (charSegmentCount == 1)
        confidenceDrainers += 91;
      else if (charSegmentCount < 5)
        confidenceDrainers += (5 - charSegmentCount) * 10;

      // Use the angle for the first line -- assume they'll always be parallel for multi-line plates
      int absangle = abs(pipeline_data->textLines[0].topLine.angle);
      if (absangle > config->maxPlateAngleDegrees)
        confidenceDrainers += 91;
      else if (absangle > 1)
        confidenceDrainers += (config->maxPlateAngleDegrees - absangle) ;

      // If a multiline plate has only one line, disqualify
      if (pipeline_data->isMultiline && pipeline_data->textLines.size() < 2)
      {
        if (config->debugCharAnalysis)
          std::cout << "Did not detect multiple lines on multi-line plate" << std::endl;
        confidenceDrainers += 95;
      }

      if (confidenceDrainers >= 90)
      {
        pipeline_data->disqualified = true;
        pipeline_data->disqualify_reason = "Low confidence in characteranalysis";
      }
      else
      {
        float confidence = 100 - confidenceDrainers;
        pipeline_data->confidence_weights.setScore("CHARACTER_ANALYSIS_SCORE", confidence, 1.0);
      }
    }
    else
    {
        pipeline_data->disqualified = true;
        pipeline_data->disqualify_reason = "No text lines found in characteranalysis";
    }

    if (config->debugTiming)
    {
      timespec endTime;
      getTimeMonotonic(&endTime);
      cout << "Character Analysis Time: " << diffclock(startTime, endTime) << "ms." << endl;
    }

    // Draw debug dashboard
    if (this->pipeline_data->config->debugCharAnalysis && pipeline_data->textLines.size() > 0)
    {
      vector<Mat> tempDash;
      for (unsigned int z = 0; z < pipeline_data->thresholds.size(); z++)
      {
        Mat tmp(pipeline_data->thresholds[z].size(), pipeline_data->thresholds[z].type());
        pipeline_data->thresholds[z].copyTo(tmp);
        cvtColor(tmp, tmp, CV_GRAY2BGR);

        tempDash.push_back(tmp);
      }

      Mat bestVal(this->bestThreshold.size(), this->bestThreshold.type());
      this->bestThreshold.copyTo(bestVal);
      cvtColor(bestVal, bestVal, CV_GRAY2BGR);

      for (unsigned int z = 0; z < this->bestContours.size(); z++)
      {
        Scalar dcolor(255,0,0);
        if (this->bestContours.goodIndices[z])
          dcolor = Scalar(0,255,0);
        drawContours(bestVal, this->bestContours.contours, z, dcolor, 1);
      }
      tempDash.push_back(bestVal);
      displayImage(config, "Character Region Step 1 Thresholds", drawImageDashboard(tempDash, bestVal.type(), 3));
    }
  }
void CharacterAnalysis::analyze()
{


    thresholds = produceThresholds(img_gray, config);


    /*
      // Morph Close the gray image to make it easier to detect blobs
      int morph_elem  = 1;
      int morph_size = 1;
      Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );

      for (int i = 0; i < thresholds.size(); i++)
      {
        //morphologyEx( mask, mask, MORPH_CLOSE, element );
        morphologyEx( thresholds[i], thresholds[i], MORPH_OPEN, element );
        //dilate( thresholds[i], thresholds[i],  element );

      }
    */


    timespec startTime;
    getTime(&startTime);


    for (int i = 0; i < thresholds.size(); i++)
    {
        vector<vector<Point> > contours;
        vector<Vec4i> hierarchy;

        Mat tempThreshold(thresholds[i].size(), CV_8U);
        thresholds[i].copyTo(tempThreshold);
        findContours(tempThreshold,
                     contours, // a vector of contours
                     hierarchy,
                     CV_RETR_TREE, // retrieve all contours
                     CV_CHAIN_APPROX_SIMPLE ); // all pixels of each contours

        allContours.push_back(contours);
        allHierarchy.push_back(hierarchy);
    }




    if (config->debugTiming)
    {
        timespec endTime;
        getTime(&endTime);
        cout << "  -- Character Analysis Find Contours Time: " << diffclock(startTime, endTime) << "ms." << endl;
    }
    //Mat img_equalized = equalizeBrightness(img_gray);


    getTime(&startTime);

    for (int i = 0; i < thresholds.size(); i++)
    {
        vector<bool> goodIndices = this->filter(thresholds[i], allContours[i], allHierarchy[i]);
        charSegments.push_back(goodIndices);

        if (config->debugCharAnalysis)
            cout << "Threshold " << i << " had " << getGoodIndicesCount(goodIndices) << " good indices." << endl;
    }

    if (config->debugTiming)
    {
        timespec endTime;
        getTime(&endTime);
        cout << "  -- Character Analysis Filter Time: " << diffclock(startTime, endTime) << "ms." << endl;
    }



    this->plateMask = findOuterBoxMask();

    if (hasPlateMask)
    {
        // Filter out bad contours now that we have an outer box mask...
        for (int i = 0; i < thresholds.size(); i++)
        {
            charSegments[i] = filterByOuterMask(allContours[i], allHierarchy[i], charSegments[i]);
        }
    }


    int bestFitScore = -1;
    int bestFitIndex = -1;
    for (int i = 0; i < thresholds.size(); i++)
    {

        //vector<bool> goodIndices = this->filter(thresholds[i], allContours[i], allHierarchy[i]);
        //charSegments.push_back(goodIndices);

        int segmentCount = getGoodIndicesCount(charSegments[i]);


        if (segmentCount > bestFitScore)
        {
            bestFitScore = segmentCount;
            bestFitIndex = i;
            bestCharSegments = charSegments[i];
            bestThreshold = thresholds[i];
            bestContours = allContours[i];
            bestHierarchy = allHierarchy[i];
            bestCharSegmentsCount = segmentCount;
        }
    }

    if (this->config->debugCharAnalysis)
        cout << "Best fit score: " << bestFitScore << " Index: " << bestFitIndex << endl;


    if (bestFitScore <= 1)
        return;


    //getColorMask(img, allContours, allHierarchy, charSegments);

    if (this->config->debugCharAnalysis)
    {

        Mat img_contours(bestThreshold.size(), CV_8U);
        bestThreshold.copyTo(img_contours);
        cvtColor(img_contours, img_contours, CV_GRAY2RGB);

        vector<vector<Point> > allowedContours;
        for (int i = 0; i < bestContours.size(); i++)
        {
            if (bestCharSegments[i])
                allowedContours.push_back(bestContours[i]);
        }

        drawContours(img_contours, bestContours,
                     -1, // draw all contours
                     cv::Scalar(255,0,0), // in blue
                     1); // with a thickness of 1

        drawContours(img_contours, allowedContours,
                     -1, // draw all contours
                     cv::Scalar(0,255,0), // in green
                     1); // with a thickness of 1


        displayImage(config, "Matching Contours", img_contours);
    }


    //charsegments = this->getPossibleCharRegions(img_threshold, allContours, allHierarchy, STARTING_MIN_HEIGHT + (bestFitIndex * HEIGHT_STEP), STARTING_MAX_HEIGHT + (bestFitIndex * HEIGHT_STEP));



    this->linePolygon =  getBestVotedLines(img_gray, bestContours, bestCharSegments);

    if (this->linePolygon.size() > 0)
    {
        this->topLine = LineSegment(this->linePolygon[0].x, this->linePolygon[0].y, this->linePolygon[1].x, this->linePolygon[1].y);
        this->bottomLine = LineSegment(this->linePolygon[3].x, this->linePolygon[3].y, this->linePolygon[2].x, this->linePolygon[2].y);
        //this->charArea = getCharSegmentsBetweenLines(bestThreshold, bestContours, this->linePolygon);
        filterBetweenLines(bestThreshold, bestContours, bestHierarchy, linePolygon, bestCharSegments);

        this->charArea = getCharArea();

        if (this->charArea.size() > 0)
        {
            this->charBoxTop = LineSegment(this->charArea[0].x, this->charArea[0].y, this->charArea[1].x, this->charArea[1].y);
            this->charBoxBottom = LineSegment(this->charArea[3].x, this->charArea[3].y, this->charArea[2].x, this->charArea[2].y);
            this->charBoxLeft = LineSegment(this->charArea[3].x, this->charArea[3].y, this->charArea[0].x, this->charArea[0].y);
            this->charBoxRight = LineSegment(this->charArea[2].x, this->charArea[2].y, this->charArea[1].x, this->charArea[1].y);


        }
    }

    this->thresholdsInverted = isPlateInverted();



}