void CharacterAnalysis::filterByOuterMask(TextContours& textContours)
  {
    float MINIMUM_PERCENT_LEFT_AFTER_MASK = 0.1;
    float MINIMUM_PERCENT_OF_CHARS_INSIDE_PLATE_MASK = 0.6;

    if (this->pipeline_data->hasPlateBorder == false)
      return;


    cv::Mat plateMask = pipeline_data->plateBorderMask;

    Mat tempMaskedContour = Mat::zeros(plateMask.size(), CV_8U);
    Mat tempFullContour = Mat::zeros(plateMask.size(), CV_8U);

    int charsInsideMask = 0;
    int totalChars = 0;

    vector<bool> originalindices;
    for (unsigned int i = 0; i < textContours.size(); i++)
      originalindices.push_back(textContours.goodIndices[i]);

    for (unsigned int i=0; i < textContours.size(); i++)
    {
      if (textContours.goodIndices[i] == false)
        continue;

      totalChars++;

	  tempFullContour = Mat::zeros(plateMask.size(), CV_8U);
	  drawContours(tempFullContour, textContours.contours, i, Scalar(255,255,255), CV_FILLED, 8, textContours.hierarchy);
      bitwise_and(tempFullContour, plateMask, tempMaskedContour);

      float beforeMaskWhiteness = mean(tempFullContour)[0];
      float afterMaskWhiteness = mean(tempMaskedContour)[0];

      if (afterMaskWhiteness / beforeMaskWhiteness > MINIMUM_PERCENT_LEFT_AFTER_MASK)
      {
        charsInsideMask++;
        textContours.goodIndices[i] = true;
      }
    }

    if (totalChars == 0)
    {
      textContours.goodIndices = originalindices;
      return;
    }

    // Check to make sure that this is a valid box.  If the box is too small (e.g., 1 char is inside, and 3 are outside)
    // then don't use this to filter.
    float percentCharsInsideMask = ((float) charsInsideMask) / ((float) totalChars);
    if (percentCharsInsideMask < MINIMUM_PERCENT_OF_CHARS_INSIDE_PLATE_MASK)
    {
      textContours.goodIndices = originalindices;
      return;
    }

  }
