Changed GetDimensions to handle a stream at the end and one exit Switched to using Action? over IDlibDotNet for Tick method Switched to using AsReadOnly over new() Moved Meta Base to Shared
700 lines
37 KiB
C#
700 lines
37 KiB
C#
using ShellProgressBar;
|
|
using System.Collections.ObjectModel;
|
|
using System.Data;
|
|
using System.Text.Json;
|
|
using View_by_Distance.FaceRecognitionDotNet;
|
|
using View_by_Distance.Map.Models;
|
|
using View_by_Distance.Property.Models.Stateless;
|
|
using View_by_Distance.Shared.Models;
|
|
using View_by_Distance.Shared.Models.Methods;
|
|
using View_by_Distance.Shared.Models.Stateless.Methods;
|
|
|
|
namespace View_by_Distance.Distance.Models;
|
|
|
|
public partial class E_Distance : IDistance
|
|
{
|
|
|
|
internal record Record(FilePath FilePath, FaceRecognitionDotNet.FaceEncoding FaceRecognitionDotNetFaceEncoding);
|
|
|
|
private readonly List<string> _Moved;
|
|
private readonly List<double?> _Debug;
|
|
private readonly List<string> _Renamed;
|
|
private readonly int _FaceConfidencePercent;
|
|
private readonly bool _DistanceRenameToMatch;
|
|
private readonly bool _DistanceMoveUnableToMatch;
|
|
private readonly float _RectangleIntersectMinimum;
|
|
private readonly List<string> _AllMappedFaceFiles;
|
|
private readonly List<string> _AllMappedFaceFileNames;
|
|
private readonly double _RangeDistanceToleranceAverage;
|
|
private readonly List<string> _DuplicateMappedFaceFiles;
|
|
|
|
public E_Distance(bool distanceMoveUnableToMatch, bool distanceRenameToMatch, int faceConfidencePercent, float[] rangeDistanceTolerance, float[] rectangleIntersectMinimums)
|
|
{
|
|
_Debug = [];
|
|
_Moved = [];
|
|
_Renamed = [];
|
|
_AllMappedFaceFiles = [];
|
|
_AllMappedFaceFileNames = [];
|
|
_DuplicateMappedFaceFiles = [];
|
|
_DistanceRenameToMatch = distanceRenameToMatch;
|
|
_FaceConfidencePercent = faceConfidencePercent;
|
|
_DistanceMoveUnableToMatch = distanceMoveUnableToMatch;
|
|
_RectangleIntersectMinimum = rectangleIntersectMinimums.Max();
|
|
_RangeDistanceToleranceAverage = rangeDistanceTolerance.Average();
|
|
}
|
|
|
|
private static void MoveUnableToMatch(FilePath filePath)
|
|
{
|
|
string checkFile = $"{filePath.FullName}.unk";
|
|
if (File.Exists(filePath.FullName) && !File.Exists(checkFile))
|
|
File.Move(filePath.FullName, checkFile);
|
|
}
|
|
|
|
private FaceDistanceContainer[] GetFaceDistanceContainers(MappingFromItem mappingFromItem, List<Face> intersectFaces)
|
|
{
|
|
FaceDistanceContainer[] results;
|
|
DateTime dateTime;
|
|
int wholePercentages;
|
|
int confidencePercent;
|
|
FaceDistance faceDistance;
|
|
FaceDistanceContainer faceDistanceContainer;
|
|
List<FaceDistanceContainer> collection = [];
|
|
foreach (Face face in intersectFaces)
|
|
{
|
|
if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null)
|
|
throw new NotSupportedException();
|
|
if (face.Mapping?.MappingFromFilterPost is null)
|
|
throw new NotSupportedException();
|
|
dateTime = mappingFromItem.GetDateTimeOriginalThenMinimumDateTime();
|
|
confidencePercent = Shared.Models.Stateless.Methods.ILocation.GetConfidencePercent(_FaceConfidencePercent, face.Location.Confidence);
|
|
wholePercentages = Shared.Models.Stateless.Methods.ILocation.GetWholePercentages(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, dateTime, faceEncoding, face.Mapping?.MappingFromFilterPost, mappingFromItem.Id, mappingFromItem.IsWrongYear, wholePercentages);
|
|
else
|
|
{
|
|
faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding);
|
|
faceDistance = new(confidencePercent, dateTime, faceEncoding, face.Mapping?.MappingFromFilterPost, mappingFromItem.Id, mappingFromItem.IsWrongYear, wholePercentages);
|
|
lock (intersectFaces)
|
|
face.SetFaceDistance(faceDistance);
|
|
}
|
|
faceDistanceContainer = new(face, faceDistance);
|
|
collection.Add(faceDistanceContainer);
|
|
}
|
|
results = collection.ToArray();
|
|
return results;
|
|
}
|
|
|
|
private static ReadOnlyCollection<FaceDistance> GetFaceDistanceEncodings(FaceDistanceContainer[] faceDistanceContainers)
|
|
{
|
|
List<FaceDistance> faceDistanceEncodings = [];
|
|
foreach (FaceDistanceContainer faceDistanceContainer in faceDistanceContainers)
|
|
{
|
|
if (faceDistanceContainer.FaceDistance.Encoding is null)
|
|
continue;
|
|
faceDistanceEncodings.Add(faceDistanceContainer.FaceDistance);
|
|
}
|
|
return new(faceDistanceEncodings);
|
|
}
|
|
|
|
private List<(Face Face, double? Length)> GetValues(IDistanceLimits distanceLimits, MappingFromItem mappingFromItem, List<Face> intersectFaces, Shared.Models.FaceEncoding modelsFaceEncoding)
|
|
{
|
|
List<(Face Face, double? Length)> results = [];
|
|
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();
|
|
ReadOnlyCollection<FaceDistance> faceDistanceEncodings = GetFaceDistanceEncodings(faceDistanceContainers);
|
|
if (faceDistanceEncodings.Count != intersectFaces.Count)
|
|
throw new NotSupportedException();
|
|
List<FaceDistance> 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 || faceDistanceLength.Length > distanceLimits.RangeDistanceToleranceUpperLimit)
|
|
continue;
|
|
if (faceDistanceLength.Length is null)
|
|
throw new NotSupportedException();
|
|
results.Add(new(face, faceDistanceLength.Length.Value));
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private (Face, double?)[] GetClosestFaceByDistanceIgnoringTolerance(IDistanceLimits distanceLimits, MappingFromItem mappingFromItem, List<Face> intersectFaces, Shared.Models.FaceEncoding modelsFaceEncoding)
|
|
{
|
|
(Face, double?)[] results;
|
|
List<(Face Face, double? Length)> collection = GetValues(distanceLimits, mappingFromItem, intersectFaces, modelsFaceEncoding);
|
|
results = (from l in collection where l.Length < _RangeDistanceToleranceAverage orderby l.Length select l).Take(1).ToArray();
|
|
if (results.Length > 0)
|
|
{
|
|
(Face _, double? length) = results.First();
|
|
_Debug.Add(length);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private static List<(Face, double?)> GetMatchingFacesByFaceEncoding(List<Face> faces, string? json)
|
|
{
|
|
List<(Face, double?)> results = [];
|
|
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, FilePath filePath, 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 = IMapping.GetDeterministicHashCodeKey(filePath, face.Location, Shared.Models.Stateless.ILocation.Digits, face.OutputResolution);
|
|
checkFile = Path.Combine(mappedFaceDirectory, $"{deterministicHashCodeKey}{mappingFromItem.FilePath.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 = [];
|
|
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 = $"{collection[i].FullName}.dup";
|
|
if (File.Exists(checkFile))
|
|
continue;
|
|
File.Move(collection[i].FullName, checkFile);
|
|
}
|
|
}
|
|
|
|
public void LookForMatchFacesAndPossiblyRename(bool overrideForFaceImages, IDistanceLimits distanceLimits, IFaceD dFace, FilePath filePath, MappingFromItem mappingFromItem, ExifDirectory exifDirectory, List<Face> faces, ReadOnlyCollection<LocationContainer> locationContainers)
|
|
{
|
|
string? json;
|
|
string[] matches;
|
|
FileInfo? fileInfo;
|
|
List<Face> intersectFaces;
|
|
Shared.Models.FaceEncoding? modelsFaceEncoding;
|
|
List<(Face Face, double? Distance)> checkFaces = [];
|
|
foreach (LocationContainer locationContainer in locationContainers)
|
|
{
|
|
if (_Renamed.Contains(locationContainer.FilePath.FullName))
|
|
continue;
|
|
if (locationContainer.FromDistanceContent && _DuplicateMappedFaceFiles.Contains(locationContainer.FilePath.Name))
|
|
continue;
|
|
checkFaces.Clear();
|
|
if (locationContainer.ExifDirectory is null)
|
|
{
|
|
if (locationContainer.FromDistanceContent)
|
|
throw new NullReferenceException(nameof(locationContainer.ExifDirectory));
|
|
continue;
|
|
}
|
|
json = Metadata.Models.Stateless.Methods.IMetadata.GetFaceEncoding(locationContainer.ExifDirectory);
|
|
if (json is null)
|
|
{
|
|
if (_DistanceMoveUnableToMatch)
|
|
MoveUnableToMatch(locationContainer.FilePath);
|
|
continue;
|
|
}
|
|
if (faces.Count > 0)
|
|
checkFaces.AddRange(GetMatchingFacesByFaceEncoding(faces, json));
|
|
if (checkFaces.Count == 1)
|
|
_Debug.Add(0);
|
|
if (checkFaces.Count != 1 && !string.IsNullOrEmpty(json))
|
|
{
|
|
checkFaces.Clear();
|
|
modelsFaceEncoding = JsonSerializer.Deserialize<Shared.Models.FaceEncoding>(json);
|
|
if (modelsFaceEncoding is null)
|
|
throw new NotSupportedException();
|
|
if (faces.Count > 0)
|
|
{
|
|
intersectFaces = Shared.Models.Stateless.Methods.ILocation.FilterByIntersect(faces, _RectangleIntersectMinimum, locationContainer.WholePercentages);
|
|
if (intersectFaces.Count > 0)
|
|
checkFaces.AddRange(GetClosestFaceByDistanceIgnoringTolerance(distanceLimits, mappingFromItem, intersectFaces, modelsFaceEncoding));
|
|
}
|
|
}
|
|
if (checkFaces.Count == 0)
|
|
{
|
|
if (_DistanceMoveUnableToMatch)
|
|
MoveUnableToMatch(locationContainer.FilePath);
|
|
continue;
|
|
}
|
|
if (checkFaces.Count != 1)
|
|
{
|
|
if (_DistanceMoveUnableToMatch)
|
|
MoveUnableToMatch(locationContainer.FilePath);
|
|
continue;
|
|
}
|
|
fileInfo = CheckFileThenGetFileInfo(dFace.FileNameExtension, filePath, mappingFromItem, locationContainer.FilePath.FullName, checkFaces);
|
|
if (fileInfo is not null)
|
|
{
|
|
if (_DistanceRenameToMatch && fileInfo is not null)
|
|
{
|
|
if (fileInfo.Exists)
|
|
File.Delete(locationContainer.FilePath.FullName);
|
|
else
|
|
File.Move(locationContainer.FilePath.FullName, fileInfo.FullName);
|
|
File.WriteAllText($"{fileInfo.FullName}.old", $"{fileInfo.FullName}{Environment.NewLine}{locationContainer.FilePath.FullName}");
|
|
_Renamed.Add(locationContainer.FilePath.FullName);
|
|
}
|
|
continue;
|
|
}
|
|
if (overrideForFaceImages)
|
|
{
|
|
json = Metadata.Models.Stateless.Methods.IMetadata.GetOutputResolution(locationContainer.ExifDirectory);
|
|
if (json is null || !json.Contains(nameof(DateTime)))
|
|
{
|
|
if (checkFaces.Count == 1)
|
|
dFace.ReSaveFace(exifDirectory, locationContainer.FilePath, checkFaces[0].Face, mappedFile: true);
|
|
}
|
|
}
|
|
if (_AllMappedFaceFileNames.Contains(locationContainer.FilePath.Name))
|
|
{
|
|
lock (_AllMappedFaceFiles)
|
|
matches = (from l in _AllMappedFaceFiles where l != locationContainer.FilePath.FullName && Path.GetFileName(l) == locationContainer.FilePath.Name select l).ToArray();
|
|
if (locationContainer.FromDistanceContent && matches.Length > 0)
|
|
AppendMatchingDuplicates(locationContainer.FilePath.FullName, matches);
|
|
}
|
|
if (!locationContainer.FromDistanceContent)
|
|
continue;
|
|
lock (_AllMappedFaceFiles)
|
|
_AllMappedFaceFiles.Add(locationContainer.FilePath.FullName);
|
|
lock (_AllMappedFaceFileNames)
|
|
_AllMappedFaceFileNames.Add(locationContainer.FilePath.Name);
|
|
}
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
// double?[] debug = (from l in _Debug where l is null or not 0 select l).ToArray();
|
|
// if (debug.Length > 0)
|
|
// {
|
|
// string debugMessage = $"{_Debug.Count - debug.Length} - {debug.Min()} - {_Debug.Max()}";
|
|
// }
|
|
// if (_DuplicateMappedFaceFiles.Count > 0)
|
|
// _Log.Info($"Renamed {_DuplicateMappedFaceFiles.Count} to *.dup file(s)");
|
|
if (_Moved.Count > 0 || _Renamed.Count > 0)
|
|
throw new NotImplementedException("Restart!");
|
|
_Debug.Clear();
|
|
_Moved.Clear();
|
|
_Renamed.Clear();
|
|
_AllMappedFaceFiles.Clear();
|
|
_AllMappedFaceFileNames.Clear();
|
|
_DuplicateMappedFaceFiles.Clear();
|
|
}
|
|
|
|
public static void SaveFaceDistances(Property.Models.Configuration configuration, ReadOnlyCollection<SortingContainer> sortingContainers)
|
|
{
|
|
string eDistanceContentCollectionDirectory = 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);
|
|
}
|
|
|
|
public static ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> GetMappedWithEncoding(ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> mapped)
|
|
{
|
|
Dictionary<int, ReadOnlyDictionary<int, LocationContainer>> results = [];
|
|
string? json;
|
|
LocationContainer? locationContainer;
|
|
Shared.Models.FaceEncoding? faceEncoding;
|
|
FaceRecognitionDotNet.FaceEncoding? encoding;
|
|
Dictionary<int, LocationContainer> keyValuePairs;
|
|
foreach (KeyValuePair<int, ReadOnlyDictionary<int, LocationContainer>> keyValuePair in mapped)
|
|
{
|
|
keyValuePairs = [];
|
|
foreach (KeyValuePair<int, LocationContainer> keyValue in keyValuePair.Value)
|
|
{
|
|
json = Metadata.Models.Stateless.Methods.IMetadata.GetFaceEncoding(keyValue.Value.ExifDirectory);
|
|
faceEncoding = json is null ? null : JsonSerializer.Deserialize<Shared.Models.FaceEncoding>(json);
|
|
if (faceEncoding is null)
|
|
continue;
|
|
encoding = FaceRecognition.LoadFaceEncoding(faceEncoding.RawEncoding);
|
|
locationContainer = LocationContainer.Get(keyValue.Value, encoding, keepExifDirectory: false);
|
|
keyValuePairs.Add(keyValue.Key, locationContainer);
|
|
}
|
|
results.Add(keyValuePair.Key, keyValuePairs.AsReadOnly());
|
|
}
|
|
return results.AsReadOnly();
|
|
}
|
|
|
|
public static List<LocationContainer> GetPreFilterLocationContainer(int maxDegreeOfParallelism, Configuration configuration, string focusDirectory, string focusModel, int? skipPersonWithMoreThen, long ticks, MapLogic mapLogic, long[] jLinkResolvedPersonKeys, ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> mapped, List<LocationContainer> available)
|
|
{
|
|
List<LocationContainer> results = [];
|
|
string? json;
|
|
string? model;
|
|
bool? canReMap;
|
|
bool? isFocusPerson;
|
|
bool? inSkipCollection;
|
|
Shared.Models.FaceEncoding? faceEncoding;
|
|
FaceRecognitionDotNet.FaceEncoding? encoding;
|
|
ReadOnlyDictionary<int, LocationContainer>? keyValuePairs;
|
|
ReadOnlyDictionary<int, ReadOnlyCollection<PersonContainer>>? wholePercentagesToPersonContainers;
|
|
foreach (LocationContainer locationContainer in available)
|
|
{
|
|
if (mapped.TryGetValue(locationContainer.Id, out keyValuePairs))
|
|
{
|
|
if (keyValuePairs.ContainsKey(locationContainer.WholePercentages))
|
|
continue;
|
|
}
|
|
if (locationContainer.ExifDirectory is null || locationContainer.FaceFile is null)
|
|
continue;
|
|
inSkipCollection = mapLogic.InSkipCollection(locationContainer.Id, locationContainer.WholePercentages);
|
|
if (inSkipCollection is not null && inSkipCollection.Value)
|
|
continue;
|
|
wholePercentagesToPersonContainers = mapLogic.GetWholePercentagesToPersonContainers(locationContainer.Id);
|
|
canReMap = Map.Models.Stateless.Methods.IMapLogic.CanReMap(jLinkResolvedPersonKeys, wholePercentagesToPersonContainers, locationContainer.WholePercentages);
|
|
if (canReMap is not null && !canReMap.Value)
|
|
continue;
|
|
isFocusPerson = mapLogic.IsFocusPerson(skipPersonWithMoreThen, jLinkResolvedPersonKeys, wholePercentagesToPersonContainers, locationContainer.WholePercentages);
|
|
if (isFocusPerson is not null && !isFocusPerson.Value)
|
|
continue;
|
|
if (!string.IsNullOrEmpty(focusModel))
|
|
{
|
|
model = IMetaBase.GetModel(locationContainer.ExifDirectory);
|
|
if (string.IsNullOrEmpty(model) || !model.Contains(focusModel))
|
|
continue;
|
|
}
|
|
if (!string.IsNullOrEmpty(focusDirectory))
|
|
{
|
|
if (!locationContainer.FilePath.DirectoryFullPath.Contains(focusDirectory))
|
|
continue;
|
|
}
|
|
json = Metadata.Models.Stateless.Methods.IMetadata.GetFaceEncoding(locationContainer.ExifDirectory);
|
|
faceEncoding = json is null ? null : JsonSerializer.Deserialize<Shared.Models.FaceEncoding>(json);
|
|
if (faceEncoding is null)
|
|
continue;
|
|
encoding = FaceRecognition.LoadFaceEncoding(faceEncoding.RawEncoding);
|
|
results.Add(LocationContainer.Get(locationContainer, encoding, keepExifDirectory: false));
|
|
}
|
|
return results;
|
|
}
|
|
|
|
public static void PreFilterSetFaceDistances(int maxDegreeOfParallelism, Configuration configuration, long ticks, ReadOnlyCollection<Face> distinctValidImageFaces)
|
|
{
|
|
List<Face> faces = [];
|
|
foreach (Face face in distinctValidImageFaces)
|
|
{
|
|
if (face.Mapping?.MappingFromFilterPre is null)
|
|
throw new NotSupportedException();
|
|
if (face.Mapping.MappingFromFilterPre.InSkipCollection is not null && face.Mapping.MappingFromFilterPre.InSkipCollection.Value)
|
|
continue;
|
|
if (face.Mapping.MappingFromFilterPre.IsFocusModel is not null && !face.Mapping.MappingFromFilterPre.IsFocusModel.Value)
|
|
continue;
|
|
if (face.Mapping.MappingFromFilterPre.IsFocusRelativePath is not null && !face.Mapping.MappingFromFilterPre.IsFocusRelativePath.Value)
|
|
continue;
|
|
// if (!configuration.ReMap && face.Mapping.MappingFromPerson is not null)
|
|
// continue;
|
|
if (!configuration.ReMap && face.FaceEncoding is not null && face.FaceDistance?.Encoding is not null && face.FaceDistance.Encoding is FaceRecognitionDotNet.FaceEncoding)
|
|
throw new NotSupportedException($"{face.FaceEncoding} should not be null!");
|
|
faces.Add(face);
|
|
}
|
|
int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
|
|
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
|
|
string message = $") {faces.Count:000} Load Face Encoding - {totalSeconds} total second(s)";
|
|
ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true };
|
|
using ProgressBar progressBar = new(faces.Count, message, options);
|
|
_ = Parallel.For(0, faces.Count, parallelOptions, (i, state) =>
|
|
{
|
|
Face face = faces[i];
|
|
FaceRecognitionDotNet.FaceEncoding faceEncoding;
|
|
if (face.FaceEncoding is null || face.Mapping?.MappingFromLocation is null)
|
|
throw new NotSupportedException();
|
|
progressBar.Tick();
|
|
faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding);
|
|
DateTime dateTime = face.Mapping.MappingFromItem.GetDateTimeOriginalThenMinimumDateTime();
|
|
FaceDistance faceDistance = new(face.Mapping.MappingFromLocation.ConfidencePercent, dateTime, faceEncoding, face.Mapping.MappingFromFilterPost, face.Mapping.MappingFromItem.Id, face.Mapping.MappingFromItem.IsWrongYear, face.Mapping.MappingFromLocation.WholePercentages);
|
|
lock (face)
|
|
face.SetFaceDistance(faceDistance);
|
|
});
|
|
}
|
|
|
|
private static List<SortingContainer> GetSortingContainers(Configuration mapConfiguration, IDistanceLimits distanceLimits, Face face, FaceDistance faceDistanceEncoding, List<Sorting> sortingCollection)
|
|
{
|
|
List<SortingContainer> results = [];
|
|
int days = 0, distance = 0;
|
|
SortingContainer sortingContainer;
|
|
Sorting[] collection = ISorting.Sort(sortingCollection);
|
|
foreach (Sorting sorting in collection)
|
|
{
|
|
if (face.Mapping?.MappingFromLocation is null || faceDistanceEncoding.WholePercentages is null)
|
|
throw new NotSupportedException();
|
|
if (!mapConfiguration.SaveSortingWithoutPerson && face.Mapping.MappingFromPerson is null)
|
|
continue;
|
|
if (sorting.DaysDelta > distanceLimits.RangeDaysDeltaTolerance)
|
|
{
|
|
days += 1;
|
|
continue;
|
|
}
|
|
if (sorting.DistancePermyriad > distanceLimits.FaceDistancePermyriad)
|
|
{
|
|
distance += 1;
|
|
continue;
|
|
}
|
|
sortingContainer = new(sorting, face.Mapping);
|
|
results.Add(sortingContainer);
|
|
if (results.Count >= distanceLimits.SortingMaximumPerFaceShouldBeHigh)
|
|
break;
|
|
}
|
|
distanceLimits.AddCounts(days, distance);
|
|
return results;
|
|
}
|
|
|
|
private static List<Sorting> GetSortingCollection(MapLogic mapLogic, ReadOnlyCollection<FaceDistance> faceDistanceEncodings, int i, Face face, FaceDistance faceDistanceEncoding)
|
|
{
|
|
List<Sorting> results;
|
|
List<FaceDistance> faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding);
|
|
results = mapLogic.GetSortingCollection(i, face, faceDistanceEncoding, faceDistanceLengths);
|
|
return results;
|
|
}
|
|
|
|
public static ReadOnlyCollection<FaceDistanceContainer> GetFaceDistanceContainers(ReadOnlyCollection<Face> distinctValidImageFaces)
|
|
{
|
|
ReadOnlyCollection<FaceDistanceContainer> results;
|
|
DateTime dateTime;
|
|
FaceDistance faceDistance;
|
|
FaceDistanceContainer faceDistanceContainer;
|
|
List<FaceDistanceContainer> collection = [];
|
|
foreach (Face face in distinctValidImageFaces)
|
|
{
|
|
if (face.Mapping?.MappingFromLocation is null)
|
|
throw new NotSupportedException();
|
|
if (face.FaceDistance?.Encoding is not FaceRecognitionDotNet.FaceEncoding faceEncoding)
|
|
continue;
|
|
dateTime = face.Mapping.MappingFromItem.GetDateTimeOriginalThenMinimumDateTime();
|
|
faceDistance = new(face.Mapping.MappingFromLocation.ConfidencePercent, dateTime, faceEncoding, face.Mapping.MappingFromFilterPost, face.Mapping.MappingFromItem.Id, face.Mapping.MappingFromItem.IsWrongYear, face.Mapping.MappingFromLocation.WholePercentages);
|
|
faceDistanceContainer = new(face, faceDistance);
|
|
collection.Add(faceDistanceContainer);
|
|
}
|
|
results = collection.AsReadOnly();
|
|
return results;
|
|
}
|
|
|
|
public static List<LocationContainer> GetPostFilterLocationContainer(MapLogic mapLogic, List<LocationContainer> preFiltered, DistanceLimits distanceLimits)
|
|
{
|
|
List<LocationContainer> results = [];
|
|
foreach (LocationContainer locationContainer in preFiltered)
|
|
{
|
|
if (locationContainer.FaceFile is null)
|
|
continue;
|
|
if (locationContainer.FaceFile.AreaPermyriad < distanceLimits.FaceAreaPermyriad)
|
|
continue;
|
|
if (locationContainer.FaceFile.ConfidencePercent < distanceLimits.FaceConfidencePercent)
|
|
continue;
|
|
results.Add(locationContainer);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
public static FaceDistanceContainer[] FilteredPostLoadFaceDistanceContainers(MapLogic mapLogic, ReadOnlyCollection<FaceDistanceContainer> faceDistanceContainers, long? skipOlderThan, DistanceLimits distanceLimits)
|
|
{
|
|
List<FaceDistanceContainer> results = [];
|
|
foreach (FaceDistanceContainer faceDistanceContainer in faceDistanceContainers)
|
|
{
|
|
if (faceDistanceContainer.FaceDistance is null || faceDistanceContainer.Face.Mapping?.MappingFromLocation is null)
|
|
throw new NotSupportedException();
|
|
if (skipOlderThan is not null && faceDistanceContainer.FaceDistance.DateTimeOriginalThenMinimumDateTime.Ticks < skipOlderThan.Value)
|
|
continue;
|
|
if (faceDistanceContainer.Face.Mapping.MappingFromLocation.ConfidencePercent < distanceLimits.FaceConfidencePercent)
|
|
continue;
|
|
if (faceDistanceContainer.Face.Mapping.MappingFromLocation.AreaPermyriad < distanceLimits.FaceAreaPermyriad)
|
|
continue;
|
|
if (faceDistanceContainer.Face.Mapping.MappingFromFilterPre.InSkipCollection is not null && faceDistanceContainer.Face.Mapping.MappingFromFilterPre.InSkipCollection.Value)
|
|
// throw new NotSupportedException(nameof(PreFilterSetFaceDistances));
|
|
continue;
|
|
if (faceDistanceContainer.Face.Mapping.MappingFromFilterPre.IsFocusModel is not null && !faceDistanceContainer.Face.Mapping.MappingFromFilterPre.IsFocusModel.Value)
|
|
continue;
|
|
if (faceDistanceContainer.Face.Mapping.MappingFromFilterPre.IsFocusRelativePath is not null && !faceDistanceContainer.Face.Mapping.MappingFromFilterPre.IsFocusRelativePath.Value)
|
|
continue;
|
|
if (faceDistanceContainer.Face.Mapping.MappingFromFilterPost.InSkipCollection is not null && faceDistanceContainer.Face.Mapping.MappingFromFilterPost.InSkipCollection.Value)
|
|
continue;
|
|
if (faceDistanceContainer.Face.Mapping.MappingFromFilterPost.IsFocusPerson is not null && !faceDistanceContainer.Face.Mapping.MappingFromFilterPost.IsFocusPerson.Value)
|
|
continue;
|
|
results.Add(faceDistanceContainer);
|
|
}
|
|
return results.ToArray();
|
|
}
|
|
|
|
private static ReadOnlyCollection<LocationContainer> GetReadOnlyLocationContainer(ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> mappedWithEncoding, List<LocationContainer> postFiltered)
|
|
{
|
|
List<LocationContainer> results = [];
|
|
foreach (LocationContainer locationContainer in postFiltered)
|
|
results.Add(locationContainer);
|
|
foreach (KeyValuePair<int, ReadOnlyDictionary<int, LocationContainer>> keyValuePair in mappedWithEncoding)
|
|
{
|
|
foreach (KeyValuePair<int, LocationContainer> keyValue in keyValuePair.Value)
|
|
results.Add(keyValue.Value);
|
|
}
|
|
return results.AsReadOnly();
|
|
}
|
|
|
|
public static ReadOnlyCollection<LocationContainer> GetMatrixLocationContainers(Configuration mapConfiguration, long ticks, MapLogic mapLogic, ReadOnlyDictionary<int, ReadOnlyDictionary<int, LocationContainer>> mappedWithEncoding, List<LocationContainer> preFiltered, DistanceLimits distanceLimits, List<LocationContainer> postFiltered, Action? tick)
|
|
{
|
|
List<LocationContainer> results = [];
|
|
ReadOnlyCollection<LocationContainer> locationContainers;
|
|
ReadOnlyCollection<LocationContainer> readOnlyLocationContainers = GetReadOnlyLocationContainer(mappedWithEncoding, postFiltered);
|
|
foreach (LocationContainer locationContainer in postFiltered)
|
|
{
|
|
tick?.Invoke();
|
|
locationContainers = FaceRecognition.GetLocationContainers(mapConfiguration.FaceDistancePermyriad, readOnlyLocationContainers, locationContainer);
|
|
foreach (LocationContainer item in locationContainers)
|
|
{
|
|
if (item.LengthPermyriad is null)
|
|
continue;
|
|
if (item.LengthPermyriad > distanceLimits.FaceDistancePermyriad)
|
|
break;
|
|
if (!mapConfiguration.SaveSortingWithoutPerson && item.PersonKey is null)
|
|
continue;
|
|
if (item.Id == locationContainer.Id && item.WholePercentages == locationContainer.WholePercentages)
|
|
continue;
|
|
results.Add(item);
|
|
}
|
|
}
|
|
LocationContainer[] array = results.OrderBy(l => l.LengthPermyriad).ToArray();
|
|
return new(array);
|
|
}
|
|
|
|
public static ReadOnlyCollection<SortingContainer> SetFaceMappingSortingCollectionThenGetSortedSortingContainers(int maxDegreeOfParallelism, Configuration mapConfiguration, long ticks, MapLogic mapLogic, IDistanceLimits distanceLimits, ReadOnlyCollection<FaceDistance> faceDistanceEncodings, FaceDistanceContainer[] filteredFaceDistanceContainers)
|
|
{
|
|
List<SortingContainer> results = [];
|
|
int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds);
|
|
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
|
|
string message = $") {filteredFaceDistanceContainers.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(filteredFaceDistanceContainers.Length, message, options);
|
|
_ = Parallel.For(0, filteredFaceDistanceContainers.Length, parallelOptions, (i, state) =>
|
|
{
|
|
progressBar.Tick();
|
|
Face face = filteredFaceDistanceContainers[i].Face;
|
|
FaceDistance faceDistanceEncoding = filteredFaceDistanceContainers[i].FaceDistance;
|
|
List<Sorting> sortingCollection = GetSortingCollection(mapLogic, faceDistanceEncodings, i, face, faceDistanceEncoding);
|
|
if (sortingCollection.Count == 0)
|
|
return;
|
|
List<SortingContainer> sortingContainers = GetSortingContainers(mapConfiguration, distanceLimits, face, faceDistanceEncoding, sortingCollection);
|
|
if (sortingContainers.Count > 0)
|
|
{
|
|
lock (results)
|
|
results.AddRange(sortingContainers);
|
|
}
|
|
});
|
|
if (distanceLimits is not null && distanceLimits.RangeDaysDeltaTargetLessThenUpper)
|
|
results = ISortingContainer.Sort(results);
|
|
else
|
|
results = ISortingContainer.SortUsingDaysDelta(results);
|
|
return results.AsReadOnly();
|
|
}
|
|
|
|
private static ReadOnlyCollection<RelationContainer> GetRelationCollections(IDistanceLimits distanceLimits, int faceDistancePermyriad, int locationContainerDistanceTake, float distanceTolerance, List<Record> records)
|
|
{
|
|
List<RelationContainer> results = [];
|
|
string fileName;
|
|
FileHolder fileHolder;
|
|
int distancePermyriad;
|
|
List<string> files = [];
|
|
List<Relation> mappedRelations;
|
|
long ticks = DateTime.Now.Ticks;
|
|
FaceDistance? faceDistanceEncoding;
|
|
List<FaceDistance> faceDistanceLengths;
|
|
List<FaceDistance> faceDistanceEncodings = [];
|
|
foreach (Record record in records)
|
|
{
|
|
files.Add(record.FilePath.FullName);
|
|
faceDistanceEncodings.Add(new(record.FaceRecognitionDotNetFaceEncoding));
|
|
}
|
|
foreach (Record record in records)
|
|
{
|
|
mappedRelations = [];
|
|
FaceDistance faceDistanceLength;
|
|
fileHolder = Shared.Models.Stateless.Methods.IFileHolder.Get(record.FilePath.FullName);
|
|
if (files.Count > 1)
|
|
{
|
|
faceDistanceEncoding = new(record.FaceRecognitionDotNetFaceEncoding);
|
|
if (faceDistanceEncoding is null)
|
|
throw new NullReferenceException(nameof(faceDistanceEncoding));
|
|
faceDistanceLengths = FaceRecognition.FaceDistances(new(faceDistanceEncodings), faceDistanceEncoding);
|
|
for (int i = 0; i < faceDistanceLengths.Count; i++)
|
|
{
|
|
fileName = Path.GetFileName(files[i]);
|
|
if (fileName == fileHolder.Name)
|
|
continue;
|
|
faceDistanceLength = faceDistanceLengths[i];
|
|
if (faceDistanceLength.Length is null || faceDistanceLength.Length > distanceLimits.RangeDistanceToleranceUpperLimit)
|
|
continue;
|
|
if (faceDistanceLength.Length is null || faceDistanceLength.Length.Value > distanceTolerance)
|
|
continue;
|
|
distancePermyriad = (int)(faceDistanceLength.Length.Value * faceDistancePermyriad);
|
|
mappedRelations.Add(new(distancePermyriad, files[i]));
|
|
}
|
|
}
|
|
mappedRelations = (from l in mappedRelations orderby l.DistancePermyriad select l).Take(locationContainerDistanceTake).ToList();
|
|
results.Add(new(fileHolder, mappedRelations.AsReadOnly()));
|
|
}
|
|
return results.AsReadOnly();
|
|
}
|
|
|
|
ReadOnlyCollection<RelationContainer> IDistance.GetRelationContainers(IDistanceLimits distanceLimits, int faceDistancePermyriad, int locationContainerDistanceTake, float locationContainerDistanceTolerance, ReadOnlyCollection<LocationContainer> locationContainers)
|
|
{
|
|
ReadOnlyCollection<RelationContainer> result;
|
|
string? json;
|
|
List<Record> records = [];
|
|
Shared.Models.FaceEncoding? modelsFaceEncoding;
|
|
FaceRecognitionDotNet.FaceEncoding faceRecognitionDotNetFaceEncoding;
|
|
foreach (LocationContainer locationContainer in locationContainers)
|
|
{
|
|
json = Metadata.Models.Stateless.Methods.IMetadata.GetFaceEncoding(locationContainer.ExifDirectory);
|
|
if (json is null)
|
|
continue;
|
|
modelsFaceEncoding = JsonSerializer.Deserialize<Shared.Models.FaceEncoding>(json);
|
|
if (modelsFaceEncoding is null)
|
|
throw new NotSupportedException();
|
|
faceRecognitionDotNetFaceEncoding = FaceRecognition.LoadFaceEncoding(modelsFaceEncoding.RawEncoding);
|
|
records.Add(new(locationContainer.FilePath, faceRecognitionDotNetFaceEncoding));
|
|
}
|
|
result = GetRelationCollections(distanceLimits, faceDistancePermyriad, locationContainerDistanceTake, locationContainerDistanceTolerance, records);
|
|
return result;
|
|
}
|
|
|
|
} |