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; /// // List /// public class D_Face { public List 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 Serilog.ILogger? _Log; private readonly bool _OverrideForFaceImages; private readonly Configuration _Configuration; 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, bool overrideForFaceImages, string predictorModelName, bool propertiesChangedForFaces) { _ArgZero = argZero; _Configuration = configuration; _ImageCodecInfo = imageCodecInfo; _EncoderParameters = encoderParameters; _FileNameExtension = filenameExtension; _Log = Serilog.Log.ForContext(); AngleBracketCollection = new List(); _HiddenImageCodecInfo = hiddenImageCodecInfo; _OverrideForFaceImages = overrideForFaceImages; _HiddenEncoderParameters = hiddenEncoderParameters; _HiddenFileNameExtension = hiddenFileNameExtension; _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(), 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 PropertyItem GetPropertyItem(int id, string value) { PropertyItem result = (PropertyItem)_ConstructorInfo.Invoke(null); byte[] bytes = C_Resize.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, bool)> 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, bool save) in collection) { if (!save) continue; 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 GetFaces(string outputResolution, Shared.Models.Property property, MappingFromItem mappingFromItem, Dictionary outputResolutionToResize, List? locations) { if (_Log is null) throw new NullReferenceException(nameof(_Log)); if (_Configuration.NumberOfJitters is null) throw new NullReferenceException(nameof(_Configuration.NumberOfJitters)); if (_Configuration.NumberOfTimesToUpsample is null) throw new NullReferenceException(nameof(_Configuration.NumberOfTimesToUpsample)); List results = new(); FaceRecognitionDotNet.Image? unknownImage; 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 not null) { (int outputResolutionWidth, int outputResolutionHeight, int outputResolutionOrientation) = Resize.Models.Stateless.Methods.IResize.Get(outputResolution, outputResolutionToResize); List<(Location Location, FaceRecognitionDotNet.FaceEncoding? FaceEncoding, Dictionary? FaceParts)> collection; FaceRecognition faceRecognition = new(_Configuration.NumberOfJitters.Value, _Configuration.NumberOfTimesToUpsample.Value, _Model, _ModelParameter, _PredictorModel); collection = faceRecognition.GetCollection(unknownImage, locations, 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? 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(); } 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); } result = Path.Combine(AngleBracketCollection[0].Replace("<>", "()"), item.ImageFileHolder.NameWithoutExtension); if (!angleBracketCollectionAny) AngleBracketCollection.Clear(); return result; } #pragma warning restore CA1416 private static List> GetCollection(string outputResolution, List> collection, Dictionary outputResolutionToResize, List faces) { List> results = new(); string? json; Location? location; Rectangle? rectangle; List skip = new(); OutputResolution? outputResolutionCheck = null; (int outputResolutionWidth, int outputResolutionHeight, int outputResolutionOrientation) = Resize.Models.Stateless.Methods.IResize.Get(outputResolution, outputResolutionToResize); foreach (Shared.Models.Face face in faces) { if (face.Location is null || face.OutputResolution is null) continue; skip.Add(Shared.Models.Stateless.Methods.ILocation.GetNormalizedRectangle(face.Location, ILocation.Digits, face.OutputResolution)); } foreach (LocationContainer locationContainer in collection) { if (locationContainer.Directories is null) continue; if (skip.Contains(locationContainer.NormalizedRectangle)) continue; foreach (Shared.Models.Face face in faces) { if (face.Location is not null && face.OutputResolution is not null) continue; json = Metadata.Models.Stateless.Methods.IMetadata.GetOutputResolution(locationContainer.Directories); if (json is null) continue; outputResolutionCheck = JsonSerializer.Deserialize(json); if (outputResolutionCheck is null || outputResolutionCheck.Width != outputResolutionWidth || outputResolutionCheck.Height != outputResolutionHeight) continue; rectangle = Shared.Models.Stateless.Methods.ILocation.GetRectangle(ILocation.Digits, locationContainer.NormalizedRectangle, outputResolutionCheck); if (rectangle is null) continue; location = Shared.Models.Stateless.Methods.ILocation.GetLocation(outputResolutionHeight, rectangle.Value, outputResolutionWidth); if (location is null) continue; if (!results.Any(l => l.NormalizedRectangle == locationContainer.NormalizedRectangle)) results.Add(new(locationContainer.FromDistanceContent, locationContainer.File, locationContainer.PersonKey, locationContainer.Id, locationContainer.NormalizedRectangle, locationContainer.Directories, rectangle.Value, location)); } } if (results.Any()) outputResolutionCheck = null; return results; } public List GetFaces(string outputResolution, string dResultsFullGroupDirectory, List> subFileTuples, List parseExceptions, Shared.Models.Property property, MappingFromItem mappingFromItem, Dictionary outputResolutionToResize, List>? collection, List? mappingFromPhotoPrismCollection) { List? results; if (string.IsNullOrEmpty(dResultsFullGroupDirectory)) throw new NullReferenceException(nameof(dResultsFullGroupDirectory)); string? json; List? locations; string[] changesFrom = new string[] { nameof(A_Property), nameof(B_Metadata), nameof(C_Resize) }; List 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 (_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>(json); if (results is null) throw new NullReferenceException(nameof(results)); subFileTuples.Add(new Tuple(nameof(D_Face), fileInfo.LastWriteTime)); } catch (Exception) { results = null; parseExceptions.Add(nameof(D_Face)); } } List> locationContainers; if (results is null || collection is null) locationContainers = new(); else locationContainers = GetCollection(outputResolution, collection, outputResolutionToResize, results); if (mappingFromPhotoPrismCollection is null || results is null) locations = (from l in locationContainers where l is not null select l.Location).ToList(); else locations = Shared.Models.Stateless.Methods.ILocation.GetLocations(mappingFromPhotoPrismCollection, results, locationContainers); if (results is null || (locations is not null && locations.Any())) { results = GetFaces(outputResolution, property, mappingFromItem, outputResolutionToResize, locations); if (!results.Any()) File.Move(mappingFromItem.ResizedFileHolder.FullName, $"{mappingFromItem.ResizedFileHolder.FullName}.err"); else { bool updateDateWhenMatches = dateTimes.Any() && fileInfo.Exists && dateTimes.Max() > fileInfo.LastWriteTime; DateTime? dateTime = !updateDateWhenMatches ? null : dateTimes.Max(); json = JsonSerializer.Serialize(results, _WriteIndentedAndWhenWritingNull); if (Shared.Models.Stateless.Methods.IPath.WriteAllText(fileInfo.FullName, json, updateDateWhenMatches, compareBeforeWrite: true, updateToWhenMatches: dateTime)) { if (!_ForceFaceLastWriteTimeToCreationTime) subFileTuples.Add(new Tuple(nameof(D_Face), DateTime.Now)); else { File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime); fileInfo.Refresh(); subFileTuples.Add(new Tuple(nameof(D_Face), fileInfo.CreationTime)); } } } } return results; } public List<(Shared.Models.Face, FileInfo?, string, bool)> SaveFaces(string f, string dResultsFullGroupDirectory, List> subFileTuples, List parseExceptions, MappingFromItem mappingFromItem, string facesDirectory, List faces) { List<(Shared.Models.Face, FileInfo?, string, bool Save)> results = new(); bool save; FileInfo fileInfo; string deterministicHashCodeKey; string[] changesFrom = new string[] { nameof(A_Property), nameof(B_Metadata), nameof(C_Resize) }; List 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) { save = false; if (face.FaceEncoding is null || face.Location is null || face.OutputResolution is null) { results.Add(new(face, null, string.Empty, save)); continue; } deterministicHashCodeKey = Shared.Models.Stateless.Methods.IMapping.GetDeterministicHashCodeKey(mappingFromItem.Id, face.Location, ILocation.Digits, face.OutputResolution); fileInfo = new FileInfo(Path.Combine(facesDirectory, $"{deterministicHashCodeKey}{mappingFromItem.ImageFileHolder.ExtensionLowered}{_FileNameExtension}")); if (_OverrideForFaceImages) save = true; else if (!fileInfo.Exists) save = true; else if (_CheckDFaceAndUpWriteDates && dateTimes.Any() && dateTimes.Max() > fileInfo.LastWriteTime) save = true; results.Add(new(face, fileInfo, Path.Combine(facesDirectory, $"{deterministicHashCodeKey}{mappingFromItem.ImageFileHolder.ExtensionLowered}{_HiddenFileNameExtension}"), save)); } if (results.Any(l => l.Save)) SaveFaces(mappingFromItem.ResizedFileHolder, results); return results; } }