public static MatchError ComputeMouthDentError( DatabaseFin unknownFin, DatabaseFin databaseFin, MatchOptions options) { if (unknownFin == null) { throw new ArgumentNullException(nameof(unknownFin)); } if (databaseFin == null) { throw new ArgumentNullException(nameof(databaseFin)); } var maxError = new MatchError { Error = 1 }; if (databaseFin.FinOutline?.FeatureSet?.Features.ContainsKey(Features.FeatureType.HasMouthDent) != true || unknownFin.FinOutline?.FeatureSet?.Features.ContainsKey(Features.FeatureType.BrowCurvature) != true) { return(maxError); } var unknownHasMouthDent = unknownFin.FinOutline?.FeatureSet?.Features[Features.FeatureType.HasMouthDent].Value; var databaseHasMouthDent = databaseFin.FinOutline?.FeatureSet?.Features[Features.FeatureType.HasMouthDent].Value; if (unknownHasMouthDent == null || databaseHasMouthDent == null) { return(maxError); } return(new MatchError { Error = Math.Abs(unknownHasMouthDent.Value - databaseHasMouthDent.Value) }); }
//******************************************************************* // // float Match::matchSingleFin(int registrationMethod, int regSegmentsUsed, // bool categoryToMatch[], bool useFullFinError, // bool useAbsoluteOffsets) // // Attempts to match unknown fin to a single database fin. The // current fin is mDatabase->getItem(mCurrentFin). The match_method // parameter determines which of several outline mapping and // error between match techniques is used. // // now modified to match ONLY those database fins with category designation // indicated in categoryToMatch array // // ***055ER // the new useFullFinError parameter indicates whether or not to use the // entire outlines in the final calculation of the error measure. When false, // only the portion of the outlines between the adjusted leadingeEdgeBegin and // trailingEdgeEnd points is used. When true, the fin mapping is done using // various subsets of the outlines (and errors derived from same), but the // final error (measure of mismatch) uses all outline points from each fin. // This parameter does not affect the original and the trim by 1/10th percent // matching techniques, which always use the entire outline in error calculations // // ***1.3 // New parameter useAbsoluteOffsets, wen true, causes function to step through // the database absolute offset list so that fins are matched in the actual // order that they are stored in the database. This is used ONLY when MATCH // QUEUES are being processed, and it keeps the fin numbers correct even if the // database is later modified. // public float MatchSingleIndividual(List <Category> categoriesToMatch) { if (MatchFactors == null || MatchFactors.Count < 1) { throw new Exception("Match factors haven't been set yet!"); } var stopWatch = System.Diagnostics.Stopwatch.StartNew(); int matchIndex = 0; lock (locker) { // If we have any factors missing updateOutlines, but we know what // the delegate should be, fill them in if (CurrentFinIndex == 0 && _updateOutlines != null && MatchFactors.Any(mf => mf.MatchFactorType == MatchFactorType.Outline && mf.UpdateOutlines == null)) { foreach (var matchFactor in MatchFactors.Where(mf => mf.MatchFactorType == MatchFactorType.Outline && mf.UpdateOutlines == null)) { matchFactor.UpdateOutlines = _updateOutlines; } } // TODO? //if (CurrentFinIndex >= Database.AllFins.Count) // return 100.0f; matchIndex = CurrentFinIndex; CurrentFinIndex += 1; } DatabaseFin thisDBFin = Database.AllFins[matchIndex]; bool tryMatch = categoriesToMatch.Exists(c => c.Name.ToLower() == thisDBFin.DamageCategory.ToLower()); if (tryMatch) { // TODO: This isn't done quite right yet MatchError matchErrorResult = new MatchError(); double errorBetweenFins = 0.0; List <MatchFactorError> rawError = new List <MatchFactorError>(); int factorIndex = 0; try { foreach (var factor in MatchFactors) { var factorResult = factor.FindErrorBetweenIndividuals(UnknownFin, thisDBFin); if (factor.MatchFactorType == MatchFactorType.Outline) { Vector <double> saveRawRatios = null; if (matchErrorResult.RawRatios != null) { saveRawRatios = matchErrorResult.RawRatios; } Vector <double> saveRHat = null; if (matchErrorResult.RHat != null) { saveRHat = matchErrorResult.RHat; } Vector <double> saveDBRHat = null; if (matchErrorResult.DBRHat != null) { saveDBRHat = matchErrorResult.DBRHat; } Vector <double> saveDBRawRatios = null; if (matchErrorResult.DBRawRatios != null) { saveDBRawRatios = matchErrorResult.DBRawRatios; } matchErrorResult = factorResult; if (factorResult.Contour1 != null) { UnknownFin.FinOutline.RemappedChainPoints = factorResult.Contour1; } if (saveRawRatios != null) { matchErrorResult.RawRatios = saveRawRatios; } if (saveRHat != null) { matchErrorResult.RHat = saveRHat; } if (saveDBRawRatios != null) { matchErrorResult.DBRawRatios = saveDBRawRatios; } if (saveDBRHat != null) { matchErrorResult.DBRHat = saveDBRHat; } } else { matchErrorResult.RawRatios = factorResult.RawRatios; matchErrorResult.RHat = factorResult.RHat; matchErrorResult.DBRawRatios = factorResult.DBRawRatios; matchErrorResult.DBRHat = factorResult.DBRHat; } // We're going to rescale this later -- should probably remove this // errorBetweenFins += factor.Weight * result.Error; var matchFactorError = new MatchFactorError { FactorIndex = factorIndex, Error = factorResult.Error, Weight = factor.Weight }; rawError.Add(matchFactorError); lock (locker) { RawErrorTracking.Add(matchFactorError); } factorIndex += 1; } } catch (Exception ex) { Trace.WriteLine(ex); } // Now, store the result Result r = new Result( matchErrorResult.Contour1, //***005CM matchErrorResult.Contour2, //***005CM thisDBFin.ID, // TODO - This image filename stuff is a little ugly. (string.IsNullOrEmpty(thisDBFin.OriginalImageFilename)) ? thisDBFin.ImageFilename : thisDBFin.OriginalImageFilename, thisDBFin.ThumbnailFilenameUri, matchIndex, rawError, errorBetweenFins, thisDBFin.IDCode, thisDBFin.Name, thisDBFin.DamageCategory, thisDBFin.DateOfSighting, thisDBFin.LocationCode); if (matchErrorResult.RHat != null) { r.RHat = matchErrorResult.RHat; } if (matchErrorResult.RawRatios != null) { r.RawRatios = matchErrorResult.RawRatios; } if (matchErrorResult.DBRHat != null) { r.DBRHat = matchErrorResult.DBRHat; } if (matchErrorResult.DBRawRatios != null) { r.DBRawRatios = matchErrorResult.DBRawRatios; } //***1.1 - set indices of beginning, tip and end points used in mapping r.SetMappingControlPoints( matchErrorResult.Contour1ControlPoint1, matchErrorResult.Contour1ControlPoint2, matchErrorResult.Contour1ControlPoint3, // beginning, tip & end of unknown fin matchErrorResult.Contour2ControlPoint1, matchErrorResult.Contour2ControlPoint2, matchErrorResult.Contour2ControlPoint3); // beginning, tip & end of database fin stopWatch.Stop(); lock (locker) { MatchResults.AddResult(r); MatchResults.TimeTaken += stopWatch.ElapsedMilliseconds; } } lock (locker) { int numberDone = MatchResults.Count; if (numberDone >= Database.AllFins.Count) { if (RawErrorTracking != null && RawErrorTracking.Count > 0) { // Now that we're through matching, let's rescale the errors Dictionary <int, float> scaleFactors = new Dictionary <int, float>(); foreach (var idx in RawErrorTracking.Select(r => r.FactorIndex).Distinct()) { double minError = RawErrorTracking.Where(r => r.FactorIndex == idx).Min(r => r.Error); double maxError = RawErrorTracking.Where(r => r.FactorIndex == idx).Max(r => r.Error); // Force minError to 0 if it's not <= already if (minError > 0) { minError = 0; } scaleFactors[idx] = (float)(1 / (maxError - minError)); } foreach (var result in MatchResults.Results) { if (result.RawError != null && result.RawError.Count > 0) { double scaledError = 0.0; foreach (var raw in result.RawError) { scaledError += scaleFactors[raw.FactorIndex] * raw.Error * raw.Weight; } result.Error = scaledError; result.Confidence = 1.0f - scaledError; // For rounding issues so we prevent seeing "-0" in the UI if (result.Confidence < 0) { result.Confidence = 0; } } } } return(1.0f); } return((float)numberDone / Database.AllFins.Count); } }