using System.Text.Json; using System.Text.RegularExpressions; using View_by_Distance.FaceRecognitionDotNet; using View_by_Distance.Metadata.Models; using View_by_Distance.Property.Models; using View_by_Distance.Resize.Models; using View_by_Distance.Shared.Models.Properties; using View_by_Distance.Shared.Models.Stateless; using WindowsShortcutFactory; namespace View_by_Distance.Instance.Models; internal class E_Distance { private readonly Serilog.ILogger? _Log; private readonly Configuration _Configuration; private readonly JsonSerializerOptions _WriteIndentedJsonSerializerOptions; internal E_Distance(Configuration configuration) { _Configuration = configuration; _Log = Serilog.Log.ForContext(); _WriteIndentedJsonSerializerOptions = new JsonSerializerOptions { WriteIndented = true }; } public override string ToString() { string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); return result; } private static List GetDistanceHolder(Item[] items, List<(string JSONDirectory, string TSVDirectory)> directories) { List results = new(); Item item; const int zero = 0; string tsvDirectory; string jsonDirectory; FaceEncoding? faceEncoding; if (items.Length != directories.Count) throw new Exception(); for (int i = 0; i < items.Length; i++) { faceEncoding = null; item = items[i]; if (item.ImageFileHolder is null || item.Property?.Id is null || !item.Faces.Any()) continue; tsvDirectory = directories[i].TSVDirectory; jsonDirectory = directories[i].JSONDirectory; foreach (IFace face in item.Faces) { if (!face.Populated) continue; faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); results.Add(new(face, faceEncoding, item.ImageFileHolder, item.Property.Id.Value, jsonDirectory, item.Faces[zero].Location, tsvDirectory)); } if (faceEncoding is null) results.Add(new(item.Faces[zero], null, item.ImageFileHolder, item.Property.Id.Value, jsonDirectory, item.Faces[zero].Location, tsvDirectory)); } return results; } private List> GetOrderedNoFaceCollection(List> faceCollections, int i, IFace face) { List> results = new() { new(face, string.Empty) }; for (int n = 0; n < faceCollections.Count; n++) { if (i == n) continue; for (int j = 0; j < faceCollections[n].Count; j++) { if (!faceCollections[n][j].Populated) continue; results.Add(new(faceCollections[n][j], string.Empty)); } } for (int r = results.Count - 1; r > _Configuration.MaxItemsInDistanceCollection; r--) results.RemoveAt(r); return results; } private void WriteNoFaceCollection(bool updateDateWhenMatches, DateTime? updateToWhenMatches, List> subFileTuples, List distanceHolders) { string json; string check; string jsonFile; const int zero = 0; DistanceHolder distanceHolder; List> tupleCollection; for (int i = 0; i < distanceHolders.Count; i++) { distanceHolder = distanceHolders[i]; if (distanceHolder.Face.Location?.NormalizedPixelPercentage is null) continue; check = Path.Combine(distanceHolder.JSONDirectory, $"{zero} - {distanceHolder.FileHolder.NameWithoutExtension}.json"); jsonFile = Path.Combine(distanceHolder.JSONDirectory, $"{distanceHolder.Id}.{zero}{distanceHolder.FileHolder.ExtensionLowered}.json"); if (File.Exists(check)) File.Move(check, jsonFile); tupleCollection = new() { new(distanceHolders[i].Face, string.Empty) }; for (int j = 0; j < distanceHolders.Count; j++) { if (j == i) continue; distanceHolder = distanceHolders[j]; tupleCollection.Add(new(distanceHolder.Face, string.Empty)); if (tupleCollection.Count > _Configuration.MaxItemsInDistanceCollection) break; } json = JsonSerializer.Serialize(tupleCollection, _WriteIndentedJsonSerializerOptions); if (Property.Models.Stateless.IPath.WriteAllText(jsonFile, json, updateDateWhenMatches, compareBeforeWrite: true, updateToWhenMatches: updateToWhenMatches)) subFileTuples.Add(new Tuple(nameof(E_Distance), DateTime.Now)); } } private static List GetFaceEncodings(List distanceHolders) { List results = new(); foreach (DistanceHolder distanceHolder in distanceHolders) { if (!distanceHolder.Face.Populated || distanceHolder.FaceEncoding is null) continue; results.Add(distanceHolder.FaceEncoding); } return results; } private void SaveDistanceResults(bool updateDateWhenMatches, DateTime? updateToWhenMatches, List> subFileTuples, List distanceHolders) { string json; string check; string jsonFile; int locationIndex; List faceDistances; DistanceHolder distanceHolder; int normalizedPixelPercentage; DistanceHolder[] sortedDistanceHolders; List> tupleCollection; List<(int Index, double Distance)> collection; distanceHolders = distanceHolders.OrderByDescending(l => l.Face.Populated).ToList(); List faceEncodings = GetFaceEncodings(distanceHolders); for (int i = 0; i < distanceHolders.Count; i++) { distanceHolder = distanceHolders[i]; collection = new(); tupleCollection = new(); distanceHolder.Sort = 0d; if (distanceHolder.Face.LocationIndex is null) locationIndex = 0; else locationIndex = distanceHolder.Face.LocationIndex.Value; if (!distanceHolder.Face.Populated || distanceHolder.Face.Location?.NormalizedPixelPercentage is null) normalizedPixelPercentage = 0; else normalizedPixelPercentage = distanceHolder.Face.Location.NormalizedPixelPercentage.Value; check = Path.Combine(distanceHolder.JSONDirectory, $"{locationIndex} - {distanceHolder.FileHolder.NameWithoutExtension}.json"); jsonFile = Path.Combine(distanceHolder.JSONDirectory, $"{distanceHolder.Id}.{normalizedPixelPercentage}{distanceHolder.FileHolder.ExtensionLowered}.json"); if (!Directory.Exists(distanceHolder.JSONDirectory)) _ = Directory.CreateDirectory(distanceHolder.JSONDirectory); if (File.Exists(check)) File.Move(check, jsonFile); if (faceEncodings.Count == 1) faceDistances = new() { 0d }; else if (distanceHolder.FaceEncoding is null) faceDistances = Enumerable.Repeat(9d, faceEncodings.Count).ToList(); else faceDistances = FaceRecognition.FaceDistances(faceEncodings, distanceHolder.FaceEncoding); if (distanceHolder.Face.Populated && distanceHolder.FaceEncoding is not null && faceDistances[i] != 0d) faceDistances[i] = 0d; for (int d = 0; d < faceDistances.Count; d++) collection.Add(new(d, faceDistances[d])); collection = collection.OrderBy(l => l.Distance).ToList(); foreach ((int index, double distance) in collection) distanceHolders[index].Sort = ((distance * _Configuration.DistanceFactor) + (distanceHolders[index].Location.Confidence * _Configuration.LocationConfidenceFactor)) / 10; sortedDistanceHolders = distanceHolders.OrderBy(l => l.Sort).ToArray(); for (int j = 0; j < sortedDistanceHolders.Length; j++) { distanceHolder = sortedDistanceHolders[j]; tupleCollection.Add(new(distanceHolders[j].Face, string.Empty)); if (tupleCollection.Count > _Configuration.MaxItemsInDistanceCollection) break; } json = JsonSerializer.Serialize(tupleCollection, _WriteIndentedJsonSerializerOptions); if (Property.Models.Stateless.IPath.WriteAllText(jsonFile, json, updateDateWhenMatches, compareBeforeWrite: true, updateToWhenMatches: updateToWhenMatches)) subFileTuples.Add(new Tuple(nameof(E_Distance), DateTime.Now)); } } internal void LoadOrCreateThenSaveDistanceResults(Property.Models.Configuration configuration, string eResultsFullGroupDirectory, string sourceDirectory, string outputResolution, List> sourceDirectoryChanges, Item[] filteredItems) { Item item; string json; bool check = false; string parentCheck; bool hasPopulatedFace; string usingRelativePath; DateTime? dateTime = null; string dCollectionDirectory; bool updateDateWhenMatches = false; System.IO.DirectoryInfo directoryInfo; System.IO.DirectoryInfo tvsDirectoryInfo; IEnumerator fileInfoCollection; List<(string, string)> directories = new(); string[] changesFrom = new string[] { nameof(A_Property), nameof(B_Metadata), nameof(C_Resize), nameof(D_Face) }; List dateTimes = (from l in sourceDirectoryChanges where changesFrom.Contains(l.Item1) select l.Item2).ToList(); List directoryInfoCollection = Property.Models.Stateless.IResult.GetDirectoryInfoCollection( configuration, sourceDirectory, eResultsFullGroupDirectory, contentDescription: ".tvs File", singletonDescription: string.Empty, collectionDescription: "n json file(s) for each face found (one to many)", converted: true); for (int i = 0; i < filteredItems.Length; i++) { item = filteredItems[i]; if (item.ImageFileHolder is null || item.Property?.Id is null) continue; hasPopulatedFace = (from l in item.Faces where l.Populated select true).Any(); usingRelativePath = Path.Combine(directoryInfoCollection[0].Replace("<>", "[]"), item.ImageFileHolder.NameWithoutExtension); dCollectionDirectory = Path.Combine(eResultsFullGroupDirectory, "[]", Property.Models.Stateless.IResult.AllInOne, $"{item.Property.Id.Value}{item.ImageFileHolder.ExtensionLowered}"); directoryInfo = new System.IO.DirectoryInfo(dCollectionDirectory); if (!directoryInfo.Exists) { if (Directory.Exists(usingRelativePath)) { Directory.Move(usingRelativePath, directoryInfo.FullName); directoryInfo.Refresh(); } if (!Directory.Exists(dCollectionDirectory)) { if (directoryInfo.Parent?.Parent is null) throw new Exception(); parentCheck = Path.Combine(directoryInfo.Parent.Parent.FullName, directoryInfo.Name); if (Directory.Exists(parentCheck)) { foreach (string file in Directory.GetFiles(parentCheck)) File.Delete(file); Directory.Delete(parentCheck); } } } tvsDirectoryInfo = new System.IO.DirectoryInfo(Path.Combine(directoryInfoCollection[0].Replace("<>", "()"), item.ImageFileHolder.NameWithoutExtension)); directories.Add(new(directoryInfo.FullName, tvsDirectoryInfo.FullName)); if (_Configuration.CheckJsonForDistanceResults && directoryInfo.Exists) { json = string.Empty; fileInfoCollection = directoryInfo.EnumerateFiles("*.json", SearchOption.AllDirectories).GetEnumerator(); for (int j = 0; j < int.MaxValue; j++) { if (!fileInfoCollection.MoveNext()) break; json = Shared.Models.Stateless.Methods.IIndex.GetJson(fileInfoCollection.Current.FullName, fileInfoCollection.Current); if (!_Configuration.PropertiesChangedForDistance && Shared.Models.Stateless.Methods.IFace.GetFace(fileInfoCollection.Current.FullName) is null) { check = true; break; } } if (!check && string.IsNullOrEmpty(json)) check = true; } if (check) continue; if (_Configuration.PropertiesChangedForDistance) check = true; else if (hasPopulatedFace && !directoryInfo.Exists) check = true; else if (dateTimes.Any() && dateTimes.Max() > directoryInfo.LastWriteTime) check = true; if (check && !updateDateWhenMatches) { updateDateWhenMatches = dateTimes.Any() && directoryInfo.Exists && dateTimes.Max() > directoryInfo.LastWriteTime; dateTime = !updateDateWhenMatches ? null : dateTimes.Max(); } } if (check) { DateTime? updateToWhenMatches = dateTime; List> subFileTuples = new(); List distanceHolders = GetDistanceHolder(filteredItems, directories); SaveDistanceResults(updateDateWhenMatches, updateToWhenMatches, subFileTuples, distanceHolders); } _ = Property.Models.Stateless.IPath.DeleteEmptyDirectories(directoryInfoCollection[0].Replace("<>", "()")); } private List<(string, List>)> GetFiles(Property.Models.Configuration configuration, Model? model, PredictorModel? predictorModel, string outputResolution) { string json; List>? facesKeyValuePairCollection; List<(string, List>)> results = new(); string dFacesCollectionDirectory = Path.Combine(Property.Models.Stateless.IResult.GetResultsFullGroupDirectory(configuration, model, predictorModel, nameof(D_Face), outputResolution, includeResizeGroup: true, includeModel: true, includePredictorModel: true), "[[]]"); string[] dFacesCollectionFiles = Directory.GetFiles(dFacesCollectionDirectory, "*.json", SearchOption.TopDirectoryOnly); foreach (string dFacesCollectionFile in dFacesCollectionFiles) { json = File.ReadAllText(dFacesCollectionFile); facesKeyValuePairCollection = JsonSerializer.Deserialize>>(json); if (facesKeyValuePairCollection is null) continue; results.Add(new(dFacesCollectionFile, facesKeyValuePairCollection)); } return results; } private static List<(string, List, List)> GetMatches(List<(string, List>)> files) { List<(string, List, List)> results = new(); FaceEncoding faceEncoding; List faces; List faceEncodings; foreach ((string, List>) file in files) { faces = new(); faceEncodings = new(); foreach (KeyValuePair keyValuePair in file.Item2) { foreach (Shared.Models.Face face in keyValuePair.Value) { if (!face.Populated) continue; faces.Add(face); faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); faceEncodings.Add(faceEncoding); } } results.Add(new(file.Item1, faces, faceEncodings)); } return results; } private static int GetIndex(double[] faceDistances) { int result; List faceDistancesWithIndex = new(); for (int y = 0; y < faceDistances.Length; y++) faceDistancesWithIndex.Add(new double[] { faceDistances[y], y }); faceDistancesWithIndex = (from l in faceDistancesWithIndex orderby l[0] select l).ToList(); result = (int)faceDistancesWithIndex[0][1]; return result; } private void Save(Property.Models.Configuration configuration, Model? model, PredictorModel? predictorModel, string outputResolution, string eDistanceCollectionDirectory, int k, string relativePath, Shared.Models.Face face, List> faceAndFaceDistanceCollection) { if (string.IsNullOrEmpty(eDistanceCollectionDirectory)) eDistanceCollectionDirectory = Path.Combine(Property.Models.Stateless.IResult.GetResultsFullGroupDirectory(configuration, model, predictorModel, nameof(E_Distance), outputResolution, includeResizeGroup: true, includeModel: true, includePredictorModel: true), "[]"); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(face.RelativePath); string jsonDirectory = string.Concat(eDistanceCollectionDirectory, Path.Combine(relativePath, fileNameWithoutExtension)); if (!Directory.Exists(jsonDirectory)) _ = Directory.CreateDirectory(jsonDirectory); string json = JsonSerializer.Serialize(faceAndFaceDistanceCollection, _WriteIndentedJsonSerializerOptions); string jsonFile = Path.Combine(jsonDirectory, $"{k} - {fileNameWithoutExtension}.nosj"); _ = Property.Models.Stateless.IPath.WriteAllText(jsonFile, json, updateDateWhenMatches: true, compareBeforeWrite: true); } private static Tuple Get(FaceEncoding faceEncoding, (string, List, List) match) { Tuple result; double[] faceDistances = FaceRecognition.FaceDistances(match.Item3, faceEncoding).ToArray(); int index = GetIndex(faceDistances); result = new(match.Item2[index], faceDistances[index]); return result; } internal void LoadOrCreateThenSaveDirectoryDistanceResultsForOutputResolutions(Property.Models.Configuration configuration, Model? model, PredictorModel? predictorModel, string outputResolution) { if (_Log is null) throw new NullReferenceException(nameof(_Log)); string? relativePath; Shared.Models.Face face; ParallelOptions parallelOptions = new(); FaceEncoding faceEncoding; string eDistanceCollectionDirectory = string.Empty; Tuple faceAndFaceDistance; List> faceAndFaceDistanceCollection; List<(string, List>)> files = GetFiles(configuration, model, predictorModel, outputResolution); List<(string, List, List)> matches = GetMatches(files); if (files.Count != matches.Count) throw new Exception(); int filesCount = files.Count; for (int i = 0; i < filesCount; i++) { _Log.Debug(string.Concat("LoadOrCreateThenSaveDirectoryDistanceResultsForOutputResolutions - ", nameof(outputResolution), ' ', outputResolution, " - ", i, " of ", filesCount)); for (int j = 0; j < files[i].Item2.Count; j++) { if (!matches[i].Item2.Any()) continue; for (int k = 0; k < files[i].Item2[j].Value.Length; k++) { if (!files[i].Item2[j].Value[k].Populated) continue; face = files[i].Item2[j].Value[k]; faceAndFaceDistanceCollection = new(matches.Count); relativePath = Path.GetDirectoryName(face.RelativePath); if (string.IsNullOrEmpty(relativePath)) continue; faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); _ = Parallel.For(0, matches.Count, parallelOptions, z => { if (z != i && matches[z].Item2.Any()) { faceAndFaceDistance = Get(faceEncoding, matches[z]); // if (faceAndFaceDistance.Item2 < _Configuration.) faceAndFaceDistanceCollection.Add(new(faceAndFaceDistance.Item1, faceAndFaceDistance.Item2.ToString("0.000"))); } }); if (faceAndFaceDistanceCollection.Any()) { faceAndFaceDistanceCollection = (from l in faceAndFaceDistanceCollection orderby l.Item2 select l).Take(_Configuration.CrossDirectoryMaxItemsInDistanceCollection).ToList(); Save(configuration, model, predictorModel, outputResolution, eDistanceCollectionDirectory, k, relativePath, face, faceAndFaceDistanceCollection); } } } } } public static double GetStandardDeviation(IEnumerable values, double average) { double result = 0; if (!values.Any()) throw new Exception("Collection must have at least one value!"); double sum = values.Sum(l => (l - average) * (l - average)); result = Math.Sqrt(sum / values.Count()); return result; } private static FaceEncoding? GetFaceEncoding(IFace face) { FaceEncoding? result; if (!face.Populated) result = null; else result = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); return result; } private static (int index, double sum) GetIndexAndSum(int i, List results) { List faceDistances = FaceRecognition.FaceDistances(results, results[i]); return new(i, faceDistances.Sum()); } private static List GetFaceEncodings(int maxDegreeOfParallelism, List<(DateTime MinimumDateTime, bool? IsWrongYear, Shared.Models.PersonBirthday PersonBirthday, IFace Face)> collection) { List results; if (maxDegreeOfParallelism == 1) { results = new(); FaceEncoding faceEncoding; foreach ((DateTime _, bool? _, Shared.Models.PersonBirthday _, IFace face) in collection) { if (!face.Populated) continue; faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); results.Add(faceEncoding); } } else { results = new(); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; _ = Parallel.For(0, collection.Count, parallelOptions, i => { FaceEncoding? faceEncoding = GetFaceEncoding(collection[i].Face); if (faceEncoding is not null) { lock (results) results.Add(faceEncoding); } }); } if (collection.Count == results.Count && results.Count > 1) { double sum; int lowestIndex; double lowestSum; List faceDistances; if (maxDegreeOfParallelism == 1) { lowestIndex = 0; lowestSum = double.MaxValue; for (int i = 0; i < results.Count; i++) { faceDistances = FaceRecognition.FaceDistances(results, results[i]); sum = faceDistances.Sum(); if (sum >= lowestSum) continue; lowestIndex = i; lowestSum = sum; } } else { List<(int Index, double Sum)> indicesAndSums = new(); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; _ = Parallel.For(0, results.Count, parallelOptions, i => { (int index, double sum) = GetIndexAndSum(i, results); lock (indicesAndSums) indicesAndSums.Add(new(index, sum)); }); (lowestIndex, lowestSum) = (from l in indicesAndSums orderby l.Sum select l).First(); } faceDistances = FaceRecognition.FaceDistances(results, results[lowestIndex]); sum = faceDistances.Sum(); if (sum == lowestSum) { double average = faceDistances.Average(); double standardDeviation = GetStandardDeviation(faceDistances, average); double lcl = average - (standardDeviation * 3); double ucl = average + (standardDeviation * 3); for (int i = results.Count - 1; i > -1; i--) { if (faceDistances[i] < lcl || faceDistances[i] > ucl) results.RemoveAt(i); } } } return results; } private static List<(DateTime, bool?, Shared.Models.PersonBirthday, FaceEncoding[])> GetThreeSigmaFaceEncodings(int maxDegreeOfParallelism, Dictionary> keyValuePairs) { List<(DateTime, bool?, Shared.Models.PersonBirthday, FaceEncoding[])> results = new(); const int zero = 0; List faceEncodings; foreach (KeyValuePair> keyValuePair in keyValuePairs) { faceEncodings = GetFaceEncodings(maxDegreeOfParallelism, keyValuePair.Value); results.Add(new(keyValuePair.Value[zero].MinimumDateTime, keyValuePair.Value[zero].IsWrongYear, keyValuePair.Value[zero].PersonBirthday, faceEncodings.ToArray())); } return results; } private static Closest? GetClosestParallelFor(DateTime minimumDateTime, bool? isWrongYear, IFace face, FaceEncoding faceEncoding, (DateTime MinimumDateTime, bool? IsWrongYear, Shared.Models.PersonBirthday PersonBirthday, FaceEncoding[] FaceEncodings) tuple) { Closest? result; if (isWrongYear.HasValue && !isWrongYear.Value && minimumDateTime < tuple.PersonBirthday.Value) result = null; else { List faceDistances = FaceRecognition.FaceDistances(tuple.FaceEncodings, faceEncoding); result = new(face.Location?.NormalizedPixelPercentage, tuple.MinimumDateTime, tuple.IsWrongYear, tuple.PersonBirthday, faceDistances); if (result.Minimum > Closest.MaximumMinimum) result = null; } return result; } private static Closest[] GetClosestCollection(int maxDegreeOfParallelism, List<(DateTime, bool?, Shared.Models.PersonBirthday, FaceEncoding[])> collection, DateTime itemMinimumDateTime, bool? itemIsWrongYear, IFace face) { Closest[] results; List closestCollection; FaceEncoding faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); if (maxDegreeOfParallelism == 1) { closestCollection = new(); Closest closest; List faceDistances; foreach ((DateTime minimumDateTime, bool? isWrongYear, Shared.Models.PersonBirthday personBirthday, FaceEncoding[] faceEncodings) in collection) { if (itemIsWrongYear.HasValue && !itemIsWrongYear.Value && itemMinimumDateTime < personBirthday.Value) continue; faceDistances = FaceRecognition.FaceDistances(faceEncodings, faceEncoding); closest = new(face.Location?.NormalizedPixelPercentage, minimumDateTime, isWrongYear, personBirthday, faceDistances); if (closest.Minimum > Closest.MaximumMinimum) continue; closestCollection.Add(closest); } } else { closestCollection = new(); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; _ = Parallel.For(0, collection.Count, parallelOptions, i => { Closest? closest = GetClosestParallelFor(itemMinimumDateTime, itemIsWrongYear, face, faceEncoding, collection[i]); if (closest is not null) { lock (closestCollection) closestCollection.Add(closest); } }); } results = Closest.Get(closestCollection); return results; } private static void AddClosest(int maxDegreeOfParallelism, string argZero, PropertyLogic propertyLogic, List containers, List<(DateTime, bool?, Shared.Models.PersonBirthday, FaceEncoding[])> collection) { string key; IFace face; Closest closest; string personKey; bool? itemIsWrongYear; Closest[] closestCollection; DateTime? itemMinimumDateTime; double deterministicHashCodeKey; Dictionary results = new(); foreach (Container container in containers) { if (!container.Items.Any()) continue; if (!container.SourceDirectory.StartsWith(argZero)) continue; foreach (Item item in container.Items) { if (item.ImageFileHolder is null || item.Property is null || item.Named.Any()) continue; itemMinimumDateTime = Property.Models.Stateless.A_Property.GetMinimumDateTime(item.Property); if (itemMinimumDateTime is null) continue; (itemIsWrongYear, _) = item.IsWrongYear(); if (Closest.SkipIsWrongYear && itemIsWrongYear.HasValue && itemIsWrongYear.Value) continue; item.Closest.Clear(); for (int i = 0; i < item.Faces.Count; i++) { face = item.Faces[i]; closest = new(face.Location?.NormalizedPixelPercentage, itemMinimumDateTime.Value, itemIsWrongYear); item.Closest.Add(closest); if (!face.Populated) continue; deterministicHashCodeKey = Named.GetDeterministicHashCodeKey(item, face); closestCollection = GetClosestCollection(maxDegreeOfParallelism, collection, itemMinimumDateTime.Value, itemIsWrongYear, face); for (int j = 0; j < closestCollection.Length; j++) { closest = closestCollection[j]; if (closest.PersonBirthday is null) continue; personKey = Shared.Models.Stateless.Methods.IPersonBirthday.GetFormatted(closest.PersonBirthday); if (propertyLogic.IncorrectDeterministicHashCodeKeyValuePairs.ContainsKey(deterministicHashCodeKey) && propertyLogic.IncorrectDeterministicHashCodeKeyValuePairs[deterministicHashCodeKey].Contains(personKey)) continue; key = Item.GetKey(closest.MinimumDateTime, closest.IsWrongYear, closest.PersonBirthday); if (!results.ContainsKey(key)) results.Add(key, 0); else if (results[key] > Closest.MaximumPer) continue; results[key] += 1; item.Closest[0] = closest; break; } } } } } internal static List<(DateTime, bool?, Shared.Models.PersonBirthday, FaceEncoding[])> ParallelWork(int maxDegreeOfParallelism, string argZero, PropertyLogic propertyLogic, List containers) { List<(DateTime, bool?, Shared.Models.PersonBirthday, FaceEncoding[])> results; Dictionary> keyValuePairs = Item.GetKeyValuePairs(argZero, containers); results = GetThreeSigmaFaceEncodings(maxDegreeOfParallelism, keyValuePairs); AddClosest(maxDegreeOfParallelism, argZero, propertyLogic, containers, results); return results; } public static void SavePropertyHolders(string argZero, List containers, string zPropertyHolderSingletonDirectory) { string json; FileInfo fileInfo; bool updateDateWhenMatches = false; JsonSerializerOptions jsonSerializerOptions = new() { WriteIndented = true }; foreach (Container container in containers) { if (!container.Items.Any()) continue; if (!container.SourceDirectory.StartsWith(argZero)) continue; foreach (Item item in container.Items) { if (item.ImageFileHolder is null || item.Property is null || !item.Faces.Any() || !item.Closest.Any()) continue; if (!(from l in item.Closest where l.Average.HasValue select true).Any()) continue; json = JsonSerializer.Serialize(item, jsonSerializerOptions); fileInfo = new(string.Concat(zPropertyHolderSingletonDirectory, item.RelativePath, ".json")); if (fileInfo.Directory is null) continue; if (!fileInfo.Directory.Exists) fileInfo.Directory.Create(); _ = Property.Models.Stateless.IPath.WriteAllText(fileInfo.FullName, json, updateDateWhenMatches, compareBeforeWrite: true); } } } internal static void SaveThreeSigmaFaceEncodings(List<(DateTime, bool?, Shared.Models.PersonBirthday, FaceEncoding[])> collection, Dictionary> peopleCollection, string eDistanceCollectionDirectory) { string json; string checkFile; string personKey; string directory; List rawEncodings; Shared.Models.Person person; const string facePopulatedKey = "ThreeSigma"; const string pattern = @"[\\,\/,\:,\*,\?,\"",\<,\>,\|]"; foreach ((DateTime minimumDateTime, bool? isWrongYear, Shared.Models.PersonBirthday personBirthday, FaceEncoding[] faceEncodings) in collection) { rawEncodings = new(); checkFile = string.Empty; personKey = Shared.Models.Stateless.Methods.IPersonBirthday.GetFormatted(personBirthday); directory = Item.GetDirectory(eDistanceCollectionDirectory, facePopulatedKey, minimumDateTime, isWrongYear, personBirthday, personKey); if (!peopleCollection.ContainsKey(personKey)) continue; person = peopleCollection[personKey][0]; checkFile = string.Concat(directory, " - ", Regex.Replace(Shared.Models.Stateless.Methods.IPersonName.GetFullName(person.Name), pattern, string.Empty), ".json"); if (string.IsNullOrEmpty(checkFile)) continue; if (!Directory.Exists(directory)) _ = Directory.CreateDirectory(directory); for (int i = 0; i < faceEncodings.Length; i++) rawEncodings.Add(faceEncodings[i].GetRawEncoding()); json = JsonSerializer.Serialize(rawEncodings, new JsonSerializerOptions { WriteIndented = true }); _ = Property.Models.Stateless.IPath.WriteAllText(checkFile, json, updateDateWhenMatches: true, compareBeforeWrite: true); } } internal static List<(FileHolder? resizedFileHolder, string directory, FileInfo? faceFileInfo, string checkFile, string shortcutFile)> GetClosest(string argZero, List containers, Dictionary> peopleCollection, string eDistanceContentDirectory, string dFacesContentDirectory) { List<(FileHolder?, string, FileInfo?, string, string)> results = new(); string checkFile; string directory; string personKey; string personName; string shortcutFile; FileInfo faceFileInfo; string? directoryName; string facesDirectory; string personDirectory; Shared.Models.Person person; double deterministicHashCodeKey; const string facePopulatedKey = "Closest"; const string pattern = @"[\\,\/,\:,\*,\?,\"",\<,\>,\|]"; foreach (Container container in containers) { if (!container.Items.Any()) continue; if (!container.SourceDirectory.StartsWith(argZero)) continue; foreach (Item item in container.Items) { if (item.ImageFileHolder is null || item.Property?.Id is null || item.ResizedFileHolder is null || item.Named.Any()) continue; if (!item.Closest.Any()) continue; directoryName = Path.GetDirectoryName(item.RelativePath); if (directoryName is null) throw new Exception(); foreach (Closest closest in item.Closest) { if (closest.Average is null || closest.NormalizedPixelPercentage is null || closest.PersonBirthday is null) continue; personKey = Shared.Models.Stateless.Methods.IPersonBirthday.GetFormatted(closest.PersonBirthday); directory = Item.GetDirectory(eDistanceContentDirectory, facePopulatedKey, closest.MinimumDateTime, closest.IsWrongYear, closest.PersonBirthday, personKey); if (!peopleCollection.ContainsKey(personKey)) personDirectory = string.Empty; else { person = peopleCollection[personKey][0]; personName = Shared.Models.Stateless.Methods.IPersonName.GetFullName(person.Name); personDirectory = Path.Combine(directory, Regex.Replace(personName, pattern, string.Empty), "lnk"); results.Add(new(null, personDirectory, null, string.Empty, string.Empty)); } facesDirectory = string.Concat(dFacesContentDirectory, Path.Combine(directoryName, item.ImageFileHolder.NameWithoutExtension)); deterministicHashCodeKey = Named.GetDeterministicHashCodeKey(item, closest); checkFile = Path.Combine(directory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}"); faceFileInfo = new(Path.Combine(facesDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.png")); if (string.IsNullOrEmpty(personDirectory)) shortcutFile = string.Empty; else shortcutFile = Path.Combine(personDirectory, $"{deterministicHashCodeKey}{item.ImageFileHolder.ExtensionLowered}.lnk"); results.Add(new(item.ResizedFileHolder, directory, faceFileInfo, checkFile, shortcutFile)); } } } return results; } internal static void SaveClosest(string argZero, List containers, Dictionary> peopleCollection, string eDistanceContentDirectory, string dFacesContentDirectory) { WindowsShortcut windowsShortcut; List<(FileHolder? resizedFileHolder, string directory, FileInfo? faceFileInfo, string checkFile, string shortcutFile)> collection = GetClosest(argZero, containers, peopleCollection, eDistanceContentDirectory, dFacesContentDirectory); string[] directories = (from l in collection select l.directory).Distinct().ToArray(); foreach (string directory in directories) { if (string.IsNullOrEmpty(directory)) continue; if (!Directory.Exists(directory)) _ = Directory.CreateDirectory(directory); } foreach ((FileHolder? resizedFileHolder, string directory, FileInfo? faceFileInfo, string checkFile, string shortcutFile) in collection) { if (string.IsNullOrEmpty(directory) || string.IsNullOrEmpty(checkFile) || resizedFileHolder is null || faceFileInfo is null) continue; if (File.Exists(checkFile)) continue; if (faceFileInfo.Directory is not null && faceFileInfo.Directory.Exists && faceFileInfo.Exists) File.Copy(faceFileInfo.FullName, checkFile); else File.Copy(resizedFileHolder.FullName, checkFile); } foreach ((FileHolder? resizedFileHolder, string directory, FileInfo? _, string checkFile, string shortcutFile) in collection) { if (string.IsNullOrEmpty(directory) || string.IsNullOrEmpty(checkFile) || resizedFileHolder is null) continue; if (string.IsNullOrEmpty(shortcutFile) || !resizedFileHolder.Exists) continue; try { windowsShortcut = new() { Path = resizedFileHolder.FullName }; windowsShortcut.Save(shortcutFile); windowsShortcut.Dispose(); } catch (Exception) { } } } }