using ShellProgressBar; using System.Text.Json; using View_by_Distance.Distance.Models.Stateless; using View_by_Distance.FaceRecognitionDotNet; using View_by_Distance.Shared.Models; namespace View_by_Distance.Distance.Models; public partial class E_Distance { private readonly List _Moved; private readonly List _Debug; private readonly List _Renamed; private readonly Serilog.ILogger? _Log; private readonly int _FaceConfidencePercent; private readonly bool _DistanceRenameToMatch; private readonly double[] _RangeFaceConfidence; private readonly bool _DistanceMoveUnableToMatch; private readonly List _AllMappedFaceFiles; private readonly double[] _RangeDistanceTolerance; private readonly List _AllMappedFaceFileNames; private readonly List _DuplicateMappedFaceFiles; public E_Distance(bool distanceMoveUnableToMatch, bool distanceRenameToMatch, int faceConfidencePercent, double[] rangeDistanceTolerance, double[] rangeFaceConfidence) { _Debug = new(); _Moved = new(); _Renamed = new(); _AllMappedFaceFiles = new(); _AllMappedFaceFileNames = new(); _DuplicateMappedFaceFiles = new(); _RangeFaceConfidence = rangeFaceConfidence; _Log = Serilog.Log.ForContext(); _DistanceRenameToMatch = distanceRenameToMatch; _FaceConfidencePercent = faceConfidencePercent; _RangeDistanceTolerance = rangeDistanceTolerance; _DistanceMoveUnableToMatch = distanceMoveUnableToMatch; } private static void MoveUnableToMatch(string file) { string checkFile = string.Concat(file, ".unk"); if (File.Exists(file) && !File.Exists(checkFile)) File.Move(file, checkFile); } private FaceDistanceContainer[] GetFaceDistanceContainers(MappingFromItem mappingFromItem, List intersectFaces) { FaceDistanceContainer[] results; int confidencePercent; int normalizedRectangle; FaceDistance faceDistance; FaceDistanceContainer faceDistanceContainer; List collection = new(); foreach (Face face in intersectFaces) { if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null) throw new NotSupportedException(); confidencePercent = Shared.Models.Stateless.Methods.ILocation.GetConfidencePercent(_FaceConfidencePercent, _RangeFaceConfidence, face.Location.Confidence); normalizedRectangle = Shared.Models.Stateless.Methods.ILocation.GetNormalizedRectangle(face.Location, Shared.Models.Stateless.ILocation.Digits, face.OutputResolution); if (face.FaceDistance?.Encoding is not null && face.FaceDistance.Encoding is FaceRecognitionDotNet.FaceEncoding faceEncoding) faceDistance = new(confidencePercent, faceEncoding, mappingFromItem.Id, mappingFromItem.IsWrongYear, mappingFromItem.MinimumDateTime, normalizedRectangle); else { faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); faceDistance = new(confidencePercent, faceEncoding, mappingFromItem.Id, mappingFromItem.IsWrongYear, mappingFromItem.MinimumDateTime, normalizedRectangle); lock (intersectFaces) face.SetFaceDistance(faceDistance); } faceDistanceContainer = new(face, faceDistance); collection.Add(faceDistanceContainer); } results = collection.ToArray(); 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; } private List<(Face Face, double? Length)> GetValues(MappingFromItem mappingFromItem, List intersectFaces, Shared.Models.FaceEncoding modelsFaceEncoding) { List<(Face Face, double? Length)> results = new(); Face face; FaceDistance faceDistanceLength; FaceRecognitionDotNet.FaceEncoding faceRecognitionDotNetFaceEncoding = FaceRecognition.LoadFaceEncoding(modelsFaceEncoding.RawEncoding); FaceDistance faceDistanceEncoding = new(faceRecognitionDotNetFaceEncoding); FaceDistanceContainer[] faceDistanceContainers = GetFaceDistanceContainers(mappingFromItem, intersectFaces); int faceDistanceContainersLength = faceDistanceContainers.Length; if (faceDistanceContainersLength != intersectFaces.Count) throw new NotSupportedException(); List faceDistanceEncodings = GetFaceDistanceEncodings(faceDistanceContainers); if (faceDistanceEncodings.Count != intersectFaces.Count) throw new NotSupportedException(); List faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding); if (faceDistanceLengths.Count != faceDistanceContainersLength) throw new NotSupportedException(); for (int i = 0; i < intersectFaces.Count; i++) { face = intersectFaces[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 intersectFaces, Shared.Models.FaceEncoding modelsFaceEncoding) { (Face, double?)[] results; List<(Face Face, double? Length)> collection = GetValues(mappingFromItem, intersectFaces, modelsFaceEncoding); results = (from l in collection orderby l.Length select l).Take(1).ToArray(); if (results.Any()) { (Face _, double? length) = results.First(); _Debug.Add(length); } return results; } private static List<(Face, double?)> GetMatchingFacesByFaceEncoding(Face[] filteredFaces, string? json) { List<(Face, double?)> results = new(); string check; foreach (Face face in filteredFaces) { 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 file, 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(file); if (mappedFaceDirectory is null) throw new NotSupportedException(); deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(mappingFromItem.Id, face.Location, Shared.Models.Stateless.ILocation.Digits, face.OutputResolution); checkFile = Path.Combine(mappedFaceDirectory, $"{deterministicHashCodeKey}{mappingFromItem.ImageFileHolder.ExtensionLowered}{facesFileNameExtension}"); if (checkFile == file) continue; result = new FileInfo(checkFile); } return result; } private void AppendMatchingDuplicates(string file, string[] matches) { string checkFile; FileInfo fileInfo = new(file); List<(long Length, string FullName)> collection = new(); if (fileInfo.Exists) collection.Add(new(fileInfo.Length, fileInfo.FullName)); lock (_DuplicateMappedFaceFiles) _DuplicateMappedFaceFiles.Add(file); 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> collection) { string? json; string fileName; string[] matches; FileInfo? fileInfo; List intersectFaces; List<(Face, double?)> checkFaces = new(); Shared.Models.FaceEncoding? modelsFaceEncoding; Face[] filteredFaces = (from l in faces where l.FaceEncoding is not null && l.Location is not null && l.OutputResolution is not null select l).ToArray(); if (filteredFaces.Length != faces.Count) checkFaces.Clear(); foreach (LocationContainer? locationContainer in collection) { if (_Renamed.Contains(locationContainer.File)) continue; fileName = Path.GetFileName(locationContainer.File); if (locationContainer.FromDistanceContent && _DuplicateMappedFaceFiles.Contains(fileName)) continue; checkFaces.Clear(); if (!locationContainer.Directories.Any()) { if (locationContainer.FromDistanceContent) throw new NullReferenceException(nameof(locationContainer.Directories)); continue; } json = Metadata.Models.Stateless.Methods.IMetadata.GetFaceEncoding(locationContainer.Directories); if (json is null) { if (_DistanceMoveUnableToMatch) MoveUnableToMatch(locationContainer.File); continue; } if (filteredFaces.Any()) checkFaces.AddRange(GetMatchingFacesByFaceEncoding(filteredFaces, json)); if (checkFaces.Count == 1) _Debug.Add(0); if (checkFaces.Count != 1 && !string.IsNullOrEmpty(json)) { checkFaces.Clear(); modelsFaceEncoding = JsonSerializer.Deserialize(json); if (modelsFaceEncoding is null) throw new NotSupportedException(); if (filteredFaces.Any()) { intersectFaces = Shared.Models.Stateless.Methods.ILocation.FilterByIntersect(filteredFaces, locationContainer.NormalizedRectangle); if (intersectFaces.Any()) checkFaces.AddRange(GetClosestFaceByDistanceIgnoringTolerance(mappingFromItem, intersectFaces, modelsFaceEncoding)); } } if (!checkFaces.Any()) { if (_DistanceMoveUnableToMatch) MoveUnableToMatch(locationContainer.File); continue; } if (checkFaces.Count != 1) { if (_DistanceMoveUnableToMatch) MoveUnableToMatch(locationContainer.File); continue; } fileInfo = CheckFileThenGetFileInfo(facesFileNameExtension, mappingFromItem, locationContainer.File, checkFaces); if (fileInfo is not null) { if (_DistanceRenameToMatch && fileInfo is not null) { if (fileInfo.Exists) File.Delete(locationContainer.File); else File.Move(locationContainer.File, fileInfo.FullName); File.WriteAllText($"{fileInfo.FullName}.old", $"{fileInfo.FullName}{Environment.NewLine}{locationContainer.File}"); _Renamed.Add(locationContainer.File); } continue; } if (_AllMappedFaceFileNames.Contains(fileName)) { lock (_AllMappedFaceFiles) matches = (from l in _AllMappedFaceFiles where l != locationContainer.File && Path.GetFileName(l) == fileName select l).ToArray(); if (locationContainer.FromDistanceContent && matches.Any()) AppendMatchingDuplicates(locationContainer.File, matches); } if (!locationContainer.FromDistanceContent) continue; lock (_AllMappedFaceFiles) _AllMappedFaceFiles.Add(locationContainer.File); lock (_AllMappedFaceFileNames) _AllMappedFaceFileNames.Add(fileName); } } public void Clear() { if (_Log is null) throw new NullReferenceException(nameof(_Log)); double?[] debug = (from l in _Debug where l is null or not 0 select l).ToArray(); if (debug.Any()) { string debugMessage = $"{_Debug.Count - debug.Length} - {debug.Min()} - {_Debug.Max()}"; _Log.Info(debugMessage); } if (_Moved.Any() || _Renamed.Any() || _DuplicateMappedFaceFiles.Any()) throw new NotImplementedException("Restart!"); _Debug.Clear(); _Moved.Clear(); _Renamed.Clear(); _AllMappedFaceFiles.Clear(); _AllMappedFaceFileNames.Clear(); _DuplicateMappedFaceFiles.Clear(); } public List GetMissingFaceDistanceContainer(int maxDegreeOfParallelism, long ticks, string dFacesCollectionDirectory, Dictionary> missingIdThenNormalizedRectangleToPersonContainers) { List results = new(); string[] files; List? faces; int confidencePercent; int normalizedRectangle; bool? isWrongYear = null; FaceDistance faceDistance; List<(int id, string json)> collection = new(); FaceDistanceContainer faceDistanceContainer; foreach (KeyValuePair> keyValuePair in missingIdThenNormalizedRectangleToPersonContainers) { files = Directory.GetFiles(dFacesCollectionDirectory, $"{keyValuePair.Key}*.json", SearchOption.TopDirectoryOnly); if (files.Length != 1) continue; collection.Add(new(keyValuePair.Key, Shared.Models.Stateless.Methods.IFace.GetJson(files[0]))); } int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism }; string message = $") {collection.Count:000} Setting missing distance containers - {totalSeconds} total second(s)"; ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; using ProgressBar progressBar = new(collection.Count, message, options); _ = Parallel.For(0, collection.Count, parallelOptions, (i, state) => { progressBar.Tick(); int id = collection[i].id; string json = collection[i].json; faces = JsonSerializer.Deserialize>(json); if (faces is null) throw new NullReferenceException(nameof(faces)); foreach (Face face in faces) { if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null) continue; confidencePercent = Shared.Models.Stateless.Methods.ILocation.GetConfidencePercent(_FaceConfidencePercent, _RangeFaceConfidence, face.Location.Confidence); normalizedRectangle = Shared.Models.Stateless.Methods.ILocation.GetNormalizedRectangle(face.Location, Shared.Models.Stateless.ILocation.Digits, face.OutputResolution); if (face.FaceDistance?.Encoding is not null && face.FaceDistance.Encoding is FaceRecognitionDotNet.FaceEncoding faceEncoding) faceDistance = new(confidencePercent, faceEncoding, id, isWrongYear, face.DateTime, normalizedRectangle); else { faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding); faceDistance = new(confidencePercent, faceEncoding, id, isWrongYear, face.DateTime, normalizedRectangle); face.SetFaceDistance(faceDistance); } faceDistanceContainer = new(face, faceDistance); lock (results) results.Add(faceDistanceContainer); } }); return results; } }