void CharacterAnalysis::filterContourHoles(TextContours& textContours)
{

  for (uint i = 0; i < textContours.size(); i++)
  {
    if (textContours.goodIndices[i] == false)
      continue;

    textContours.goodIndices[i] = false;  // Set it to not included unless it proves valid
    
    int parentIndex = textContours.hierarchy[i][3];

    if (parentIndex >= 0 && textContours.goodIndices[parentIndex])
    {
      // this contour is a child of an already identified contour.  REMOVE it
      if (this->config->debugCharAnalysis)
      {
        cout << "filterContourHoles: contour index: " << i << endl;
      }
    }
    else
    {
      textContours.goodIndices[i] = true;
    }
  }

}
// Goes through the contours for the plate and picks out possible char segments based on min/max height
void CharacterAnalysis::filterByBoxSize(TextContours& textContours, int minHeightPx, int maxHeightPx)
{
  float idealAspect=config->charWidthMM / config->charHeightMM;
  float aspecttolerance=0.25;


  for (uint i = 0; i < textContours.size(); i++)
  {
    if (textContours.goodIndices[i] == false)
      continue;

    textContours.goodIndices[i] = false;  // Set it to not included unless it proves valid
    
    //Create bounding rect of object
    Rect mr= boundingRect(textContours.contours[i]);

    float minWidth = mr.height * 0.2;
    //Crop image
    
    //cout << "Height: " << minHeightPx << " - " << mr.height << " - " << maxHeightPx << " ////// Width: " << mr.width << " - " << minWidth << endl;
    if(mr.height >= minHeightPx && mr.height <= maxHeightPx && mr.width > minWidth)
    {
      float charAspect= (float)mr.width/(float)mr.height;

      //cout << "  -- stage 2 aspect: " << abs(charAspect) << " - " << aspecttolerance << endl;
      if (abs(charAspect - idealAspect) < aspecttolerance)
        textContours.goodIndices[i] = true;
    }
  }

}
void CharacterAnalysis::filter(Mat img, TextContours& textContours)
{
  static int STARTING_MIN_HEIGHT = round (((float) img.rows) * config->charAnalysisMinPercent);
  static int STARTING_MAX_HEIGHT = round (((float) img.rows) * (config->charAnalysisMinPercent + config->charAnalysisHeightRange));
  static int HEIGHT_STEP = round (((float) img.rows) * config->charAnalysisHeightStepSize);
  static int NUM_STEPS = config->charAnalysisNumSteps;

  int bestFitScore = -1;
  
  vector<bool> bestIndices;
  
  for (int i = 0; i < NUM_STEPS; i++)
  {
    
    //vector<bool> goodIndices(contours.size());
    for (uint z = 0; z < textContours.size(); z++) textContours.goodIndices[z] = true;

    this->filterByBoxSize(textContours, STARTING_MIN_HEIGHT + (i * HEIGHT_STEP), STARTING_MAX_HEIGHT + (i * HEIGHT_STEP));
    
    int goodIndices = textContours.getGoodIndicesCount();
    if ( goodIndices == 0 || goodIndices <= bestFitScore)	// Don't bother doing more filtering if we already lost...
      continue;
    
    this->filterContourHoles(textContours);

    goodIndices = textContours.getGoodIndicesCount();
    if ( goodIndices == 0 || goodIndices <= bestFitScore)	// Don't bother doing more filtering if we already lost...
      continue;
    

    int segmentCount = textContours.getGoodIndicesCount();

    if (segmentCount > bestFitScore)
    {
      bestFitScore = segmentCount;
      bestIndices = textContours.getIndicesCopy();
    }
  }

  textContours.setIndices(bestIndices);
}
void CharacterAnalysis::filterBetweenLines(Mat img, TextContours& textContours, vector<TextLine> textLines )
{
  static float MIN_AREA_PERCENT_WITHIN_LINES = 0.88;
  static float MAX_DISTANCE_PERCENT_FROM_LINES = 0.15;

  if (textLines.size() == 0)
    return;

  vector<Point> validPoints;


  // Create a white mask for the area inside the polygon
  Mat outerMask = Mat::zeros(img.size(), CV_8U);

  for (uint i = 0; i < textLines.size(); i++)
    fillConvexPoly(outerMask, textLines[i].linePolygon.data(), textLines[i].linePolygon.size(), Scalar(255,255,255));

  // For each contour, determine if enough of it is between the lines to qualify
  for (uint i = 0; i < textContours.size(); i++)
  {
    if (textContours.goodIndices[i] == false)
      continue;
    
    float percentInsideMask = getContourAreaPercentInsideMask(outerMask, 
            textContours.contours,
            textContours.hierarchy, 
            (int) i);
    
    

    if (percentInsideMask < MIN_AREA_PERCENT_WITHIN_LINES)
    {
      // Not enough area is inside the lines.
      if (config->debugCharAnalysis)
        cout << "Rejecting due to insufficient area" << endl;
      textContours.goodIndices[i] = false; 

      continue;
    }
    
    
    // now check to make sure that the top and bottom of the contour are near enough to the lines
    
    // First get the high and low point for the contour
    // Remember that origin is top-left, so the top Y values are actually closer to 0.
    Rect brect = boundingRect(textContours.contours[i]);
    int xmiddle = brect.x + (brect.width / 2);
    Point topMiddle = Point(xmiddle, brect.y);
    Point botMiddle = Point(xmiddle, brect.y+brect.height);
    
    // Get the absolute distance from the top and bottom lines
    
    for (uint i = 0; i < textLines.size(); i++)
    {
      Point closestTopPoint = textLines[i].topLine.closestPointOnSegmentTo(topMiddle);
      Point closestBottomPoint = textLines[i].bottomLine.closestPointOnSegmentTo(botMiddle);

      float absTopDistance = distanceBetweenPoints(closestTopPoint, topMiddle);
      float absBottomDistance = distanceBetweenPoints(closestBottomPoint, botMiddle);

      float maxDistance = textLines[i].lineHeight * MAX_DISTANCE_PERCENT_FROM_LINES;

      if (absTopDistance < maxDistance && absBottomDistance < maxDistance)
      {
        // It's ok, leave it as-is.
      }
      else
      {
        
        textContours.goodIndices[i] = false; 
        if (config->debugCharAnalysis)
          cout << "Rejecting due to top/bottom points that are out of range" << endl;
      }
    }
    
  }

}
// Goes through the contours for the plate and picks out possible char segments based on min/max height
// returns a vector of indices corresponding to valid contours
void CharacterAnalysis::filterByParentContour( TextContours& textContours)
{

  vector<int> parentIDs;
  vector<int> votes;

  for (uint i = 0; i < textContours.size(); i++)
  {
    if (textContours.goodIndices[i] == false)
      continue;

    textContours.goodIndices[i] = false;  // Set it to not included unless it proves 
    
    int voteIndex = -1;
    int parentID = textContours.hierarchy[i][3];
    // check if parentID is already in the lsit
    for (uint j = 0; j < parentIDs.size(); j++)
    {
      if (parentIDs[j] == parentID)
      {
        voteIndex = j;
        break;
      }
    }
    if (voteIndex == -1)
    {
      parentIDs.push_back(parentID);
      votes.push_back(1);
    }
    else
    {
      votes[voteIndex] = votes[voteIndex] + 1;
    }
  }

  // Tally up the votes, pick the winner
  int totalVotes = 0;
  int winningParentId = 0;
  int highestVotes = 0;
  for (uint i = 0; i < parentIDs.size(); i++)
  {
    if (votes[i] > highestVotes)
    {
      winningParentId = parentIDs[i];
      highestVotes = votes[i];
    }
    totalVotes += votes[i];
  }

  // Now filter out all the contours with a different parent ID (assuming the totalVotes > 2)
  for (uint i = 0; i < textContours.size(); i++)
  {
    if (textContours.goodIndices[i] == false)
      continue;

    if (totalVotes <= 2)
    {
      textContours.goodIndices[i] = true;
    }
    else if (textContours.hierarchy[i][3] == winningParentId)
    {
      textContours.goodIndices[i] = true;
    }
  }

}