public int FindNotch(Chain chain, int tipPosition) { if (chain == null) { throw new ArgumentNullException(nameof(chain)); } if (tipPosition < 0 || tipPosition > chain.Length) { throw new ArgumentOutOfRangeException(nameof(tipPosition)); } // TODO //if (_userSetNotch) // throw new Exception("findNotch() [User selected notch in use]"); // We're only going to be looking on the trailing edge // of the fin for the most significant notch, so we'll // build a source vector from that area int numTrailingEdgePts = chain.Length - tipPosition - 1 - 5; double[] src = new double[numTrailingEdgePts]; //***1.95 - JHS // The following original code copies the ABSOLUTE Chain angles into source. // These are probably in the range -180 .. 180. Is this causing problems with // angles that cross quadrants between +180 and -180 thus producing a LARGE // positive change in angle rather than a small negative one. Should the // angles all be SHIFTED by +180 so the range is 0..360? NO, this does not // work. However, converting ONLY the negative angles (-91..-180) to positive // by adding 360 to them DOES WORK. This way there is a continuous change in // positive outline orientation throughout all leftward extending notches // except narrow ones opening up and forward. In effect, we have removed potential // discontinuities from the chain signal being sent into the wavelet code. //memcpy(src, &((*_chain)[_tipPos + 1]), numTrailingEdgePts * sizeof(double)); Array.Copy(chain.Data, tipPosition + 1, src, 0, numTrailingEdgePts); //***1.95 - code to convert negative angles for (int di = 0; di < numTrailingEdgePts; di++) { if (src[di] < -90) { src[di] += 360.0; // force negative angles to be positive } //printf ("%5.2f\n",src[di]); } // Now set up the variables needed to perform a wavelet // transform on the chain double[,] continuousResult = new double[TransformLevels + 1, MathHelper.NextPowerOfTwo(numTrailingEdgePts)]; // Now perform the transformation WIRWavelet.WL_FrwtVector( src, ref continuousResult, numTrailingEdgePts, TransformLevels, WaveletUtil.MZLowPassFilter, WaveletUtil.MZHighPassFilter); int i; for (i = 1; i <= TransformLevels; i++) { for (int j = 0; j < numTrailingEdgePts; j++) { continuousResult[i, j] *= WaveletUtil.NormalizationCoeff(i); } } double[,] modMax = new double[TransformLevels, numTrailingEdgePts]; // ..and find its local minima and maxima for (i = 0; i < TransformLevels; i++) { double[] temp = new double[numTrailingEdgePts]; double[] continousExtract = WaveletUtil.Extract1DArray(continuousResult, i + 1, numTrailingEdgePts); WaveletUtil.ModulusMaxima(continousExtract, ref temp, numTrailingEdgePts); WaveletUtil.Patch1DArray(temp, ref modMax, i, numTrailingEdgePts); } int level = TransformLevels / 2; if (level < 1) { level = 1; } // First, we'll find some local minima at an intermediate level // to track. int notchPosition = 0; List <int> mins = new List <int>(); while (level > 0) { for (i = 0; i < numTrailingEdgePts; i++) { if (modMax[level, i] < 0.0) { mins.Add(i); } } if (mins.Count <= 0) { level--; continue; } if (mins.Count == 1) { notchPosition = 1; if (0 == notchPosition) { notchPosition = chain.Length - tipPosition - 1; } break; } // yes, bad code break; } if (level == 0) { // Well, this really shouldn't happen: we've looked through // all the fine transform levels and haven't found any local // minima. So, we'll just set the notch Position to the end // of the chain. notchPosition = chain.Length - tipPosition - 1; } if (notchPosition == 0) { // Now, we'll take the lowest few mins, and look at how // they change over the transform levels. double[] minVals = new double[mins.Count]; for (i = 0; i < mins.Count; i++) { minVals[i] = modMax[level, mins[i]]; } Array.Sort(minVals); int numMinsToTrack; if ((int)mins.Count < NotchNumMinsToTrack) { numMinsToTrack = mins.Count; } else { numMinsToTrack = NotchNumMinsToTrack; } int[] positions = new int[numMinsToTrack]; for (int count = 0; count < numMinsToTrack; count++) { for (i = 0; i < mins.Count; i++) { if (minVals[count] == modMax[level, mins[i]]) { positions[count] = mins[i]; break; } } } // Ok, now that we've got the few lowest mins, // let's find their corresponding positions in // a coarser level int coarserLevel = TransformLevels - 2; int correspondingPos; //double alphaMax, alpha; double difMax = 0.0, dif; bool firstRun = true; for (i = 0; i < numMinsToTrack; i++) { var extract = WaveletUtil.Extract1DArray(modMax, coarserLevel, numTrailingEdgePts); correspondingPos = FindClosestMin( 5, extract, numTrailingEdgePts, positions[i]); // If we found a corresponding min in a coarser // level... if (-1 != correspondingPos) { //alpha = alphaK( // level + 1, // coarserLevel + 1, // modMax[level][positions[i]], // modMax[coarserLevel][correspondingPos]); dif = Math.Abs(modMax[coarserLevel, correspondingPos]); //+ fabs(modMax[level][positions[i]]); if (firstRun) { firstRun = false; difMax = dif; notchPosition = positions[i]; } else if (dif > difMax) { difMax = dif; notchPosition = positions[i]; } } } if (firstRun) { notchPosition = chain.Length - 1 - tipPosition; } } /* * list<ZeroCrossing> zeroCrossings = * findZeroCrossings(continuousResult[level], numTrailingEdgePts); * * list<ZeroCrossing>::iterator it = zeroCrossings.begin(); * * double maxDist = 0.0; * * while (it != zeroCrossings.end()) { * if (it->leftMag < 0.0) { * double curDist = (fabs(it->leftMag) + fabs(it->rightMag)); * if (curDist > maxDist) { * maxDist = curDist; * notchPosition = it->position; * } * } ++it; * } * * zeroCrossings.clear(); */ return(notchPosition + tipPosition); }
/// <summary> /// Tries to find the position of the upper lip on the outline. If there's a mouth dent, /// it'll return that (and set the output parameter hasMouthDent to true). If there isn't, /// it'll return the greatest max to the right of the mouth dent. /// </summary> /// <param name="chain"></param> /// <param name="chainPoints"></param> /// <param name="underNoseDentPosition"></param> /// <param name="hasMouthDent"></param> /// <param name="mouthDentPosition"></param> /// <returns></returns> public int FindUpperLip(Chain chain, FloatContour chainPoints, int underNoseDentPosition, out bool hasMouthDent, out int bottomLipProtrusion) { if (chain == null) { throw new ArgumentNullException(nameof(chain)); } if (underNoseDentPosition < 0 || underNoseDentPosition > chain.Length) { throw new ArgumentOutOfRangeException(nameof(underNoseDentPosition)); } hasMouthDent = false; int numPointsAfterNoseDent = (int)Math.Round((chain.Length - underNoseDentPosition) * (1.0f - UpperLipEndPadding)); // We're going to transform the whole chain int numPoints = chain.Length; // First, make a copy without the first value in the chain, // since the first value skews the rest of the chain and is // unnecessary for our purposes here double[] src = new double[numPoints - 1]; Array.Copy(chain.Data, 1, src, 0, numPoints - 1); int nextPowOfTwo = MathHelper.NextPowerOfTwo(numPoints - 1); // Now set up the variables needed to perform a wavelet transform on the chain double[,] continuousResult = new double[UpperLipNumTransformsForMin + 1, nextPowOfTwo]; WIRWavelet.WL_FrwtVector(src, ref continuousResult, numPoints - 1, UpperLipNumTransformsForMin, WaveletUtil.MZLowPassFilter, WaveletUtil.MZHighPassFilter); for (int i = 1; i <= UpperLipNumTransformsForMin; i++) { for (int j = 0; j < numPoints - 1; j++) { continuousResult[i, j] *= WaveletUtil.NormalizationCoeff(i); } } double[] modMax = new double[numPoints - 1]; for (int k = 0; k < numPoints - 1; k++) { continuousResult[UpperLipNumTransformsForMin, k] *= WaveletUtil.NormalizationCoeff(UpperLipNumTransformsForMin); } double[] continousExtract = WaveletUtil.Extract1DArray(continuousResult, UpperLipNumTransformsForMin, numPoints - 1); WaveletUtil.ModulusMaxima(continousExtract, ref modMax, numPoints - 1); // Now, let's see if we have any mins at the top transform level. If we do, we probably have a mouth dent. If we don't, // we might not. for (int k = numPointsAfterNoseDent + underNoseDentPosition; k > underNoseDentPosition; k--) { if (modMax[k] < 0.0) { hasMouthDent = true; break; } } int mouthDentPosition = 0; if (hasMouthDent) { mouthDentPosition = FindNotch(chain, underNoseDentPosition + UpperLipTipPadding); // Check where it found the mouth dent -- if it's too close to the end, it's likely that it's // not the mouth, but something like the neck/etc. if (mouthDentPosition < underNoseDentPosition + numPointsAfterNoseDent) { bottomLipProtrusion = FindTip(chain, mouthDentPosition + 20, mouthDentPosition - underNoseDentPosition - UpperLipTipPadding, UpperLipTipPadding); return(mouthDentPosition); } } // Fake the mouth dent position as a starting point for finding the biggest max in the area mouthDentPosition = underNoseDentPosition + numPointsAfterNoseDent; int upperLipPosition = FindTip(chain, mouthDentPosition, mouthDentPosition - underNoseDentPosition - UpperLipTipPadding, UpperLipTipPadding); // These are fallback positions, so they're going to be off if we hit this if statement. if (upperLipPosition < underNoseDentPosition) { upperLipPosition = (int)Math.Round(mouthDentPosition + numPointsAfterNoseDent / 2.0f); } bottomLipProtrusion = upperLipPosition; return(upperLipPosition); }
//******************************************************************* // // int Outline::findTip() // // Finds the tip as the index into the chain array // public int FindTip(Chain chain, int highPointId, int highPointPaddingLeft = DefaultTipHighPointPadding, int highPointPaddingRight = DefaultTipHighPointPadding) { if (chain == null) { throw new Exception("findTip() [*_chain]"); } // TODO //if (_userSetTip) // throw new Exception("findNotch() [User selected tip in use]"); int numPoints = chain.Length; // First, make a copy without the first value in the chain, // since the first value skews the rest of the chain and is // unnecessary for our purposes here double[] src = new double[numPoints - 1]; Array.Copy(chain.Data, 1, src, 0, numPoints - 1); int nextPowOfTwo = MathHelper.NextPowerOfTwo(numPoints - 1); // Now set up the variables needed to perform a wavelet transform on the chain double[,] continuousResult = new double[TransformLevels + 1, nextPowOfTwo]; // Now perform the transformation WIRWavelet.WL_FrwtVector(src, ref continuousResult, numPoints - 1, TransformLevels, WaveletUtil.MZLowPassFilter, WaveletUtil.MZHighPassFilter); int tipPosition = 0, level = TransformLevels; /* * while (!tipPosition && level > 0) { // Find the maxima of the * coefficients double *modMax = new double[numPoints - 1]; * * for (int k = 0; k < numPoints - 1; k++) continuousResult[level][k] * *= normalizationCoeff(level); * * modulusMaxima(continuousResult[level], modMax, numPoints - 1); * * // Now, find the largest positive max, which we'll // assume is * the tip of the fin. * * double max = modMax[0]; for (int i = 1; i < numPoints - 1; i++) { * if (modMax[i] > max) { max = modMax[i]; tipPosition = i; } } * * level--; delete[] modMax; } */ while (level > 1) { // Find the maxima of the coefficients double[] modMax = new double[numPoints - 1]; for (int k = 0; k < numPoints - 1; k++) { continuousResult[level, k] *= WaveletUtil.NormalizationCoeff(level); } double[] continousExtract = WaveletUtil.Extract1DArray(continuousResult, level, numPoints - 1); WaveletUtil.ModulusMaxima(continousExtract, ref modMax, numPoints - 1); // Now, find the largest positive max, which we'll // assume is the tip of the fin. if (tipPosition == 0) { // original loop control is two line below //double max = modMax[0]; //***0041TIP-JHS restricting range of search //for (int i = 1; i < numPoints - 1; i++) { //***0041TIP-JHS restricting range of search //***0041TIP - we now restrict the range for initial detection of the tip // start at high point on fin and search within 150 point range along fin //***1.6 - NOTE: there is an unintended consequence of the limmits imposed // below. If outlines are shortened to 50% or so of full size by having // leading or trailing edges missing, the range of +/- 75 points is not // a large enough range to capture tips of "MissingTip" fins in a consistent // manner. We need to rethink this, and possibly go back to the original // and retest against a database of fins traced without this limit. // The new "Iterative, Tip Shifting" mapping approach probably conpensates // for tip placement better than this limit on detection does. -- JHS (8/2/2006) int initialIndex = highPointId - highPointPaddingLeft; if (initialIndex < 0) { initialIndex = 0; } double max = modMax[initialIndex]; //***1.6 - removed temporarily for tests int endIndex = highPointId + highPointPaddingRight; if (endIndex >= modMax.Length) { endIndex = modMax.Length - 1; } for (int i = initialIndex; i < endIndex; i++) { //***1.6 - removed temporarily for tests if (modMax[i] > max) { max = modMax[i]; tipPosition = i; } } } else { int maxSearchPoints = numPoints - 1; if (highPointPaddingRight != DefaultTipHighPointPadding) { maxSearchPoints = highPointId + highPointPaddingRight; } // TODO: Left padding? tipPosition = FindClosestMax(modMax, maxSearchPoints, tipPosition); } level--; } // Note that the actual tip position is +1 because we // ignored the first value Trace.WriteLine("Tip position: " + (tipPosition + 1)); if ((tipPosition + 1) < 5) { Trace.WriteLine("Probable bad tip position."); } return(tipPosition + 1); }
/// <summary> /// Finds the Nasion, or the point on the head between the eyes. It does this by /// finding the most significant minimum before the tip of the nose. /// </summary> /// <param name="chain"></param> /// <param name="tipPosition">Index of the tip of the nose</param> /// <returns>Integer index representing the position of the nasion</returns> public int FindNasion(Chain chain, int tipPosition) { if (chain == null) { throw new ArgumentNullException(nameof(chain)); } if (tipPosition < 0 || tipPosition > chain.Length) { throw new ArgumentOutOfRangeException(nameof(tipPosition)); } int padding = (int)Math.Round(NasionPaddingPercentage * tipPosition); int numPoints = chain.Length; int numLeadingEdgePts = tipPosition - 2 * padding; double[] src = new double[numLeadingEdgePts]; Array.Copy(chain.Data, padding, src, 0, numLeadingEdgePts); int nextPowOfTwo = MathHelper.NextPowerOfTwo(numPoints - 1); // Now set up the variables needed to perform a wavelet transform on the chain double[,] continuousResult = new double[NasionTransformLevels + 1, nextPowOfTwo]; // Now perform the transformation WIRWavelet.WL_FrwtVector(src, ref continuousResult, numLeadingEdgePts, NasionTransformLevels, WaveletUtil.MZLowPassFilter, WaveletUtil.MZHighPassFilter); int nasionPosition = 0, level = NasionTransformLevels; while (level > 1) { // Find the maxima of the coefficients double[] modMax = new double[numLeadingEdgePts]; for (int k = 0; k < numPoints - 1; k++) { continuousResult[level, k] *= WaveletUtil.NormalizationCoeff(level); } double[] continousExtract = WaveletUtil.Extract1DArray(continuousResult, level, numLeadingEdgePts); WaveletUtil.ModulusMaxima(continousExtract, ref modMax, numLeadingEdgePts); // Now, find the largest positive max, which we'll // assume is the tip of the fin. if (nasionPosition == 0) { // Find the greatest min double min = double.MaxValue; //***1.6 - removed temporarily for tests for (int i = 0; i < numLeadingEdgePts; i++) { //***1.6 - removed temporarily for tests if (modMax[i] < min) { min = modMax[i]; nasionPosition = i; } } } else { var closestPosition = FindClosestMin(modMax, numLeadingEdgePts, nasionPosition); if (closestPosition >= 0) { nasionPosition = closestPosition; } } level--; } var correctedNasionPosition = nasionPosition + padding; Trace.WriteLine("Nasion position: " + correctedNasionPosition); if (correctedNasionPosition < 5) { Trace.WriteLine("Probable bad nasion position."); } return(correctedNasionPosition); }