using ShellProgressBar; using System.Text.Json; using View_by_Distance.FaceRecognitionDotNet; using View_by_Distance.Map.Models; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Properties; namespace View_by_Distance.Distance.Models; public class E_Distance : Shared.Models.Methods.IFaceDistance { private readonly List _Moved; private readonly List _Debug; private readonly List _Renamed; private readonly Serilog.ILogger? _Log; private readonly string _ResultAllInOne; private readonly int _FaceDistancePermyriad; private readonly bool _DistanceRenameToMatch; private readonly double _FaceDistanceTolerance; private readonly int _SortingDaysDeltaTolerance; private readonly bool _DistanceMoveUnableToMatch; private readonly List _AllMappedFaceFiles; private readonly int _DistancePixelDistanceTolerance; private readonly List _AllMappedFaceFileNames; private readonly double _FaceDistanceMinimumConfidence; private readonly List _DuplicateMappedFaceFiles; private readonly int _FaceDistanceAreaPermilleTolerance; private readonly int _SortingMaximumPerFaceShouldBeHigh; public E_Distance(bool distanceMoveUnableToMatch, int distancePixelDistanceTolerance, bool distanceRenameToMatch, int faceDistanceAreaPermilleTolerance, double faceDistanceMinimumConfidence, int faceDistancePermyriad, double faceDistanceTolerance, string resultAllInOne, int sortingDaysDeltaTolerance, int sortingMaximumPerFaceShouldBeHigh) { _Debug = new(); _Moved = new(); _Renamed = new(); _AllMappedFaceFiles = new(); _AllMappedFaceFileNames = new(); _ResultAllInOne = resultAllInOne; _DuplicateMappedFaceFiles = new(); _Log = Serilog.Log.ForContext(); _DistanceRenameToMatch = distanceRenameToMatch; _FaceDistancePermyriad = faceDistancePermyriad; _FaceDistanceTolerance = faceDistanceTolerance; _DistanceMoveUnableToMatch = distanceMoveUnableToMatch; _SortingDaysDeltaTolerance = sortingDaysDeltaTolerance; _FaceDistanceMinimumConfidence = faceDistanceMinimumConfidence; _DistancePixelDistanceTolerance = distancePixelDistanceTolerance; _FaceDistanceAreaPermilleTolerance = faceDistanceAreaPermilleTolerance; _SortingMaximumPerFaceShouldBeHigh = sortingMaximumPerFaceShouldBeHigh; } public static void SaveFaceDistances(Property.Models.Configuration configuration, SortingContainer[] sortingContainers) { string eDistanceContentCollectionDirectory = Property.Models.Stateless.IResult.GetResultsDateGroupDirectory(configuration, nameof(E_Distance), "([])"); if (!Directory.Exists(eDistanceContentCollectionDirectory)) _ = Directory.CreateDirectory(eDistanceContentCollectionDirectory); #pragma warning disable string[] results = (from l in sortingContainers select l.ToString()).ToArray(); #pragma warning restore string eDistanceContentFileName = Path.Combine(eDistanceContentCollectionDirectory, $"{configuration.ResultAllInOne}.tvs"); File.WriteAllLines(eDistanceContentFileName, results); } private static List GetSortingCollection(MapLogic mapLogic, List faceDistanceEncodings, int faceDistanceContainersLength, int i, FaceDistance faceDistanceEncoding) { List results; List faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding); if (faceDistanceLengths.Count != faceDistanceContainersLength) throw new NotSupportedException(); results = mapLogic.GetSortingCollection(i, faceDistanceEncoding, faceDistanceLengths); return results; } private List GetSortingContainers(Face face, FaceDistance faceDistanceEncoding, List sortingCollection, int? useFiltersCounter) { List results = new(); SortingContainer sortingContainer; Sorting[] collection = Shared.Models.Stateless.Methods.ISorting.Sort(sortingCollection); double faceDistancePermyriad; double sortingDaysDeltaTolerance; double faceDistanceMinimumConfidence; double faceDistanceAreaPermilleTolerance; if (useFiltersCounter is null) { sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance; faceDistancePermyriad = _FaceDistancePermyriad; faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance; faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence; } else if (useFiltersCounter.Value == 1) { sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 1.5; faceDistancePermyriad = _FaceDistancePermyriad; faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance; faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence; } else if (useFiltersCounter.Value == 2) { sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance; faceDistancePermyriad = _FaceDistancePermyriad * 1.5; faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance; faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence; } else if (useFiltersCounter.Value == 3) { sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance; faceDistancePermyriad = _FaceDistancePermyriad; faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance; faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .5; } else if (useFiltersCounter.Value == 4) { sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 2; faceDistancePermyriad = _FaceDistancePermyriad * 1.5; faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance; faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .5; } else if (useFiltersCounter.Value == 5) { sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 1.5; faceDistancePermyriad = _FaceDistancePermyriad * 2; faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance; faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .5; } else if (useFiltersCounter.Value == 6) { sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 1.5; faceDistancePermyriad = _FaceDistancePermyriad * 1.5; faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance; faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .25; } else if (useFiltersCounter.Value == 7) { sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 2.5; faceDistancePermyriad = _FaceDistancePermyriad * 2; faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance; faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .25; } else if (useFiltersCounter.Value == 8) { sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 2; faceDistancePermyriad = _FaceDistancePermyriad * 2.5; faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance; faceDistanceMinimumConfidence = _FaceDistanceMinimumConfidence * .25; } else if (useFiltersCounter.Value == 9) { sortingDaysDeltaTolerance = _SortingDaysDeltaTolerance * 2; faceDistancePermyriad = _FaceDistancePermyriad * 2; faceDistanceAreaPermilleTolerance = _FaceDistanceAreaPermilleTolerance; faceDistanceMinimumConfidence = 0; } else { sortingDaysDeltaTolerance = int.MaxValue; faceDistancePermyriad = int.MaxValue; faceDistanceAreaPermilleTolerance = 0; faceDistanceMinimumConfidence = 0; } foreach (Sorting sorting in collection) { if (face.Mapping is null || faceDistanceEncoding.NormalizedPixelPercentage is null) throw new NotSupportedException(); if (sorting.DaysDelta > sortingDaysDeltaTolerance || sorting.DistancePermyriad > faceDistancePermyriad || face.Mapping.MappingFromLocation.Confidence < faceDistanceMinimumConfidence || face.Mapping.MappingFromLocation.AreaPermille < faceDistanceAreaPermilleTolerance) continue; sortingContainer = new(face.Mapping, sorting); results.Add(sortingContainer); if (results.Count >= _SortingMaximumPerFaceShouldBeHigh) break; } return results; } private static FaceDistanceContainer[] GetOrderedFaceDistanceContainers(List collection) { FaceDistanceContainer[] results; results = (from l in collection orderby l.FaceDistance.Encoding is not null select l).ToArray(); if (results.Any() && results[0].FaceDistance.Encoding is null) throw new Exception("Sorting failed!"); return results; } private static FaceDistanceContainer[] GetOrderedFaceDistanceContainers(Face[] distinctFilteredFaces) { FaceDistanceContainer[] results; FaceDistance faceDistance; FaceDistanceContainer faceDistanceContainer; List collection = new(); foreach (Face face in distinctFilteredFaces) { if (face.Mapping is null) throw new NotSupportedException(); if (face.FaceDistance?.Encoding is not FaceRecognitionDotNet.FaceEncoding faceEncoding) continue; faceDistance = new(face.Mapping.MappingFromLocation.Confidence, faceEncoding, face.Mapping.MappingFromItem.Id, face.Mapping.MappingFromItem.IsWrongYear, face.Mapping.MappingFromItem.MinimumDateTime, face.Mapping.MappingFromLocation.NormalizedPixelPercentage); faceDistanceContainer = new(face, faceDistance); collection.Add(faceDistanceContainer); } results = GetOrderedFaceDistanceContainers(collection); return results; } private static List GetFaceDistanceEncodings(FaceDistanceContainer[] faceDistanceContainers) { List faceDistanceEncodings = new(); foreach (FaceDistanceContainer faceDistanceContainer in faceDistanceContainers) { if (faceDistanceContainer.FaceDistance.Encoding is null) continue; faceDistanceEncodings.Add(faceDistanceContainer.FaceDistance); } return faceDistanceEncodings; } public SortingContainer[] SetFaceMappingSortingCollectionThenGetSortingContainers(int maxDegreeOfParallelism, long ticks, MapLogic mapLogic, Face[] distinctFilteredFaces, int? useFiltersCounter) { SortingContainer[] results; List collection = new(); int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; FaceDistanceContainer[] faceDistanceContainers = GetOrderedFaceDistanceContainers(distinctFilteredFaces); List faceDistanceEncodings = GetFaceDistanceEncodings(faceDistanceContainers); string message = $") {faceDistanceContainers.Length:000} Get Sorting Containers Then Set Face Mapping Sorting Collection - {totalSeconds} total second(s)"; ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; using ProgressBar progressBar = new(faceDistanceContainers.Length, message, options); _ = Parallel.For(0, faceDistanceContainers.Length, parallelOptions, (i, state) => { progressBar.Tick(); Face face = faceDistanceContainers[i].Face; if (face.Mapping is null) throw new NotSupportedException(); FaceDistance faceDistanceEncoding = faceDistanceContainers[i].FaceDistance; if (mapLogic.Used(faceDistanceEncoding)) return; List sortingCollection = GetSortingCollection(mapLogic, faceDistanceEncodings, faceDistanceContainers.Length, i, faceDistanceEncoding); if (!sortingCollection.Any()) return; List sortingContainers = GetSortingContainers(face, faceDistanceEncoding, sortingCollection, useFiltersCounter); if (sortingContainers.Any()) { lock (collection) collection.AddRange(sortingContainers); } }); if (!collection.Any()) results = Array.Empty(); else results = Shared.Models.Stateless.Methods.ISortingContainer.Sort(collection); return results; } public static Mapping[] GetSelectedMappingCollection(Face[] distinctFilteredFaces) { Mapping[] results; IEnumerable collection = from l in distinctFilteredFaces orderby l.Mapping?.MappingFromItem.MinimumDateTime descending select l.Mapping; results = (from l in collection where l is not null select l).ToArray(); return results; } public static void SetFaceDistances(int maxDegreeOfParallelism, long ticks, Face[] distinctFilteredFaces) { int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; string message = $") {distinctFilteredFaces.Length:000} Load Face Encoding - {totalSeconds} total second(s)"; ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; using ProgressBar progressBar = new(distinctFilteredFaces.Length, message, options); _ = Parallel.For(0, distinctFilteredFaces.Length, parallelOptions, (i, state) => { Face face = distinctFilteredFaces[i]; if (face.FaceEncoding is null || face.Mapping is null) throw new NotSupportedException(); if (face.FaceDistance?.Encoding is not null && face.FaceDistance.Encoding is FaceRecognitionDotNet.FaceEncoding faceEncoding) return; progressBar.Tick(); faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); FaceDistance faceDistance = new(face.Mapping.MappingFromLocation.Confidence, faceEncoding, face.Mapping.MappingFromItem.Id, face.Mapping.MappingFromItem.IsWrongYear, face.Mapping.MappingFromItem.MinimumDateTime, face.Mapping.MappingFromLocation.NormalizedPixelPercentage); lock (face) face.SetFaceDistance(faceDistance); }); } void Shared.Models.Methods.IFaceDistance.SavePossiblyNewPersonContainers(IPropertyConfiguration propertyConfiguration, string personBirthdayFormat, string facesFileNameExtension, string? a2PeopleSingletonDirectory, Dictionary personKeyToPersonContainer, List<(string[], PersonContainer)> possiblyNewPersonDisplayDirectoryNamesAndPersonContainer) { char @char; string json; string[] files; string checkFile; string[] segments; const int zero = 0; string personKeyFormatted; string personDisplayDirectory; PersonBirthday personBirthday; string personDisplayDirectoryName; string checkPersonDisplayDirectory; string checkPersonKeyFormattedDirectory; char[] chars = Shared.Models.Stateless.Methods.IAge.GetChars(); JsonSerializerOptions jsonSerializerOptions = new() { WriteIndented = true }; foreach ((string[] personDisplayDirectoryNames, PersonContainer personContainer) in possiblyNewPersonDisplayDirectoryNamesAndPersonContainer) { if (a2PeopleSingletonDirectory is null || personContainer.Key is null || personContainer.Birthdays is null || !personContainer.Birthdays.Any()) continue; personBirthday = personContainer.Birthdays[zero]; personDisplayDirectoryName = personDisplayDirectoryNames[^1]; personDisplayDirectory = Path.Combine(personDisplayDirectoryNames); personKeyFormatted = Shared.Models.Stateless.Methods.IPersonBirthday.GetFormatted(personBirthdayFormat, personBirthday); segments = personDisplayDirectoryName.Split(chars); if (segments.Length != 2) @char = '_'; else @char = personDisplayDirectoryName[segments[zero].Length]; checkPersonDisplayDirectory = Path.Combine(a2PeopleSingletonDirectory, @char.ToString(), personDisplayDirectoryName); checkPersonKeyFormattedDirectory = Path.Combine(checkPersonDisplayDirectory, personKeyFormatted); if (Directory.Exists(checkPersonKeyFormattedDirectory)) continue; _ = Directory.CreateDirectory(checkPersonKeyFormattedDirectory); checkFile = Path.Combine(checkPersonKeyFormattedDirectory, $"{personKeyFormatted}.json"); json = JsonSerializer.Serialize(personContainer.Person, jsonSerializerOptions); _ = Shared.Models.Stateless.Methods.IPath.WriteAllText(checkFile, json, updateDateWhenMatches: false, compareBeforeWrite: true); if (!Directory.Exists(personDisplayDirectory)) continue; files = Directory.GetFiles(personDisplayDirectory, $"*{facesFileNameExtension}", SearchOption.TopDirectoryOnly); foreach (string file in files) { checkFile = Path.Combine(checkPersonDisplayDirectory, Path.GetFileName(file)); if (File.Exists(checkFile)) continue; File.Copy(files[0], checkFile); break; } } } private void MoveUnableToMatch(string eDistanceContentDirectory, string mappedFaceFile, string mappedFaceFileName) { bool check; string? directoryName = Path.GetDirectoryName(mappedFaceFile); if (mappedFaceFileName is null || directoryName is null) check = false; else { if (string.IsNullOrEmpty(directoryName) || string.IsNullOrEmpty(directoryName) || !directoryName.Contains(eDistanceContentDirectory)) check = false; else { List directoryNames = new(); string? checkDirectoryName = directoryName; for (int i = 0; i < int.MaxValue; i++) { if (string.IsNullOrEmpty(checkDirectoryName)) continue; directoryNames.Add(Path.GetFileName(checkDirectoryName)); checkDirectoryName = Path.GetDirectoryName(checkDirectoryName); if (string.IsNullOrEmpty(checkDirectoryName)) continue; if (checkDirectoryName == eDistanceContentDirectory) break; } if (string.IsNullOrEmpty(checkDirectoryName) || !directoryNames.Any() || !long.TryParse(directoryNames[^1][1..^1], out long directoryTicks)) { check = false; File.Delete(mappedFaceFile); } else { checkDirectoryName = Path.Combine(checkDirectoryName, $"({directoryTicks}{_FaceDistanceTolerance.ToString()[1..]})"); for (int i = directoryNames.Count - 1 - 1; i > -1; i--) checkDirectoryName = Path.Combine(checkDirectoryName, directoryNames[i]); if (!Directory.Exists(checkDirectoryName)) _ = Directory.CreateDirectory(checkDirectoryName); File.Move(mappedFaceFile, Path.Combine(checkDirectoryName, mappedFaceFileName)); check = true; } } } if (check) _Moved.Add(mappedFaceFile); } public static string? GetFaceEncoding(string file) { string? result; List results = new(); const string comment = "Comment: "; if (File.Exists(file)) { IReadOnlyList directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(file); foreach (MetadataExtractor.Directory directory in directories) { if (directory.Name != "PNG-tEXt") continue; foreach (MetadataExtractor.Tag tag in directory.Tags) { if (tag.Name != "Textual Data" || string.IsNullOrEmpty(tag.Description)) continue; if (!tag.Description.StartsWith(comment)) continue; results.Add(tag.Description); } } } result = results.Any() ? results[0][comment.Length..] : null; return result; } public static string? GetFaceLocation(string file) { string? result; List results = new(); const string artist = "Artist: "; if (File.Exists(file)) { IReadOnlyList directories = MetadataExtractor.ImageMetadataReader.ReadMetadata(file); foreach (MetadataExtractor.Directory directory in directories) { if (directory.Name != "PNG-tEXt") continue; foreach (MetadataExtractor.Tag tag in directory.Tags) { if (tag.Name != "Textual Data" || string.IsNullOrEmpty(tag.Description)) continue; if (!tag.Description.StartsWith(artist)) continue; results.Add(tag.Description); } } } result = results.Any() ? results[0][artist.Length..] : null; return result; } private static FaceDistanceContainer[] GetOrderedFaceDistanceContainers(MappingFromItem mappingFromItem, List faces) { FaceDistanceContainer[] results; FaceDistance faceDistance; int normalizedPixelPercentage; FaceDistanceContainer faceDistanceContainer; List collection = new(); foreach (Face face in faces) { if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null) throw new NotSupportedException(); normalizedPixelPercentage = Shared.Models.Stateless.Methods.ILocation.GetNormalizedPixelPercentage(face.Location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution); if (face.FaceDistance?.Encoding is not null && face.FaceDistance.Encoding is FaceRecognitionDotNet.FaceEncoding faceEncoding) faceDistance = new(face.Location.Confidence, faceEncoding, mappingFromItem.Id, mappingFromItem.IsWrongYear, mappingFromItem.MinimumDateTime, normalizedPixelPercentage); else { faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); faceDistance = new(face.Location.Confidence, faceEncoding, mappingFromItem.Id, mappingFromItem.IsWrongYear, mappingFromItem.MinimumDateTime, normalizedPixelPercentage); lock (faces) face.SetFaceDistance(faceDistance); } faceDistanceContainer = new(face, faceDistance); collection.Add(faceDistanceContainer); } results = GetOrderedFaceDistanceContainers(collection); return results; } private static List<(Face Face, double? Length)> GetValues(MappingFromItem mappingFromItem, List faces, string json) { List<(Face Face, double? Length)> results = new(); Face face; FaceDistance faceDistanceLength; Shared.Models.FaceEncoding? modelsFaceEncoding = JsonSerializer.Deserialize(json); if (modelsFaceEncoding is null) throw new NotSupportedException(); FaceRecognitionDotNet.FaceEncoding faceRecognitionDotNetFaceEncoding = FaceRecognition.LoadFaceEncoding(modelsFaceEncoding.RawEncoding); FaceDistance faceDistanceEncoding = new(faceRecognitionDotNetFaceEncoding); FaceDistanceContainer[] faceDistanceContainers = GetOrderedFaceDistanceContainers(mappingFromItem, faces); int faceDistanceContainersLength = faceDistanceContainers.Length; if (faceDistanceContainersLength != faces.Count) throw new NotSupportedException(); List faceDistanceEncodings = GetFaceDistanceEncodings(faceDistanceContainers); if (faceDistanceEncodings.Count != faces.Count) throw new NotSupportedException(); List faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding); if (faceDistanceLengths.Count != faceDistanceContainersLength) throw new NotSupportedException(); for (int i = 0; i < faces.Count; i++) { face = faces[i]; faceDistanceLength = faceDistanceLengths[i]; if (faceDistanceLength.Length is null) throw new NotSupportedException(); results.Add(new(face, faceDistanceLength.Length.Value)); } return results; } private (Face, double?)[] GetClosestFaceByDistanceIgnoringTolerance(MappingFromItem mappingFromItem, List faces, string json) { (Face, double?)[] results; List<(Face Face, double? Length)> collection = GetValues(mappingFromItem, faces, json); results = (from l in collection orderby l.Length select l).Take(1).ToArray(); (Face face, double? length) = results.First(); lock (_Debug) _Debug.Add(length); return results; } static (int?, int?) GetXY(int normalizedPixelPercentage, OutputResolution? outputResolution) { int? x; int? y; if (outputResolution is null) { x = null; y = null; } else { string normalizedPixelPercentagePadded = Shared.Models.Stateless.Methods.ILocation.GetLeftPadded(Shared.Models.Stateless.ILocation.Digits, normalizedPixelPercentage); (x, y) = Shared.Models.Stateless.Methods.ILocation.GetXY(Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, outputResolution.Width, outputResolution.Height, normalizedPixelPercentagePadded); } return new(x, y); } private (Face, double?)[] GetClosestFaceByPixel(List faces, int? x1, int? y1) { (Face, double?)[] results; int? x2; int? y2; double distance; int normalizedPixelPercentageLoop; string normalizedPixelPercentagePadded; List<(Face Face, double? Order)> collection = new(); if (x1 is null || y1 is null) throw new NotSupportedException(); foreach (Face face in faces) { if (face.Location is null || face.OutputResolution is null) throw new NotSupportedException(); normalizedPixelPercentageLoop = Shared.Models.Stateless.Methods.ILocation.GetNormalizedPixelPercentage(face.Location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution); normalizedPixelPercentagePadded = Shared.Models.Stateless.Methods.ILocation.GetLeftPadded(Shared.Models.Stateless.ILocation.Digits, normalizedPixelPercentageLoop); (x2, y2) = Shared.Models.Stateless.Methods.ILocation.GetXY(Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution.Width, face.OutputResolution.Height, normalizedPixelPercentagePadded); if (x2 is null || y2 is null) throw new NotSupportedException(); distance = Math.Sqrt(Math.Pow(x1.Value - x2.Value, 2) + Math.Pow(y1.Value - y2.Value, 2)); collection.Add(new(face, distance)); } results = (from l in collection orderby l.Order where l.Order < _DistancePixelDistanceTolerance select l).Take(1).ToArray(); return results; } private (Face, double?)[] GetClosestFaceByPixel(List faces, int normalizedPixelPercentage) { if (!faces.Any()) throw new NotSupportedException(); (Face, double?)[] results; const int zero = 0; OutputResolution? outputResolution = faces[zero].OutputResolution; (int? x1, int? y1) = GetXY(normalizedPixelPercentage, outputResolution); results = GetClosestFaceByPixel(faces, x1, y1); return results; } private (Face, double?)[] GetClosestFaceByPixel(List faces, string json) { if (!faces.Any()) throw new NotSupportedException(); (Face, double?)[] results; const int zero = 0; OutputResolution? outputResolution = faces[zero].OutputResolution; if (outputResolution is null) throw new NullReferenceException(nameof(outputResolution)); Location? location = JsonSerializer.Deserialize(json); if (location is null) throw new NullReferenceException(nameof(location)); int normalizedPixelPercentage = Shared.Models.Stateless.Methods.ILocation.GetNormalizedPixelPercentage(location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, outputResolution); (int? x1, int? y1) = GetXY(normalizedPixelPercentage, outputResolution); results = GetClosestFaceByPixel(faces, x1, y1); return results; } private static List<(Face, double?)> GetMatchingFacesByFaceEncoding(List faces, string? json) { List<(Face, double?)> results = new(); string check; foreach (Face face in faces) { if (json is null || face.FaceEncoding is null) continue; if (!json.Contains(face.FaceEncoding.RawEncoding[0].ToString())) continue; check = JsonSerializer.Serialize(face.FaceEncoding); if (check != json) continue; results.Add(new(face, 0)); } return results; } private static FileInfo? CheckFileThenGetFileInfo(string facesFileNameExtension, MappingFromItem mappingFromItem, string mappedFaceFile, List<(Face, double?)> checkFaces) { FileInfo? result = null; string checkFile; string? mappedFaceDirectory; string deterministicHashCodeKey; foreach ((Face face, _) in checkFaces) { if (checkFaces.Count != 1) break; if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null) throw new NotSupportedException(); mappedFaceDirectory = Path.GetDirectoryName(mappedFaceFile); if (mappedFaceDirectory is null) throw new NotSupportedException(); deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(mappingFromItem.Id, face.Location, Shared.Models.Stateless.ILocation.Digits, Shared.Models.Stateless.ILocation.Factor, face.OutputResolution); checkFile = Path.Combine(mappedFaceDirectory, $"{deterministicHashCodeKey}{mappingFromItem.ImageFileHolder.ExtensionLowered}{facesFileNameExtension}"); if (checkFile == mappedFaceFile) continue; result = new FileInfo(checkFile); } return result; } private void AppendMatchingDuplicates(string mappedFaceFile, string[] matches) { string checkFile; FileInfo fileInfo = new(mappedFaceFile); List<(long Length, string FullName)> collection = new(); if (fileInfo.Exists) collection.Add(new(fileInfo.Length, fileInfo.FullName)); lock (_DuplicateMappedFaceFiles) _DuplicateMappedFaceFiles.Add(mappedFaceFile); foreach (string match in matches) { fileInfo = new(match); if (!fileInfo.Exists) continue; collection.Add(new(fileInfo.Length, fileInfo.FullName)); break; } collection = collection.OrderBy(l => l.Length).ToList(); for (int i = 0; i < collection.Count - 1; i++) { checkFile = string.Concat(collection[i].FullName, ".dup"); if (File.Exists(checkFile)) continue; File.Move(collection[i].FullName, checkFile); } } public void LookForMatchFacesAndPossiblyRename(string facesFileNameExtension, string? eDistanceContentDirectory, MappingFromItem mappingFromItem, List faces, List<(string MappedFaceFile, int normalizedPixelPercentage)> collection) { string? json; string[] matches; FileInfo? fileInfo; string mappedFaceFileName; List<(Face, double?)> checkFaces = new(); foreach ((string mappedFaceFile, int normalizedPixelPercentage) in collection) { mappedFaceFileName = Path.GetFileName(mappedFaceFile); if (_DuplicateMappedFaceFiles.Contains(mappedFaceFileName)) continue; checkFaces.Clear(); json = GetFaceEncoding(mappedFaceFile); if (json is null) { if (!string.IsNullOrEmpty(eDistanceContentDirectory) && _DistanceMoveUnableToMatch) MoveUnableToMatch(eDistanceContentDirectory, mappedFaceFile, mappedFaceFileName); continue; } checkFaces.AddRange(GetMatchingFacesByFaceEncoding(faces, json)); if (checkFaces.Count == 1) _Debug.Add(0); if (checkFaces.Count != 1 && !string.IsNullOrEmpty(json)) { checkFaces.Clear(); if (json is null) throw new NotSupportedException(); checkFaces.AddRange(GetClosestFaceByDistanceIgnoringTolerance(mappingFromItem, faces, json)); } if (checkFaces.Count != 1 && _DistancePixelDistanceTolerance > 0) { checkFaces.Clear(); json = GetFaceLocation(mappedFaceFile); if (json is not null) checkFaces.AddRange(GetClosestFaceByPixel(faces, json)); else checkFaces.AddRange(GetClosestFaceByPixel(faces, normalizedPixelPercentage)); throw new NotImplementedException("Without a tolerance this should not ever occur!"); } if (!checkFaces.Any()) { if (!string.IsNullOrEmpty(eDistanceContentDirectory) && _DistanceMoveUnableToMatch) MoveUnableToMatch(eDistanceContentDirectory, mappedFaceFile, mappedFaceFileName); continue; } if (checkFaces.Count != 1) { if (!string.IsNullOrEmpty(eDistanceContentDirectory) && _DistanceMoveUnableToMatch) MoveUnableToMatch(eDistanceContentDirectory, mappedFaceFile, mappedFaceFileName); continue; } fileInfo = CheckFileThenGetFileInfo(facesFileNameExtension, mappingFromItem, mappedFaceFile, checkFaces); if (fileInfo is not null) { if (_DistanceRenameToMatch && fileInfo is not null) { if (fileInfo.Exists) File.Delete(fileInfo.FullName); File.Move(mappedFaceFile, fileInfo.FullName); _Renamed.Add(mappedFaceFile); } continue; } if (_AllMappedFaceFileNames.Contains(mappedFaceFileName)) { lock (_AllMappedFaceFiles) matches = (from l in _AllMappedFaceFiles where l != mappedFaceFile && Path.GetFileName(l) == mappedFaceFileName select l).ToArray(); if (matches.Any()) AppendMatchingDuplicates(mappedFaceFile, matches); } lock (_AllMappedFaceFiles) _AllMappedFaceFiles.Add(mappedFaceFile); lock (_AllMappedFaceFileNames) _AllMappedFaceFileNames.Add(mappedFaceFileName); } } public void Clear() { double?[] debug = (from l in _Debug where l is null or not 0 select l).ToArray(); string debugMessage = $"{_Debug.Count - debug.Length} - {debug.Min()} - {_Debug.Max()}"; if (_Moved.Any() || _Renamed.Any() || _DuplicateMappedFaceFiles.Any()) throw new NotImplementedException("Restart!"); _Debug.Clear(); _Moved.Clear(); _Renamed.Clear(); _AllMappedFaceFiles.Clear(); _AllMappedFaceFileNames.Clear(); _DuplicateMappedFaceFiles.Clear(); } }