2022-11-20 23:20:28 -07:00

440 lines
22 KiB
C#

using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using View_by_Distance.Face.Models.Stateless;
using View_by_Distance.FaceRecognitionDotNet;
using View_by_Distance.Metadata.Models;
using View_by_Distance.Property.Models;
using View_by_Distance.Property.Models.Stateless;
using View_by_Distance.Resize.Models;
using View_by_Distance.Shared.Models;
using View_by_Distance.Shared.Models.Stateless;
namespace View_by_Distance.Face.Models;
/// <summary>
// List<D_Faces>
/// </summary>
public class D_Face
{
public List<string> AngleBracketCollection { get; }
protected readonly string _FileNameExtension;
public string FileNameExtension => _FileNameExtension;
protected readonly string _HiddenFileNameExtension;
public string HiddenFileNameExtension => _HiddenFileNameExtension;
private readonly Model _Model;
private readonly string _ArgZero;
private readonly int _NumberOfJitters;
private readonly Serilog.ILogger? _Log;
private readonly bool _OverrideForFaceImages;
private readonly Configuration _Configuration;
private readonly int _NumberOfTimesToUpsample;
private readonly bool _RetryImagesWithoutAFace;
private readonly ImageCodecInfo _ImageCodecInfo;
private readonly ModelParameter _ModelParameter;
private readonly PredictorModel _PredictorModel;
private readonly bool _CheckDFaceAndUpWriteDates;
private readonly bool _PropertiesChangedForFaces;
private readonly ConstructorInfo _ConstructorInfo;
private readonly int _FaceDistanceHiddenImageFactor;
private readonly EncoderParameters _EncoderParameters;
private readonly ImageCodecInfo _HiddenImageCodecInfo;
private readonly bool _ForceFaceLastWriteTimeToCreationTime;
private readonly EncoderParameters _HiddenEncoderParameters;
private readonly JsonSerializerOptions _WriteIndentedAndWhenWritingNull;
public D_Face(
string argZero,
bool checkDFaceAndUpWriteDates,
Configuration configuration,
EncoderParameters encoderParameters,
int faceDistanceHiddenImageFactor,
string filenameExtension,
bool forceFaceLastWriteTimeToCreationTime,
EncoderParameters hiddenEncoderParameters,
string hiddenFileNameExtension,
ImageCodecInfo hiddenImageCodecInfo,
ImageCodecInfo imageCodecInfo,
string modelDirectory,
string modelName,
int numberOfJitters,
int numberOfTimesToUpsample,
bool overrideForFaceImages,
bool retryImagesWithoutAFace,
string predictorModelName,
bool propertiesChangedForFaces)
{
_ArgZero = argZero;
_Configuration = configuration;
_ImageCodecInfo = imageCodecInfo;
_NumberOfJitters = numberOfJitters;
_EncoderParameters = encoderParameters;
_FileNameExtension = filenameExtension;
_Log = Serilog.Log.ForContext<D_Face>();
AngleBracketCollection = new List<string>();
_HiddenImageCodecInfo = hiddenImageCodecInfo;
_OverrideForFaceImages = overrideForFaceImages;
_RetryImagesWithoutAFace = retryImagesWithoutAFace;
_HiddenEncoderParameters = hiddenEncoderParameters;
_HiddenFileNameExtension = hiddenFileNameExtension;
_NumberOfTimesToUpsample = numberOfTimesToUpsample;
_CheckDFaceAndUpWriteDates = checkDFaceAndUpWriteDates;
_PropertiesChangedForFaces = propertiesChangedForFaces;
_FaceDistanceHiddenImageFactor = faceDistanceHiddenImageFactor;
_ForceFaceLastWriteTimeToCreationTime = forceFaceLastWriteTimeToCreationTime;
(Model model, PredictorModel predictorModel, ModelParameter modelParameter) = GetModel(modelDirectory, modelName, predictorModelName);
_Model = model;
_PredictorModel = predictorModel;
_ModelParameter = modelParameter;
ConstructorInfo? constructorInfo = typeof(PropertyItem).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, Array.Empty<Type>(), null);
if (constructorInfo is null)
throw new Exception();
_ConstructorInfo = constructorInfo;
_WriteIndentedAndWhenWritingNull = new JsonSerializerOptions { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };
}
public override string ToString()
{
string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true });
return result;
}
private static (Model model, PredictorModel predictorModel, ModelParameter modelParameter) GetModel(string modelDirectory, string modelName, string predictorModelName)
{
(Model, PredictorModel, ModelParameter) result;
Array array;
Model? model = null;
PredictorModel? predictorModel = null;
array = Enum.GetValues(typeof(Model));
foreach (Model check in array)
{
if (modelName.Contains(check.ToString()))
{
model = check;
break;
}
}
if (model is null)
throw new Exception("Destination directory must have Model name!");
model = model.Value;
array = Enum.GetValues(typeof(PredictorModel));
foreach (PredictorModel check in array)
{
if (predictorModelName.Contains(check.ToString()))
{
predictorModel = check;
break;
}
}
if (predictorModel is null)
throw new Exception("Destination directory must have Predictor Model name!");
predictorModel = predictorModel.Value;
ModelParameter modelParameter = new()
{
CnnFaceDetectorModel = File.ReadAllBytes(Path.Combine(modelDirectory, "mmod_human_face_detector.dat")),
FaceRecognitionModel = File.ReadAllBytes(Path.Combine(modelDirectory, "dlib_face_recognition_resnet_model_v1.dat")),
PosePredictor5FaceLandmarksModel = File.ReadAllBytes(Path.Combine(modelDirectory, "shape_predictor_5_face_landmarks.dat")),
PosePredictor68FaceLandmarksModel = File.ReadAllBytes(Path.Combine(modelDirectory, "shape_predictor_68_face_landmarks.dat"))
};
result = new(model.Value, predictorModel.Value, modelParameter);
return result;
}
#pragma warning disable CA1416
private static byte[] GetBytes(string value)
{
byte[] results = new byte[value.Length + 1];
for (int i = 0; i < value.Length; i++)
results[i] = (byte)value[i];
results[value.Length] = 0x00;
return results;
}
private PropertyItem GetPropertyItem(int id, string value)
{
PropertyItem result = (PropertyItem)_ConstructorInfo.Invoke(null);
byte[] bytes = GetBytes(value);
result.Id = id;
result.Len = value.Length + 1;
result.Type = 2;
result.Value = bytes;
return result;
}
private void SaveFaces(FileHolder resizedFileHolder, List<(Shared.Models.Face, FileInfo?, string)> collection)
{
int width;
int height;
Bitmap bitmap;
Graphics graphics;
Location? location;
Rectangle rectangle;
string locationJson;
string faceEncodingJson;
PropertyItem? propertyItem;
string outputResolutionJson;
int artist = (int)IExif.Tags.Artist;
int fileSource = (int)IExif.Tags.FileSource;
int userComment = (int)IExif.Tags.UserComment;
using Bitmap source = new(resizedFileHolder.FullName);
foreach ((Shared.Models.Face face, FileInfo? fileInfo, string fileName) in collection)
{
if (fileInfo is null)
continue;
if (face.FaceEncoding is null || face?.Location is null || face?.OutputResolution is null)
continue;
location = Shared.Models.Stateless.Methods.ILocation.GetLocation(face.Location, ILocation.Digits, ILocation.Factor, source.Height, source.Width, collection.Count);
if (location is null)
continue;
width = location.Right - location.Left;
height = location.Bottom - location.Top;
locationJson = JsonSerializer.Serialize(face.Location);
faceEncodingJson = JsonSerializer.Serialize(face.FaceEncoding);
outputResolutionJson = JsonSerializer.Serialize(face.OutputResolution);
rectangle = new Rectangle(location.Left, location.Top, width, height);
using (bitmap = new(width, height))
{
using (graphics = Graphics.FromImage(bitmap))
graphics.DrawImage(source, new Rectangle(0, 0, width, height), rectangle, GraphicsUnit.Pixel);
propertyItem = GetPropertyItem(fileSource, locationJson);
bitmap.SetPropertyItem(propertyItem);
propertyItem = GetPropertyItem(artist, outputResolutionJson);
bitmap.SetPropertyItem(propertyItem);
propertyItem = GetPropertyItem(userComment, faceEncodingJson);
bitmap.SetPropertyItem(propertyItem);
bitmap.Save(fileInfo.FullName, _ImageCodecInfo, _EncoderParameters);
}
if (File.Exists(fileName))
File.Delete(fileName);
location = Shared.Models.Stateless.Methods.ILocation.GetLocation(_FaceDistanceHiddenImageFactor, face.Location, ILocation.Digits, ILocation.Factor, source.Height, source.Width, collection.Count);
if (location is null)
continue;
width = location.Right - location.Left;
height = location.Bottom - location.Top;
rectangle = new Rectangle(location.Left, location.Top, width, height);
using (bitmap = new(width, height))
{
using (graphics = Graphics.FromImage(bitmap))
graphics.DrawImage(source, new Rectangle(0, 0, width, height), rectangle, GraphicsUnit.Pixel);
bitmap.Save(fileName, _HiddenImageCodecInfo, _HiddenEncoderParameters);
}
File.SetAttributes(fileName, FileAttributes.Hidden);
}
}
private List<Shared.Models.Face> GetFaces(Shared.Models.Property property, MappingFromItem mappingFromItem, int outputResolutionWidth, int outputResolutionHeight, int outputResolutionOrientation)
{
if (_Log is null)
throw new NullReferenceException(nameof(_Log));
List<Shared.Models.Face> results = new();
FaceRecognitionDotNet.Image? unknownImage;
if (!mappingFromItem.ResizedFileHolder.Exists)
unknownImage = null;
else
{
try
{ unknownImage = FaceRecognition.LoadImageFile(mappingFromItem.ResizedFileHolder.FullName); }
catch (Exception)
{
unknownImage = null;
_Log.Info(string.Concat(new StackFrame().GetMethod()?.Name, " <", mappingFromItem.ResizedFileHolder.FullName, ">"));
}
}
if (unknownImage is null)
results.Add(new(property, outputResolutionWidth, outputResolutionHeight, outputResolutionOrientation, location: null));
else
{
List<(Location Location, FaceRecognitionDotNet.FaceEncoding? FaceEncoding, Dictionary<FacePart, FacePoint[]>? FaceParts)> collection;
FaceRecognition faceRecognition = new(_NumberOfTimesToUpsample, _NumberOfJitters, _PredictorModel, _Model, _ModelParameter);
collection = faceRecognition.GetCollection(unknownImage, includeFaceEncoding: true, includeFaceParts: true);
if (!collection.Any())
results.Add(new(property, outputResolutionWidth, outputResolutionHeight, outputResolutionOrientation, location: null));
else
{
double[] rawEncoding;
Shared.Models.Face face;
Shared.Models.FaceEncoding convertedFaceEncoding;
foreach ((Location location, FaceRecognitionDotNet.FaceEncoding? faceEncoding, Dictionary<FacePart, FacePoint[]>? faceParts) in collection)
{
face = new(property, outputResolutionWidth, outputResolutionHeight, outputResolutionOrientation, location);
if (faceEncoding is not null)
{
rawEncoding = faceEncoding.GetRawEncoding();
convertedFaceEncoding = new(rawEncoding, faceEncoding.Size);
face.SetFaceEncoding(convertedFaceEncoding);
}
if (faceParts is not null)
face.SetFaceParts(faceParts);
results.Add(face);
}
}
unknownImage.Dispose();
faceRecognition.Dispose();
}
if (!results.Any())
throw new Exception();
return results;
}
public void SetAngleBracketCollection(string dResultsFullGroupDirectory, string sourceDirectory)
{
AngleBracketCollection.Clear();
AngleBracketCollection.AddRange(IResult.GetDirectoryInfoCollection(_Configuration,
sourceDirectory,
dResultsFullGroupDirectory,
contentDescription: "n png file(s) for each face found",
singletonDescription: string.Empty,
collectionDescription: "For each image a json file with all faces found",
converted: true));
}
public string GetFacesDirectory(string dResultsFullGroupDirectory, Item item)
{
string result;
bool angleBracketCollectionAny = AngleBracketCollection.Any();
if (!angleBracketCollectionAny)
{
if (item.ImageFileHolder.DirectoryName is null)
throw new NullReferenceException(nameof(item.ImageFileHolder.DirectoryName));
SetAngleBracketCollection(dResultsFullGroupDirectory, item.ImageFileHolder.DirectoryName);
}
if (!angleBracketCollectionAny)
AngleBracketCollection.Clear();
result = Path.Combine(AngleBracketCollection[0].Replace("<>", "()"), item.ImageFileHolder.NameWithoutExtension);
return result;
}
#pragma warning restore CA1416
public List<Shared.Models.Face> GetFaces(string dResultsFullGroupDirectory, List<Tuple<string, DateTime>> subFileTuples, List<string> parseExceptions, Shared.Models.Property property, MappingFromItem mappingFromItem, int outputResolutionWidth, int outputResolutionHeight, int outputResolutionOrientation)
{
List<Shared.Models.Face>? results;
if (string.IsNullOrEmpty(dResultsFullGroupDirectory))
throw new NullReferenceException(nameof(dResultsFullGroupDirectory));
string json;
string[] changesFrom = new string[] { nameof(A_Property), nameof(B_Metadata), nameof(C_Resize) };
List<DateTime> dateTimes = (from l in subFileTuples where changesFrom.Contains(l.Item1) select l.Item2).ToList();
string dCollectionFile = Path.Combine(dResultsFullGroupDirectory, "[]", _Configuration.ResultAllInOne, $"{mappingFromItem.Id}{mappingFromItem.ImageFileHolder.ExtensionLowered}.json");
FileInfo fileInfo = new(dCollectionFile);
if (!fileInfo.FullName.Contains(_Configuration.ResultAllInOne) && !fileInfo.Exists)
{
if (fileInfo.Directory?.Parent is null)
throw new Exception();
string parentCheck = Path.Combine(fileInfo.Directory.Parent.FullName, fileInfo.Name);
if (File.Exists(parentCheck))
{
File.Move(parentCheck, fileInfo.FullName);
fileInfo.Refresh();
}
}
if (_ForceFaceLastWriteTimeToCreationTime && !fileInfo.Exists && File.Exists(Path.ChangeExtension(fileInfo.FullName, ".delete")))
{
File.Move(Path.ChangeExtension(fileInfo.FullName, ".delete"), fileInfo.FullName);
fileInfo.Refresh();
}
if (_ForceFaceLastWriteTimeToCreationTime && fileInfo.Exists && fileInfo.LastWriteTime != fileInfo.CreationTime)
{
File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime);
fileInfo.Refresh();
}
if (_PropertiesChangedForFaces)
results = null;
else if (!fileInfo.Exists)
results = null;
else if (_CheckDFaceAndUpWriteDates && dateTimes.Any() && dateTimes.Max() > fileInfo.LastWriteTime)
results = null;
else
{
json = Shared.Models.Stateless.Methods.IFace.GetJson(fileInfo.FullName);
try
{
results = JsonSerializer.Deserialize<List<Shared.Models.Face>>(json);
if (results is null)
throw new NullReferenceException(nameof(results));
subFileTuples.Add(new Tuple<string, DateTime>(nameof(D_Face), fileInfo.LastWriteTime));
}
catch (Exception)
{
results = null;
parseExceptions.Add(nameof(D_Face));
}
}
if (results is null || (_RetryImagesWithoutAFace && results.Count == 1 && results[0].FaceEncoding is null))
{
bool wasNull = results is null;
results = GetFaces(property, mappingFromItem, outputResolutionWidth, outputResolutionHeight, outputResolutionOrientation);
if (wasNull || (!wasNull && results.Any(l => l.FaceEncoding is not null)))
{
json = JsonSerializer.Serialize(results, _WriteIndentedAndWhenWritingNull);
bool updateDateWhenMatches = dateTimes.Any() && fileInfo.Exists && dateTimes.Max() > fileInfo.LastWriteTime;
DateTime? dateTime = !updateDateWhenMatches ? null : dateTimes.Max();
if (Shared.Models.Stateless.Methods.IPath.WriteAllText(fileInfo.FullName, json, updateDateWhenMatches, compareBeforeWrite: true, updateToWhenMatches: dateTime))
subFileTuples.Add(new Tuple<string, DateTime>(nameof(D_Face), DateTime.Now));
}
}
if (_ForceFaceLastWriteTimeToCreationTime)
{
results = (from l in results select new Shared.Models.Face(ILocation.Digits, ILocation.Factor, results.Count, l)).ToList();
json = JsonSerializer.Serialize(results, _WriteIndentedAndWhenWritingNull);
bool updateDateWhenMatches = dateTimes.Any() && fileInfo.Exists && dateTimes.Max() > fileInfo.LastWriteTime;
DateTime? dateTime = !updateDateWhenMatches ? null : dateTimes.Max();
if (Shared.Models.Stateless.Methods.IPath.WriteAllText(fileInfo.FullName, json, updateDateWhenMatches, compareBeforeWrite: true, updateToWhenMatches: dateTime))
{
File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime);
fileInfo.Refresh();
subFileTuples.Add(new Tuple<string, DateTime>(nameof(D_Face), fileInfo.CreationTime));
}
}
return results;
}
public bool SaveFaces(string dResultsFullGroupDirectory, List<Tuple<string, DateTime>> subFileTuples, List<string> parseExceptions, MappingFromItem mappingFromItem, string facesDirectory, List<Shared.Models.Face> faces)
{
FileInfo fileInfo;
bool result = false;
string parentCheck;
string deterministicHashCodeKey;
List<(Shared.Models.Face, FileInfo?, string)> collection = new();
string[] changesFrom = new string[] { nameof(A_Property), nameof(B_Metadata), nameof(C_Resize) };
List<DateTime> dateTimes = (from l in subFileTuples where changesFrom.Contains(l.Item1) select l.Item2).ToList();
if (!Directory.Exists(facesDirectory))
_ = Directory.CreateDirectory(facesDirectory);
foreach (Shared.Models.Face face in faces)
{
if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null)
{
collection.Add(new(face, null, string.Empty));
continue;
}
deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(mappingFromItem.Id, face.Location, ILocation.Digits, ILocation.Factor, face.OutputResolution);
fileInfo = new FileInfo(Path.Combine(facesDirectory, $"{deterministicHashCodeKey}{mappingFromItem.ImageFileHolder.ExtensionLowered}{_FileNameExtension}"));
if (!fileInfo.FullName.Contains(_Configuration.ResultAllInOne) && !fileInfo.Exists)
{
if (fileInfo.Directory?.Parent is null)
throw new Exception();
parentCheck = Path.Combine(fileInfo.Directory.Parent.FullName, fileInfo.Name);
if (File.Exists(parentCheck))
File.Delete(parentCheck);
}
collection.Add(new(face, fileInfo, Path.Combine(facesDirectory, $"{deterministicHashCodeKey}{mappingFromItem.ImageFileHolder.ExtensionLowered}{_HiddenFileNameExtension}")));
if (_OverrideForFaceImages)
result = true;
else if (!fileInfo.Exists)
result = true;
else if (_CheckDFaceAndUpWriteDates && dateTimes.Any() && dateTimes.Max() > fileInfo.LastWriteTime)
result = true;
}
if (result)
SaveFaces(mappingFromItem.ResizedFileHolder, collection);
return result;
}
}