2022-10-23 22:45:55 -07:00

470 lines
22 KiB
C#

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<string> _Moved;
private readonly List<double?> _Debug;
private readonly List<string> _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<string> _AllMappedFaceFiles;
private readonly double[] _RangeDistanceTolerance;
private readonly int _DistancePixelDistanceTolerance;
private readonly List<string> _AllMappedFaceFileNames;
private readonly List<string> _DuplicateMappedFaceFiles;
public E_Distance(bool distanceMoveUnableToMatch, int distancePixelDistanceTolerance, 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<E_Distance>();
_DistanceRenameToMatch = distanceRenameToMatch;
_FaceConfidencePercent = faceConfidencePercent;
_RangeDistanceTolerance = rangeDistanceTolerance;
_DistanceMoveUnableToMatch = distanceMoveUnableToMatch;
_DistancePixelDistanceTolerance = distancePixelDistanceTolerance;
}
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<string> 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}_{string.Join('-', _RangeDistanceTolerance)})");
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<string> results = new();
const string comment = "Comment: ";
if (File.Exists(file))
{
IReadOnlyList<MetadataExtractor.Directory> 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<string> results = new();
const string artist = "Artist: ";
if (File.Exists(file))
{
IReadOnlyList<MetadataExtractor.Directory> 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 FaceDistanceContainer[] GetFaceDistanceContainers(MappingFromItem mappingFromItem, Face[] filteredFaces)
{
FaceDistanceContainer[] results;
int confidencePercent;
FaceDistance faceDistance;
int normalizedPixelPercentage;
FaceDistanceContainer faceDistanceContainer;
List<FaceDistanceContainer> collection = new();
foreach (Face face in filteredFaces)
{
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);
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(confidencePercent, faceEncoding, mappingFromItem.Id, mappingFromItem.IsWrongYear, mappingFromItem.MinimumDateTime, normalizedPixelPercentage);
else
{
faceEncoding = FaceRecognition.LoadFaceEncoding(face.FaceEncoding.RawEncoding);
faceDistance = new(confidencePercent, faceEncoding, mappingFromItem.Id, mappingFromItem.IsWrongYear, mappingFromItem.MinimumDateTime, normalizedPixelPercentage);
lock (filteredFaces)
face.SetFaceDistance(faceDistance);
}
faceDistanceContainer = new(face, faceDistance);
collection.Add(faceDistanceContainer);
}
results = collection.ToArray();
return results;
}
private static List<FaceDistance> GetFaceDistanceEncodings(FaceDistanceContainer[] faceDistanceContainers)
{
List<FaceDistance> 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, Face[] filteredFaces, string json)
{
List<(Face Face, double? Length)> results = new();
Face face;
FaceDistance faceDistanceLength;
Shared.Models.FaceEncoding? modelsFaceEncoding = JsonSerializer.Deserialize<Shared.Models.FaceEncoding>(json);
if (modelsFaceEncoding is null)
throw new NotSupportedException();
FaceRecognitionDotNet.FaceEncoding faceRecognitionDotNetFaceEncoding = FaceRecognition.LoadFaceEncoding(modelsFaceEncoding.RawEncoding);
FaceDistance faceDistanceEncoding = new(faceRecognitionDotNetFaceEncoding);
FaceDistanceContainer[] faceDistanceContainers = GetFaceDistanceContainers(mappingFromItem, filteredFaces);
int faceDistanceContainersLength = faceDistanceContainers.Length;
if (faceDistanceContainersLength != filteredFaces.Length)
throw new NotSupportedException();
List<FaceDistance> faceDistanceEncodings = GetFaceDistanceEncodings(faceDistanceContainers);
if (faceDistanceEncodings.Count != filteredFaces.Length)
throw new NotSupportedException();
List<FaceDistance> faceDistanceLengths = FaceRecognition.FaceDistances(faceDistanceEncodings, faceDistanceEncoding);
if (faceDistanceLengths.Count != faceDistanceContainersLength)
throw new NotSupportedException();
for (int i = 0; i < filteredFaces.Length; i++)
{
face = filteredFaces[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, Face[] filteredFaces, string json)
{
(Face, double?)[] results;
List<(Face Face, double? Length)> collection = GetValues(mappingFromItem, filteredFaces, 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(Face[] filteredFaces, 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 filteredFaces)
{
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(Face[] filteredFaces, int normalizedPixelPercentage)
{
if (!filteredFaces.Any())
throw new NotSupportedException();
(Face, double?)[] results;
const int zero = 0;
OutputResolution? outputResolution = filteredFaces[zero].OutputResolution;
(int? x1, int? y1) = GetXY(normalizedPixelPercentage, outputResolution);
results = GetClosestFaceByPixel(filteredFaces, x1, y1);
return results;
}
private (Face, double?)[] GetClosestFaceByPixel(Face[] filteredFaces, string json)
{
if (!filteredFaces.Any())
throw new NotSupportedException();
(Face, double?)[] results;
const int zero = 0;
OutputResolution? outputResolution = filteredFaces[zero].OutputResolution;
if (outputResolution is null)
throw new NullReferenceException(nameof(outputResolution));
Location? location = JsonSerializer.Deserialize<Location>(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(filteredFaces, x1, y1);
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 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<Face> faces, List<(string MappedFaceFile, int normalizedPixelPercentage)> collection)
{
string? json;
string[] matches;
FileInfo? fileInfo;
string mappedFaceFileName;
List<(Face, double?)> checkFaces = new();
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 ((string mappedFaceFile, int normalizedPixelPercentage) in collection)
{
if (!filteredFaces.Any())
break;
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(filteredFaces, 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, filteredFaces, json));
}
if (checkFaces.Count != 1 && _DistancePixelDistanceTolerance > 0)
{
checkFaces.Clear();
json = GetFaceLocation(mappedFaceFile);
if (json is not null)
checkFaces.AddRange(GetClosestFaceByPixel(filteredFaces, json));
else
checkFaces.AddRange(GetClosestFaceByPixel(filteredFaces, 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()
{
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();
}
}