public Match( DatabaseFin unknownFin, DarwinDatabase db, UpdateDisplayOutlinesDelegate updateOutlines, List <MatchFactor> matchFactors, bool createDefaultFactors = false) : this(unknownFin, db, updateOutlines) { if (!createDefaultFactors) { MatchFactors = matchFactors; } else if (Database.CatalogScheme != null) { switch (db.CatalogScheme.FeatureSetType) { case FeatureSetType.DorsalFin: SetMatchOptions(RegistrationMethodType.TrimOptimalTip, true); break; case FeatureSetType.Bear: MatchFactors = MatchFactorPresets.CreateBearMatchFactors(db); break; } } CheckUnknownForRequiredFeatures(); // Force rediscovery (for debugging/testing, can uncomment //UnknownFin.FinOutline.RediscoverFeaturePoints(Database.CatalogScheme.FeatureSetType); }
public Match( DatabaseFin unknownFin, DarwinDatabase db, UpdateDisplayOutlinesDelegate updateOutlines, RegistrationMethodType registrationMethod, bool useFullFinError) : this(unknownFin, db, updateOutlines) { SetMatchOptions(registrationMethod, useFullFinError); }
private static Vector <double> CalculateRatios( DatabaseFin individual, List <FeaturePointType> benchmarkFeatures, List <FeaturePointType> landmarkFeatures, List <IEnumerable <FeaturePointType> > ratioPermutations, bool useRemappedOutline = false) { var coordinates = new Dictionary <FeaturePointType, PointF>(); foreach (var featurePoint in landmarkFeatures) { if (useRemappedOutline) { coordinates[featurePoint] = individual.FinOutline.GetRemappedFeaturePointCoords(featurePoint); } else { coordinates[featurePoint] = individual.FinOutline.GetFeaturePointCoords(featurePoint); } } var benchmarkDistance = MathHelper.GetDistance( coordinates[benchmarkFeatures[0]].X, coordinates[benchmarkFeatures[0]].Y, coordinates[benchmarkFeatures[1]].X, coordinates[benchmarkFeatures[1]].Y); Vector <double> ratios = CreateVector.Dense <double>(ratioPermutations.Count - 1); int i = 0; foreach (var permutation in ratioPermutations) { var permutationList = permutation.ToList(); if ((permutationList[0] == benchmarkFeatures[0] && permutationList[1] == benchmarkFeatures[1]) || (permutationList[0] == benchmarkFeatures[1] && permutationList[1] == benchmarkFeatures[0])) { // This is our benchmark continue; } var currentDistance = MathHelper.GetDistance( coordinates[permutationList[0]].X, coordinates[permutationList[0]].Y, coordinates[permutationList[1]].X, coordinates[permutationList[1]].Y); ratios[i] = currentDistance / benchmarkDistance; i += 1; } return(ratios); }
private void ViewOutlineInformationButton_Click(object sender, RoutedEventArgs e) { if (_vm.SelectedResult != null) { var selectedDBFin = _vm.FullyLoadFinByID(_vm.SelectedResult.DatabaseID); selectedDBFin.FinOutline.ChainPoints = _vm.SelectedResult.dbContour; DatabaseFin copyUnknown = new DatabaseFin(_vm.DatabaseFin); copyUnknown.FinOutline.ChainPoints = _vm.SelectedResult.unknownContour; var outlineWindowVM = new OutlineWindowViewModel(_vm.Database, selectedDBFin, copyUnknown, _vm.SelectedResult); var outlineWindow = new OutlineWindow(outlineWindowVM); outlineWindow.Show(); } }
private void ViewUnknownImageButton_Click(object sender, RoutedEventArgs e) { if (_vm.SelectedResult != null) { var fin = new DatabaseFin(_vm.DatabaseFin); fin.FinOutline.ChainPoints = null; if (_vm.UnknownShowOriginalImage) { fin.FinImage = fin.OriginalFinImage; } var vm = new TraceWindowViewModel(fin, _vm.Database, "Viewing Unknown", null, true); TraceWindow traceWindow = new TraceWindow(vm); traceWindow.Show(); } }
private bool FilterListView(object item) { if (string.IsNullOrEmpty(FilterTextBox.Text)) { return(true); } DatabaseFin filterItem = item as DatabaseFin; if (filterItem == null) { return(true); } if (string.IsNullOrEmpty(filterItem.IDCode)) { return(false); } return(filterItem.IDCode.IndexOf(FilterTextBox.Text, StringComparison.OrdinalIgnoreCase) > 0); }
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) }); }
public Match( DatabaseFin unknownFin, DarwinDatabase db, UpdateDisplayOutlinesDelegate updateOutlines) { if (unknownFin == null) { throw new ArgumentNullException(nameof(unknownFin)); } if (db == null) { throw new ArgumentNullException(nameof(db)); } UnknownFin = new DatabaseFin(unknownFin); Database = db; _updateOutlines = updateOutlines; CurrentFinIndex = 0; MatchResults = new MatchResults(unknownFin.IDCode, unknownFin?.FinFilename, db?.Filename); }
public static MatchError ComputeEigenValueWeightedCosineDistance( RatioComparison ratioComparison, DatabaseFin unknownFin, DatabaseFin databaseFin, MatchOptions options) { if (ratioComparison == null) { throw new ArgumentNullException(nameof(ratioComparison)); } if (unknownFin == null) { throw new ArgumentNullException(nameof(unknownFin)); } if (databaseFin == null) { throw new ArgumentNullException(nameof(databaseFin)); } bool useRemappedOutline = false; var featureSetMatchOptions = options as FeatureSetMatchOptions; if (featureSetMatchOptions != null) { useRemappedOutline = featureSetMatchOptions.UseRemappedOutline; } // TODO: Some of this is the same for each comparison, so we're doing extra work here. Might want to refactor some of this. Vector <double> unknownRatiosUncorrected = CalculateRatios(unknownFin, ratioComparison.BenchmarkFeatures, ratioComparison.LandmarkFeatures, ratioComparison.RatioPermutations, useRemappedOutline); var unknownRatios = unknownRatiosUncorrected - ratioComparison.AverageRatios; var unknownRHatMatrix = unknownRatios.ToRowMatrix() * ratioComparison.ProjectionMatrix; ratioComparison.UnknownRHat = CreateVector.Dense <double>(unknownRHatMatrix.ColumnCount); for (int i = 0; i < unknownRHatMatrix.ColumnCount; i++) { ratioComparison.UnknownRHat[i] = unknownRHatMatrix[0, i]; } var databaseRHat = ratioComparison.IndividualRatios .Where(ir => ir.DatabaseFin == databaseFin) .Select(ir => ir.RHat) .First(); var databaseRawRatios = ratioComparison.IndividualRatios .Where(ir => ir.DatabaseFin == databaseFin) .Select(ir => ir.RawRatios) .First(); double numerator = 0; double probeValue = 0; double galleryValue = 0; for (int i = 0; i < ratioComparison.UnknownRHat.Count; i++) { numerator += (databaseRHat[i] * ratioComparison.UnknownRHat[i]) / Math.Pow(ratioComparison.EigenValues[i], 2); probeValue += Math.Pow(ratioComparison.UnknownRHat[i] / ratioComparison.EigenValues[i], 2); galleryValue += Math.Pow(databaseRHat[i] / ratioComparison.EigenValues[i], 2); } numerator *= -1; double denominator = Math.Sqrt(probeValue * galleryValue); double ewcDistance = numerator / denominator; Trace.WriteLine("Unknown: " + unknownFin.IDCode + " DB: " + databaseFin.IDCode + " EWC Distance: " + ewcDistance); // Raw EWC is between -1 and 1. -1 is the best match, 1 is the worst return(new MatchError { Error = ewcDistance + 1, // Adding 1 so this should always be positive with 0 as the "best" match, RHat = ratioComparison.UnknownRHat, RawRatios = unknownRatiosUncorrected, DBRawRatios = databaseRawRatios, DBRHat = databaseRHat }); }
public static MatchError ComputeMahalanobisDistance( RatioComparison ratioComparison, DatabaseFin unknownFin, DatabaseFin databaseFin, MatchOptions options) { if (ratioComparison == null) { throw new ArgumentNullException(nameof(ratioComparison)); } if (unknownFin == null) { throw new ArgumentNullException(nameof(unknownFin)); } if (databaseFin == null) { throw new ArgumentNullException(nameof(databaseFin)); } bool useRemappedOutline = false; var featureSetMatchOptions = options as FeatureSetMatchOptions; if (featureSetMatchOptions != null) { useRemappedOutline = featureSetMatchOptions.UseRemappedOutline; } // TODO: Some of this is the same for each comparison, so we're doing extra work here. Might want to refactor some of this. Vector <double> unknownRatiosUncorrected = CalculateRatios(unknownFin, ratioComparison.BenchmarkFeatures, ratioComparison.LandmarkFeatures, ratioComparison.RatioPermutations, useRemappedOutline); var unknownRatios = unknownRatiosUncorrected - ratioComparison.AverageRatios; var unknownRHatMatrix = unknownRatios.ToRowMatrix() * ratioComparison.ProjectionMatrix; ratioComparison.UnknownRHat = CreateVector.Dense <double>(unknownRHatMatrix.ColumnCount); for (int i = 0; i < unknownRHatMatrix.ColumnCount; i++) { ratioComparison.UnknownRHat[i] = unknownRHatMatrix[0, i]; } var databaseRHat = ratioComparison.IndividualRatios .Where(ir => ir.DatabaseFin == databaseFin) .Select(ir => ir.RHat) .First(); var databaseRawRatios = ratioComparison.IndividualRatios .Where(ir => ir.DatabaseFin == databaseFin) .Select(ir => ir.RawRatios) .First(); double mahalanobisDistanceSum = 0; for (int i = 0; i < ratioComparison.UnknownRHat.Count; i++) { mahalanobisDistanceSum += Math.Abs(ratioComparison.UnknownRHat[i] - databaseRHat[i]) / ratioComparison.EigenValues[i]; } Trace.WriteLine("Unknown: " + unknownFin.IDCode + " DB: " + databaseFin.IDCode + " Mahalanobis Distance: " + mahalanobisDistanceSum); return(new MatchError { Error = mahalanobisDistanceSum, RHat = ratioComparison.UnknownRHat, RawRatios = unknownRatiosUncorrected, DBRawRatios = databaseRawRatios, DBRHat = databaseRHat }); }
//******************************************************************* // // 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); } }
// 1.1 - the following functions used in MatchQueue context public static MatchResults Load(string fileName, out DarwinDatabase db, out DatabaseFin databaseFin) { MatchResults result = new MatchResults(); db = null; databaseFin = null; if (string.IsNullOrEmpty(fileName)) { throw new ArgumentNullException(nameof(fileName)); } if (!File.Exists(fileName)) { throw new ArgumentOutOfRangeException(nameof(fileName)); } var lines = File.ReadAllLines(fileName); if (lines.Length < 6 || !lines[0].StartsWith("Results for ID:")) { return(result); } result._finID = lines[0].Substring(lines[0].LastIndexOf(":") + 1).Trim(); result.TracedFinFile = lines[1].Substring(lines[1].IndexOf(":") + 1).Trim(); result.DatabaseFile = lines[2].Substring(lines[2].IndexOf(":") + 1).Trim(); db = CatalogSupport.OpenDatabase(result.DatabaseFile, Options.CurrentUserOptions.DefaultCatalogScheme, false); //***2.2 - if we can, determine if database path has just changed drive letter // or some part of path that is a prefix to the SurveyArea. If the database // is in the same SurveyArea and has the same database name, then we can // proceed with the building of the MatchResults //int p = mDatabaseFile.find("surveyAreas"); //string rqdAreaAndDB = mDatabaseFile.substr(p); // survey area and database name //string rqdPreamble = mDatabaseFile.substr(0, p); // strip it to get preamble //string currentDBFile = db->getFilename(); //p = currentDBFile.find("surveyAreas"); //string currentAreaAndDB = currentDBFile.substr(p); // survey area and catalog name //string currentPreamble = currentDBFile.substr(0, p); // strip it to get preamble //if (db.Filename.ToLower() != DatabaseFile.ToLower()) //{ // //cout << mDatabaseFile << endl; // //cout << rqdPreamble << " " << rqdAreaAndDB << endl; // //cout << currentDBFile << endl; // //cout << currentPreamble << " " << currentAreaAndDB << endl; // if (currentAreaAndDB == rqdAreaAndDB) // { // string msg = "The Survey Area and Catalog for the match results appear corrrect,\n"; // msg += "but the path to DARWIN's home folder seems to have changed.\n"; // msg += "Is it OK to open the indicated catalog in the current location\n"; // msg += "as shown below?\n\n"; // msg += (currentPreamble + rqdAreaAndDB); // Trace.WriteLine(msg); // //ErrorDialog *err = new ErrorDialog(msg); // //err->show(); // //***2.2 - path possibly has to be fixed to FIN file as well // if ((currentDBFile != mDatabaseFile) && (currentAreaAndDB == rqdAreaAndDB)) // { // //add preamble of current DARWINHOME to FINs relative location // p = mTracedFinFile.find("surveyAreas"); // string currentFinAreaPlus = mTracedFinFile.substr(p); // mTracedFinFile = currentPreamble + currentFinAreaPlus; // cout << mTracedFinFile << endl; // } // } // else // { // string msg = "The WRONG database is currently loaded for viewing these results ...\n\n"; // msg += "LOADED DB:\n " + db->getFilename() + "\n\n"; // msg += "REQUIRED DB:\n " + mDatabaseFile + "\n\n"; // msg += "Please load the required database from the main window\n"; // msg += "and then reload the desired results file."; // //ErrorDialog *err = new ErrorDialog(msg); // //err->show(); // //***2.22 - replacing own ErrorDialog with GtkMessageDialogs // GtkWidget* errd = gtk_message_dialog_new(NULL, // GTK_DIALOG_DESTROY_WITH_PARENT, // GTK_MESSAGE_ERROR, // GTK_BUTTONS_CLOSE, // msg.c_str()); // gtk_dialog_run(GTK_DIALOG(errd)); // gtk_widget_destroy(errd); // return NULL; // } //} DatabaseFin unkFin = CatalogSupport.OpenFinz(result.TracedFinFile); // get match info on each matched database fin // After skipping some of the headers for (int i = 6; i < lines.Length; i++) { string line = lines[i]; int pos = line.IndexOf("\t"); //string rank = line.Substring(0, pos); line = line.Substring(pos + 1); pos = line.IndexOf("\t"); string error = line.Substring(0, pos); line = line.Substring(pos + 1); pos = line.IndexOf("\t"); string dbFinID = line.Substring(0, pos); line = line.Substring(pos + 1); //cout << "dbFinID[" << dbFinID << "]"; //*** 2.2 - show for now string numStr; int dbFinPosition, uBegin, uTip, uEnd, dbBegin, dbTip, dbEnd; pos = line.IndexOf("\t"); numStr = line.Substring(0, pos); line = line.Substring(pos + 1); dbFinPosition = int.Parse(numStr); //cout << "[" << dbFinPosition << "]" << endl; //*** 2.2 - show for now pos = line.IndexOf("\t"); numStr = line.Substring(0, pos); uBegin = int.Parse(numStr); line = line.Substring(pos + 1); //cout << "[" << uBegin << "]"; pos = line.IndexOf("\t"); numStr = line.Substring(0, pos); uTip = int.Parse(numStr); line = line.Substring(pos + 1); //cout << "[" << uTip << "]"; pos = line.IndexOf("\t"); numStr = line.Substring(0, pos); uEnd = int.Parse(numStr); line = line.Substring(pos + 1); //cout << "[" << uEnd << "]"; pos = line.IndexOf("\t"); numStr = line.Substring(0, pos); dbBegin = int.Parse(numStr); line = line.Substring(pos + 1); //cout << "[" << dbBegin << "]"; pos = line.IndexOf("\t"); numStr = line.Substring(0, pos); dbTip = int.Parse(numStr); line = line.Substring(pos + 1); //cout << "[" << dbTip << "]"; pos = line.IndexOf("\t"); numStr = line.Substring(0, pos); dbEnd = int.Parse(numStr); line = line.Substring(pos + 1); //cout << "[" << dbEnd << "]"; //string damage = line; //cout << "[" << damage << "]" << endl; // The position is written starting at 1, but our index is 0 based DatabaseFin thisDBFin = db.AllFins[dbFinPosition - 1]; // TODO: Should this throw an exception instead? if (thisDBFin.IDCode != dbFinID) { Trace.WriteLine("Disaster " + thisDBFin.IDCode + " " + dbFinID); } FloatContour mappedUnknownContour = unkFin.FinOutline.ChainPoints.MapContour( unkFin.FinOutline.ChainPoints[uTip], unkFin.FinOutline.ChainPoints[uBegin], unkFin.FinOutline.ChainPoints[uEnd], thisDBFin.FinOutline.ChainPoints[dbTip], thisDBFin.FinOutline.ChainPoints[dbBegin], thisDBFin.FinOutline.ChainPoints[dbEnd]); Result r = new Result( mappedUnknownContour, //***1.3 - Mem Leak - constructor make copy now thisDBFin.FinOutline.ChainPoints, //***1.3 - Mem Leak - constructor make copy now thisDBFin.ID, // TODO - This image filename stuff is a little ugly. (string.IsNullOrEmpty(thisDBFin.OriginalImageFilename)) ? thisDBFin.ImageFilename : thisDBFin.OriginalImageFilename, thisDBFin.ThumbnailFilenameUri, dbFinPosition - 1, // position of fin in database double.Parse(error), thisDBFin.IDCode, thisDBFin.Name, thisDBFin.DamageCategory, thisDBFin.DateOfSighting, thisDBFin.LocationCode); r.SetMappingControlPoints( uBegin, uTip, uEnd, // beginning, tip & end of unknown fin dbBegin, dbTip, dbEnd); // beginning, tip & end of database fin result.Results.Add(r); } databaseFin = unkFin; result.SetRankings(); return(result); }