diff --git a/Azaion.Annotator/AnnotatorEventHandler.cs b/Azaion.Annotator/AnnotatorEventHandler.cs index c25920a..58c1845 100644 --- a/Azaion.Annotator/AnnotatorEventHandler.cs +++ b/Azaion.Annotator/AnnotatorEventHandler.cs @@ -226,8 +226,9 @@ public class AnnotatorEventHandler( mediaPlayer.Stop(); mainWindow.Title = $"Azaion Annotator - {mediaInfo.Name}"; mainWindow.BlinkHelp(HelpTexts.HelpTextsDict[HelpTextEnum.PauseForAnnotations]); - if (formState.CurrentMedia.MediaType == MediaTypes.Video) - mediaPlayer.Play(new Media(libVLC, mediaInfo.Path)); + mediaPlayer.Play(new Media(libVLC, mediaInfo.Path)); + if (formState.CurrentMedia.MediaType == MediaTypes.Image) + mediaPlayer.SetPause(true); } //SAVE: MANUAL diff --git a/Azaion.Common/DTO/Config/AIRecognitionConfig.cs b/Azaion.Common/DTO/Config/AIRecognitionConfig.cs index e4087cb..94394ed 100644 --- a/Azaion.Common/DTO/Config/AIRecognitionConfig.cs +++ b/Azaion.Common/DTO/Config/AIRecognitionConfig.cs @@ -5,14 +5,15 @@ namespace Azaion.Common.DTO.Config; [MessagePackObject] public class AIRecognitionConfig { - [Key(nameof(FramePeriodRecognition))] public int FramePeriodRecognition { get; set; } - [Key(nameof(FrameRecognitionSeconds))] public double FrameRecognitionSeconds { get; set; } - [Key(nameof(ProbabilityThreshold))] public double ProbabilityThreshold { get; set; } + [Key("f_pr")] public int FramePeriodRecognition { get; set; } + [Key("f_rs")] public double FrameRecognitionSeconds { get; set; } + [Key("pt")] public double ProbabilityThreshold { get; set; } - [Key(nameof(TrackingDistanceConfidence))] public double TrackingDistanceConfidence { get; set; } - [Key(nameof(TrackingProbabilityIncrease))] public double TrackingProbabilityIncrease { get; set; } - [Key(nameof(TrackingIntersectionThreshold))] public double TrackingIntersectionThreshold { get; set; } + [Key("t_dc")] public double TrackingDistanceConfidence { get; set; } + [Key("t_pi")] public double TrackingProbabilityIncrease { get; set; } + [Key("t_it")] public double TrackingIntersectionThreshold { get; set; } - [Key(nameof(Data))] public byte[] Data { get; set; } = null!; - [Key(nameof(Paths))] public List Paths { get; set; } = null!; + [Key("d")] public byte[] Data { get; set; } = null!; + [Key("p")] public List Paths { get; set; } = null!; + [Key("m_bs")] public int ModelBatchSize { get; set; } = 2; } \ No newline at end of file diff --git a/Azaion.Common/DTO/Config/AppConfig.cs b/Azaion.Common/DTO/Config/AppConfig.cs index 8c23323..049b1af 100644 --- a/Azaion.Common/DTO/Config/AppConfig.cs +++ b/Azaion.Common/DTO/Config/AppConfig.cs @@ -8,7 +8,9 @@ namespace Azaion.Common.DTO.Config; public class AppConfig { - public PythonConfig PythonConfig { get; set; } = null!; + public InferenceClientConfig InferenceClientConfig { get; set; } = null!; + + public GpsDeniedClientConfig GpsDeniedClientConfig { get; set; } = null!; public QueueConfig QueueConfig { get; set; } = null!; @@ -85,7 +87,8 @@ public class ConfigUpdater : IConfigUpdater //Save without sensitive info var publicConfig = new { - PythonConfig = config.PythonConfig, + InferenceClientConfig = config.InferenceClientConfig, + GpsDeniedClientConfig = config.GpsDeniedClientConfig, DirectoriesConfig = config.DirectoriesConfig, AnnotationConfig = config.AnnotationConfig, AIRecognitionConfig = config.AIRecognitionConfig, diff --git a/Azaion.Common/Services/FailsafeProducer.cs b/Azaion.Common/Services/FailsafeProducer.cs index 6a8bc90..c46fcc2 100644 --- a/Azaion.Common/Services/FailsafeProducer.cs +++ b/Azaion.Common/Services/FailsafeProducer.cs @@ -100,26 +100,40 @@ public class FailsafeAnnotationsProducer .ToListAsync(token: cancellationToken); var messages = new List(); + var badImages = new List(); foreach (var annotation in annotations) { - var image = await File.ReadAllBytesAsync(annotation.ImagePath, cancellationToken); - var annCreateMessage = new AnnotationCreatedMessage + try { - Name = annotation.Name, - OriginalMediaName = annotation.OriginalMediaName, - Time = annotation.Time, - CreatedRole = annotation.CreatedRole, - CreatedEmail = annotation.CreatedEmail, - CreatedDate = annotation.CreatedDate, - Status = annotation.AnnotationStatus, + var image = await File.ReadAllBytesAsync(annotation.ImagePath, cancellationToken); + var annCreateMessage = new AnnotationCreatedMessage + { + Name = annotation.Name, + OriginalMediaName = annotation.OriginalMediaName, + Time = annotation.Time, + CreatedRole = annotation.CreatedRole, + CreatedEmail = annotation.CreatedEmail, + CreatedDate = annotation.CreatedDate, + Status = annotation.AnnotationStatus, - ImageExtension = annotation.ImageExtension, - Image = image, - Detections = JsonConvert.SerializeObject(annotation.Detections), - Source = annotation.Source, + ImageExtension = annotation.ImageExtension, + Image = image, + Detections = JsonConvert.SerializeObject(annotation.Detections), + Source = annotation.Source, + }; + messages.Add(annCreateMessage); + } + catch (Exception e) + { + _logger.LogError(e, e.Message); + badImages.Add(annotation.Name); + } + } - }; - messages.Add(annCreateMessage); + if (badImages.Any()) + { + await db.AnnotationsQueue.Where(x => badImages.Contains(x.Name)).DeleteAsync(token: cancellationToken); + _dbFactory.SaveToDisk(); } return messages; }); diff --git a/Azaion.Common/Services/InferenceService.cs b/Azaion.Common/Services/InferenceService.cs index 10f4eed..60482ba 100644 --- a/Azaion.Common/Services/InferenceService.cs +++ b/Azaion.Common/Services/InferenceService.cs @@ -2,12 +2,12 @@ using Azaion.Common.Database; using Azaion.Common.DTO.Config; using Azaion.CommonSecurity; -using Azaion.CommonSecurity.DTO; using Azaion.CommonSecurity.DTO.Commands; +using Azaion.CommonSecurity.Services; +using MessagePack; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using NetMQ; -using NetMQ.Sockets; namespace Azaion.Common.Services; @@ -16,30 +16,29 @@ public interface IInferenceService Task RunInference(List mediaPaths, Func processAnnotation, CancellationToken ct = default); } -public class PythonInferenceService(ILogger logger, IOptions pythonConfigOptions, IOptions aiConfigOptions) : IInferenceService +public class InferenceService(ILogger logger, [FromKeyedServices(SecurityConstants.EXTERNAL_INFERENCE_PATH)] IExternalClient externalClient, IOptions aiConfigOptions) : IInferenceService { public async Task RunInference(List mediaPaths, Func processAnnotation, CancellationToken ct = default) { - var pythonConfig = pythonConfigOptions.Value; var aiConfig = aiConfigOptions.Value; - using var dealer = new DealerSocket(); - var clientId = Guid.NewGuid(); - dealer.Options.Identity = Encoding.UTF8.GetBytes(clientId.ToString("N")); - dealer.Connect($"tcp://{pythonConfig.ZeroMqHost}:{pythonConfig.ZeroMqPort}"); - aiConfig.Paths = mediaPaths; - dealer.SendFrame(RemoteCommand.Serialize(CommandType.Inference, aiConfig)); + externalClient.Send(RemoteCommand.Create(CommandType.Inference, aiConfig)); while (!ct.IsCancellationRequested) { try { - var annotationStream = dealer.Get(bytes => bytes.Length == 4 && Encoding.UTF8.GetString(bytes) == "DONE", ct: ct); - if (annotationStream == null) - break; + var bytes = externalClient.GetBytes(ct: ct); + if (bytes == null) + throw new Exception("Can't get bytes from inference client"); - await processAnnotation(annotationStream); + if (bytes.Length == 4 && Encoding.UTF8.GetString(bytes) == "DONE") + return; + + var annotationImage = MessagePackSerializer.Deserialize(bytes, cancellationToken: ct); + + await processAnnotation(annotationImage); } catch (Exception e) { diff --git a/Azaion.CommonSecurity/Azaion.CommonSecurity.csproj b/Azaion.CommonSecurity/Azaion.CommonSecurity.csproj index 64162ea..4e4f70d 100644 --- a/Azaion.CommonSecurity/Azaion.CommonSecurity.csproj +++ b/Azaion.CommonSecurity/Azaion.CommonSecurity.csproj @@ -9,6 +9,8 @@ + + diff --git a/Azaion.CommonSecurity/DTO/Commands/RemoteCommand.cs b/Azaion.CommonSecurity/DTO/Commands/RemoteCommand.cs index 23cf730..4d22bfc 100644 --- a/Azaion.CommonSecurity/DTO/Commands/RemoteCommand.cs +++ b/Azaion.CommonSecurity/DTO/Commands/RemoteCommand.cs @@ -11,10 +11,10 @@ public class RemoteCommand(CommandType commandType, byte[]? data = null) [Key("Data")] public byte[]? Data { get; set; } = data; - public static byte[] Serialize(CommandType commandType, T data) where T : class + public static RemoteCommand Create(CommandType commandType, T data) where T : class { var dataBytes = MessagePackSerializer.Serialize(data); - return MessagePackSerializer.Serialize(new RemoteCommand(commandType, dataBytes )); + return new RemoteCommand(commandType, dataBytes); } } diff --git a/Azaion.CommonSecurity/DTO/PythonConfig.cs b/Azaion.CommonSecurity/DTO/ExternalClientsConfig.cs similarity index 63% rename from Azaion.CommonSecurity/DTO/PythonConfig.cs rename to Azaion.CommonSecurity/DTO/ExternalClientsConfig.cs index 6f339ff..233a620 100644 --- a/Azaion.CommonSecurity/DTO/PythonConfig.cs +++ b/Azaion.CommonSecurity/DTO/ExternalClientsConfig.cs @@ -1,11 +1,16 @@ namespace Azaion.CommonSecurity.DTO; -public class PythonConfig +public abstract class ExternalClientConfig { public string ZeroMqHost { get; set; } = ""; public int ZeroMqPort { get; set; } public double OneTryTimeoutSeconds { get; set; } public int RetryCount {get;set;} +} +public class InferenceClientConfig : ExternalClientConfig +{ public string ResourcesFolder { get; set; } = ""; } + +public class GpsDeniedClientConfig : ExternalClientConfig; \ No newline at end of file diff --git a/Azaion.CommonSecurity/DTO/SecureAppConfig.cs b/Azaion.CommonSecurity/DTO/SecureAppConfig.cs index 3c16cd3..80ea07f 100644 --- a/Azaion.CommonSecurity/DTO/SecureAppConfig.cs +++ b/Azaion.CommonSecurity/DTO/SecureAppConfig.cs @@ -2,5 +2,6 @@ public class SecureAppConfig { - public PythonConfig PythonConfig { get; set; } = null!; + public InferenceClientConfig InferenceClientConfig { get; set; } = null!; + public GpsDeniedClientConfig GpsDeniedClientConfig { get; set; } = null!; } \ No newline at end of file diff --git a/Azaion.CommonSecurity/SecurityConstants.cs b/Azaion.CommonSecurity/SecurityConstants.cs index 944116f..75d06cc 100644 --- a/Azaion.CommonSecurity/SecurityConstants.cs +++ b/Azaion.CommonSecurity/SecurityConstants.cs @@ -1,4 +1,6 @@ -namespace Azaion.CommonSecurity; +using Azaion.CommonSecurity.DTO; + +namespace Azaion.CommonSecurity; public class SecurityConstants { @@ -6,13 +8,36 @@ public class SecurityConstants public const string DUMMY_DIR = "dummy"; - #region PythonConfig - public const string AZAION_INFERENCE_PATH = "azaion-inference.exe"; + #region ExternalClientsConfig + public const string EXTERNAL_INFERENCE_PATH = "azaion-inference.exe"; + public const string EXTERNAL_GPS_DENIED_PATH = "image-matcher.exe"; + + public const string DEFAULT_ZMQ_INFERENCE_HOST = "127.0.0.1"; + public const int DEFAULT_ZMQ_INFERENCE_PORT = 5227; + + public const string DEFAULT_ZMQ_GPS_DENIED_HOST = "127.0.0.1"; + public const int DEFAULT_ZMQ_GPS_DENIED_PORT = 5227; - public const string DEFAULT_ZMQ_HOST = "127.0.0.1"; - public const int DEFAULT_ZMQ_PORT = 5127; public const int DEFAULT_RETRY_COUNT = 25; public const int DEFAULT_TIMEOUT_SECONDS = 5; - #endregion PythonConfig + public static readonly SecureAppConfig DefaultSecureAppConfig = new() + { + InferenceClientConfig = new InferenceClientConfig + { + ZeroMqHost = DEFAULT_ZMQ_INFERENCE_HOST, + ZeroMqPort = DEFAULT_ZMQ_INFERENCE_PORT, + OneTryTimeoutSeconds = DEFAULT_TIMEOUT_SECONDS, + RetryCount = DEFAULT_RETRY_COUNT, + ResourcesFolder = "" + }, + GpsDeniedClientConfig = new GpsDeniedClientConfig + { + ZeroMqHost = DEFAULT_ZMQ_GPS_DENIED_HOST, + ZeroMqPort = DEFAULT_ZMQ_GPS_DENIED_PORT, + OneTryTimeoutSeconds = DEFAULT_TIMEOUT_SECONDS, + RetryCount = DEFAULT_RETRY_COUNT, + } + }; + #endregion ExternalClientsConfig } \ No newline at end of file diff --git a/Azaion.CommonSecurity/Services/AuthProvider.cs b/Azaion.CommonSecurity/Services/AuthProvider.cs new file mode 100644 index 0000000..60dcb72 --- /dev/null +++ b/Azaion.CommonSecurity/Services/AuthProvider.cs @@ -0,0 +1,26 @@ +using Azaion.CommonSecurity.DTO; +using Azaion.CommonSecurity.DTO.Commands; +using Microsoft.Extensions.DependencyInjection; + +namespace Azaion.CommonSecurity.Services; + +public interface IAuthProvider +{ + void Login(ApiCredentials credentials); + User CurrentUser { get; } +} + +public class AuthProvider([FromKeyedServices(SecurityConstants.EXTERNAL_INFERENCE_PATH)] IExternalClient externalClient) : IAuthProvider +{ + public User CurrentUser { get; private set; } = null!; + + public void Login(ApiCredentials credentials) + { + externalClient.Send(RemoteCommand.Create(CommandType.Login, credentials)); + var user = externalClient.Get(); + if (user == null) + throw new Exception("Can't get user from Auth provider"); + + CurrentUser = user; + } +} \ No newline at end of file diff --git a/Azaion.CommonSecurity/Services/ExternalClient.cs b/Azaion.CommonSecurity/Services/ExternalClient.cs new file mode 100644 index 0000000..3aca2ba --- /dev/null +++ b/Azaion.CommonSecurity/Services/ExternalClient.cs @@ -0,0 +1,112 @@ +using System.Diagnostics; +using System.Text; +using Azaion.CommonSecurity.DTO; +using Azaion.CommonSecurity.DTO.Commands; +using MessagePack; +using Microsoft.Extensions.Options; +using NetMQ; +using NetMQ.Sockets; + +namespace Azaion.CommonSecurity.Services; + +public interface IExternalClient +{ + void Stop(); + + void Send(RemoteCommand create); + T? Get(int retries = 24, int tryTimeoutSeconds = 5, CancellationToken ct = default) where T : class; + byte[]? GetBytes(int retries = 24, int tryTimeoutSeconds = 5, CancellationToken ct = default); +} + +public abstract class BaseZeroMqExternalClient : IExternalClient +{ + private readonly DealerSocket _dealer = new(); + private readonly Guid _clientId = Guid.NewGuid(); + + private readonly ExternalClientConfig _externalClientConfig; + + protected abstract string ClientPath { get; } + + protected BaseZeroMqExternalClient(ExternalClientConfig config) + { + _externalClientConfig = config; + Start(); + } + + private void Start() + { + try + { + using var process = new Process(); + process.StartInfo = new ProcessStartInfo + { + FileName = ClientPath + //Arguments = $"-e {credentials.Email} -p {credentials.Password} -f {apiConfig.ResourcesFolder}", + //RedirectStandardOutput = true, + //RedirectStandardError = true, + //CreateNoWindow = true + }; + + process.OutputDataReceived += (_, e) => { if (e.Data != null) Console.WriteLine(e.Data); }; + process.ErrorDataReceived += (_, e) => { if (e.Data != null) Console.WriteLine(e.Data); }; + process.Start(); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + + _dealer.Options.Identity = Encoding.UTF8.GetBytes(_clientId.ToString("N")); + _dealer.Connect($"tcp://{_externalClientConfig.ZeroMqHost}:{_externalClientConfig.ZeroMqPort}"); + } + + public void Stop() + { + if (!_dealer.IsDisposed) + { + _dealer.SendFrame(MessagePackSerializer.Serialize(new RemoteCommand(CommandType.Exit))); + _dealer.Close(); + } + } + + public void Send(RemoteCommand command) + { + _dealer.SendFrame(MessagePackSerializer.Serialize(command)); + } + + public T? Get(int retries = 24, int tryTimeoutSeconds = 5, CancellationToken ct = default) where T : class + { + var bytes = GetBytes(retries, tryTimeoutSeconds, ct); + return bytes != null ? MessagePackSerializer.Deserialize(bytes, cancellationToken: ct) : null; + } + + public byte[]? GetBytes(int retries = 24, int tryTimeoutSeconds = 5, CancellationToken ct = default) + { + var tryNum = 0; + while (!ct.IsCancellationRequested && tryNum++ < retries) + { + if (!_dealer.TryReceiveFrameBytes(TimeSpan.FromSeconds(tryTimeoutSeconds), out var bytes)) + continue; + + return bytes; + } + + if (!ct.IsCancellationRequested) + throw new Exception($"Unable to get bytes after {tryNum} retries, {tryTimeoutSeconds} seconds each"); + + return null; + } +} + +public class InferenceExternalClient(IOptions inferenceClientConfig) + : BaseZeroMqExternalClient(inferenceClientConfig.Value) +{ + protected override string ClientPath => SecurityConstants.EXTERNAL_INFERENCE_PATH; +} + +public class GpsDeniedExternalClient(IOptions gpsDeniedClientConfig) + : BaseZeroMqExternalClient(gpsDeniedClientConfig.Value) +{ + protected override string ClientPath => SecurityConstants.EXTERNAL_GPS_DENIED_PATH; +} diff --git a/Azaion.CommonSecurity/Services/PythonResourceLoader.cs b/Azaion.CommonSecurity/Services/PythonResourceLoader.cs deleted file mode 100644 index b76faa3..0000000 --- a/Azaion.CommonSecurity/Services/PythonResourceLoader.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System.Diagnostics; -using System.Text; -using Azaion.CommonSecurity.DTO; -using Azaion.CommonSecurity.DTO.Commands; -using MessagePack; -using NetMQ; -using NetMQ.Sockets; - -namespace Azaion.CommonSecurity.Services; - -public interface IResourceLoader -{ - MemoryStream LoadFileFromPython(string fileName, string? folder = null); - void StopPython(); -} - -public interface IAuthProvider -{ - User CurrentUser { get; } -} - -public class PythonResourceLoader : IResourceLoader, IAuthProvider -{ - private readonly DealerSocket _dealer = new(); - private readonly Guid _clientId = Guid.NewGuid(); - - public User CurrentUser { get; set; } = null!; - - public PythonResourceLoader(PythonConfig config) - { - StartPython(); - _dealer.Options.Identity = Encoding.UTF8.GetBytes(_clientId.ToString("N")); - _dealer.Connect($"tcp://{config.ZeroMqHost}:{config.ZeroMqPort}"); - } - - private void StartPython() - { - try - { - using var process = new Process(); - process.StartInfo = new ProcessStartInfo - { - FileName = SecurityConstants.AZAION_INFERENCE_PATH, - //Arguments = $"-e {credentials.Email} -p {credentials.Password} -f {apiConfig.ResourcesFolder}", - //UseShellExecute = false, - //RedirectStandardOutput = true, - // RedirectStandardError = true, - //CreateNoWindow = true - }; - - process.OutputDataReceived += (_, e) => { if (e.Data != null) Console.WriteLine(e.Data); }; - process.ErrorDataReceived += (_, e) => { if (e.Data != null) Console.WriteLine(e.Data); }; - process.Start(); - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } - } - - public void Login(ApiCredentials credentials) - { - _dealer.SendFrame(RemoteCommand.Serialize(CommandType.Login, credentials)); - var user = _dealer.Get(); - if (user == null) - throw new Exception("Can't get user from Auth provider"); - - CurrentUser = user; - } - - public void StopPython() - { - if (!_dealer.IsDisposed) - { - _dealer.SendFrame(MessagePackSerializer.Serialize(new RemoteCommand(CommandType.Exit))); - _dealer.Close(); - } - } - - public MemoryStream LoadFileFromPython(string fileName, string? folder = null) - { - try - { - _dealer.SendFrame(RemoteCommand.Serialize(CommandType.Load, new LoadFileData(fileName, folder))); - - if (!_dealer.TryReceiveFrameBytes(TimeSpan.FromSeconds(300), out var bytes)) - throw new Exception($"Unable to receive {fileName}"); - - return new MemoryStream(bytes); - } - catch (Exception ex) - { - throw new Exception($"Failed to load fil0e '{fileName}': {ex.Message}", ex); - } - } - -} diff --git a/Azaion.CommonSecurity/Services/ResourceLoader.cs b/Azaion.CommonSecurity/Services/ResourceLoader.cs new file mode 100644 index 0000000..860bced --- /dev/null +++ b/Azaion.CommonSecurity/Services/ResourceLoader.cs @@ -0,0 +1,22 @@ +using Azaion.CommonSecurity.DTO.Commands; +using Microsoft.Extensions.DependencyInjection; + +namespace Azaion.CommonSecurity.Services; + +public interface IResourceLoader +{ + MemoryStream LoadFile(string fileName, string? folder = null); +} + +public class ResourceLoader([FromKeyedServices(SecurityConstants.EXTERNAL_INFERENCE_PATH)] IExternalClient externalClient) : IResourceLoader +{ + public MemoryStream LoadFile(string fileName, string? folder = null) + { + externalClient.Send(RemoteCommand.Create(CommandType.Load, new LoadFileData(fileName, folder))); + var bytes = externalClient.GetBytes(); + if (bytes == null) + throw new Exception($"Unable to receive {fileName}"); + + return new MemoryStream(bytes); + } +} diff --git a/Azaion.CommonSecurity/ZeroMQExtensions.cs b/Azaion.CommonSecurity/ZeroMQExtensions.cs deleted file mode 100644 index 51cff4a..0000000 --- a/Azaion.CommonSecurity/ZeroMQExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using MessagePack; -using NetMQ; -using NetMQ.Sockets; - -namespace Azaion.CommonSecurity; - -public static class ZeroMqExtensions -{ - public static T? Get(this DealerSocket dealer, Func? shouldInterceptFn = null, int retries = 24, int tryTimeoutSeconds = 5, CancellationToken ct = default) where T : class - { - var tryNum = 0; - while (!ct.IsCancellationRequested && tryNum++ < retries) - { - if (!dealer.TryReceiveFrameBytes(TimeSpan.FromSeconds(tryTimeoutSeconds), out var bytes)) - continue; - - if (shouldInterceptFn != null && shouldInterceptFn(bytes)) - return null; - - return MessagePackSerializer.Deserialize(bytes); - } - - if (!ct.IsCancellationRequested) - throw new Exception($"Unable to get {typeof(T).Name} after {tryNum} retries, {tryTimeoutSeconds} seconds each"); - - return null; - } -} \ No newline at end of file diff --git a/Azaion.Inference/ai_config.pxd b/Azaion.Inference/ai_config.pxd index b0d32aa..b5b19c5 100644 --- a/Azaion.Inference/ai_config.pxd +++ b/Azaion.Inference/ai_config.pxd @@ -9,6 +9,7 @@ cdef class AIRecognitionConfig: cdef public bytes file_data cdef public list[str] paths + cdef public int model_batch_size @staticmethod cdef from_msgpack(bytes data) \ No newline at end of file diff --git a/Azaion.Inference/ai_config.pyx b/Azaion.Inference/ai_config.pyx index d9667ae..28af40e 100644 --- a/Azaion.Inference/ai_config.pyx +++ b/Azaion.Inference/ai_config.pyx @@ -11,7 +11,8 @@ cdef class AIRecognitionConfig: tracking_intersection_threshold, file_data, - paths + paths, + model_batch_size ): self.frame_period_recognition = frame_period_recognition self.frame_recognition_seconds = frame_recognition_seconds @@ -23,26 +24,29 @@ cdef class AIRecognitionConfig: self.file_data = file_data self.paths = paths + self.model_batch_size = model_batch_size def __str__(self): return (f'frame_seconds : {self.frame_recognition_seconds}, distance_confidence : {self.tracking_distance_confidence}, ' f'probability_increase : {self.tracking_probability_increase}, ' f'intersection_threshold : {self.tracking_intersection_threshold}, ' f'frame_period_recognition : {self.frame_period_recognition}, ' - f'paths: {self.paths}') + f'paths: {self.paths}, ' + f'model_batch_size: {self.model_batch_size}') @staticmethod cdef from_msgpack(bytes data): unpacked = unpackb(data, strict_map_key=False) return AIRecognitionConfig( - unpacked.get("FramePeriodRecognition", 0), - unpacked.get("FrameRecognitionSeconds", 0.0), - unpacked.get("ProbabilityThreshold", 0.0), + unpacked.get("f_pr", 0), + unpacked.get("f_rs", 0.0), + unpacked.get("pt", 0.0), - unpacked.get("TrackingDistanceConfidence", 0.0), - unpacked.get("TrackingProbabilityIncrease", 0.0), - unpacked.get("TrackingIntersectionThreshold", 0.0), + unpacked.get("t_dc", 0.0), + unpacked.get("t_pi", 0.0), + unpacked.get("t_it", 0.0), - unpacked.get("Data", b''), - unpacked.get("Paths", []), + unpacked.get("d", b''), + unpacked.get("p", []), + unpacked.get("m_bs") ) \ No newline at end of file diff --git a/Azaion.Inference/config.yaml b/Azaion.Inference/config.yaml index f03cd9e..20a1884 100644 --- a/Azaion.Inference/config.yaml +++ b/Azaion.Inference/config.yaml @@ -1 +1 @@ -zmq_port: 5128 \ No newline at end of file +zmq_port: 5127 \ No newline at end of file diff --git a/Azaion.Inference/constants.pxd b/Azaion.Inference/constants.pxd index ef9abf3..66b3721 100644 --- a/Azaion.Inference/constants.pxd +++ b/Azaion.Inference/constants.pxd @@ -10,7 +10,6 @@ cdef str AI_MODEL_FILE_BIG # AI Model file (BIG part) cdef str AI_MODEL_FILE_SMALL # AI Model file (small part) cdef bytes DONE_SIGNAL -cdef int MODEL_BATCH_SIZE cdef log(str log_message, bytes client_id=*) \ No newline at end of file diff --git a/Azaion.Inference/constants.pyx b/Azaion.Inference/constants.pyx index 952956b..1988c2e 100644 --- a/Azaion.Inference/constants.pyx +++ b/Azaion.Inference/constants.pyx @@ -12,7 +12,6 @@ cdef str AI_MODEL_FILE_BIG = "azaion.onnx.big" cdef str AI_MODEL_FILE_SMALL = "azaion.onnx.small" cdef bytes DONE_SIGNAL = b"DONE" -cdef int MODEL_BATCH_SIZE = 4 cdef log(str log_message, bytes client_id=None): local_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) diff --git a/Azaion.Inference/inference.pyx b/Azaion.Inference/inference.pyx index ff5eba7..5f5120c 100644 --- a/Azaion.Inference/inference.pyx +++ b/Azaion.Inference/inference.pyx @@ -132,7 +132,7 @@ cdef class Inference: images.append(m) # images first, it's faster if len(images) > 0: - for chunk in self.split_list_extend(images, constants.MODEL_BATCH_SIZE): + for chunk in self.split_list_extend(images, ai_config.model_batch_size): print(f'run inference on {" ".join(chunk)}...') self._process_images(cmd, ai_config, chunk) if len(videos) > 0: @@ -158,7 +158,7 @@ cdef class Inference: batch_frames.append(frame) batch_timestamps.append(int(v_input.get(cv2.CAP_PROP_POS_MSEC))) - if len(batch_frames) == constants.MODEL_BATCH_SIZE: + if len(batch_frames) == ai_config.model_batch_size: input_blob = self.preprocess(batch_frames) outputs = self.session.run(None, {self.model_input: input_blob}) list_detections = self.postprocess(outputs, ai_config) diff --git a/Azaion.Inference/requirements.txt b/Azaion.Inference/requirements.txt index 7526e44..4d3dcec 100644 --- a/Azaion.Inference/requirements.txt +++ b/Azaion.Inference/requirements.txt @@ -7,4 +7,5 @@ psutil msgpack pyjwt zmq -requests \ No newline at end of file +requests +pyyaml \ No newline at end of file diff --git a/Azaion.Suite/App.xaml.cs b/Azaion.Suite/App.xaml.cs index 9dfc9d7..cb0474f 100644 --- a/Azaion.Suite/App.xaml.cs +++ b/Azaion.Suite/App.xaml.cs @@ -33,7 +33,10 @@ public partial class App private IMediator _mediator = null!; private FormState _formState = null!; - private PythonResourceLoader _resourceLoader = null!; + private InferenceExternalClient _inferenceClient = null!; + private IResourceLoader _resourceLoader = null!; + private IAuthProvider _authProvider = null!; + private Stream _securedConfig = null!; private static readonly Guid KeyPressTaskId = Guid.NewGuid(); @@ -55,41 +58,44 @@ public partial class App "Azaion.Dataset" ]; - private static PythonConfig ReadPythonConfig() + private static SecureAppConfig ReadSecureAppConfig() { try { if (!File.Exists(SecurityConstants.CONFIG_PATH)) throw new FileNotFoundException(SecurityConstants.CONFIG_PATH); var configStr = File.ReadAllText(SecurityConstants.CONFIG_PATH); - return JsonConvert.DeserializeObject(configStr)!.PythonConfig; + var config = JsonConvert.DeserializeObject(configStr); + + return config ?? SecurityConstants.DefaultSecureAppConfig; } catch (Exception e) { Console.WriteLine(e); - return new PythonConfig - { - ZeroMqHost = SecurityConstants.DEFAULT_ZMQ_HOST, - ZeroMqPort = SecurityConstants.DEFAULT_ZMQ_PORT, - OneTryTimeoutSeconds = SecurityConstants.DEFAULT_TIMEOUT_SECONDS, - RetryCount = SecurityConstants.DEFAULT_RETRY_COUNT, - - ResourcesFolder = "" - }; + return SecurityConstants.DefaultSecureAppConfig; } } private void StartLogin() { new ConfigUpdater().CheckConfig(); + var secureAppConfig = ReadSecureAppConfig(); + _inferenceClient = new InferenceExternalClient(new OptionsWrapper(secureAppConfig.InferenceClientConfig)); + _resourceLoader = new ResourceLoader(_inferenceClient); + _authProvider = new AuthProvider(_inferenceClient); + var login = new Login(); - var pythonConfig = ReadPythonConfig(); - _resourceLoader = new PythonResourceLoader(pythonConfig); + login.Closed += (sender, args) => + { + if (!login.MainSuiteOpened) + _inferenceClient.Stop(); + }; + login.CredentialsEntered += (_, credentials) => { - credentials.Folder = pythonConfig.ResourcesFolder; - _resourceLoader.Login(credentials); - _securedConfig = _resourceLoader.LoadFileFromPython("secured-config.json"); + credentials.Folder = secureAppConfig.InferenceClientConfig.ResourcesFolder; + _authProvider.Login(credentials); + _securedConfig = _resourceLoader.LoadFile("secured-config.json"); AppDomain.CurrentDomain.AssemblyResolve += (_, a) => { @@ -98,7 +104,7 @@ public partial class App { try { - var stream = _resourceLoader.LoadFileFromPython($"{assemblyName}.dll"); + var stream = _resourceLoader.LoadFile($"{assemblyName}.dll"); return Assembly.Load(stream.ToArray()); } catch (Exception e) @@ -147,18 +153,25 @@ public partial class App services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(_resourceLoader); - services.AddSingleton(_resourceLoader); - services.AddSingleton(); - services.Configure(context.Configuration); - services.ConfigureSection(context.Configuration); services.ConfigureSection(context.Configuration); services.ConfigureSection(context.Configuration); services.ConfigureSection(context.Configuration); services.ConfigureSection(context.Configuration); services.ConfigureSection(context.Configuration); + #region External Services + + services.ConfigureSection(context.Configuration); + services.ConfigureSection(context.Configuration); + services.AddKeyedSingleton(SecurityConstants.EXTERNAL_INFERENCE_PATH, _inferenceClient); + services.AddKeyedSingleton(SecurityConstants.EXTERNAL_GPS_DENIED_PATH); + services.AddSingleton(_resourceLoader); + services.AddSingleton(_authProvider); + services.AddSingleton(); + + #endregion + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/Azaion.Suite/Azaion.Suite.csproj b/Azaion.Suite/Azaion.Suite.csproj index 75f80f0..48eb08b 100644 --- a/Azaion.Suite/Azaion.Suite.csproj +++ b/Azaion.Suite/Azaion.Suite.csproj @@ -60,8 +60,8 @@ - - + + diff --git a/Azaion.Suite/Login.xaml.cs b/Azaion.Suite/Login.xaml.cs index f77b5ba..6da38db 100644 --- a/Azaion.Suite/Login.xaml.cs +++ b/Azaion.Suite/Login.xaml.cs @@ -8,6 +8,8 @@ namespace Azaion.Suite; public partial class Login { + public bool MainSuiteOpened { get; set; } = false; + public Login() { InitializeComponent(); @@ -20,6 +22,7 @@ public partial class Login LoginBtn.Cursor = Cursors.Wait; Cursor = Cursors.Wait; CredentialsEntered?.Invoke(this, new ApiCredentials(TbEmail.Text, TbPassword.Password)); + MainSuiteOpened = true; Close(); } diff --git a/Azaion.Suite/MainSuite.xaml.cs b/Azaion.Suite/MainSuite.xaml.cs index ac75441..e0b28ee 100644 --- a/Azaion.Suite/MainSuite.xaml.cs +++ b/Azaion.Suite/MainSuite.xaml.cs @@ -25,6 +25,7 @@ public partial class MainSuite private readonly IDbFactory _dbFactory; private readonly Dictionary _openedWindows = new(); private readonly IResourceLoader _resourceLoader; + private readonly IEnumerable _externalClients; private static readonly Guid SaveConfigTaskId = Guid.NewGuid(); public MainSuite(IOptions appConfig, @@ -33,7 +34,8 @@ public partial class MainSuite IServiceProvider sp, IGalleryService galleryService, IDbFactory dbFactory, - IResourceLoader resourceLoader) + IResourceLoader resourceLoader, + IEnumerable externalClients) { _configUpdater = configUpdater; _modules = modules; @@ -41,6 +43,7 @@ public partial class MainSuite _galleryService = galleryService; _dbFactory = dbFactory; _resourceLoader = resourceLoader; + _externalClients = externalClients; _appConfig = appConfig.Value; InitializeComponent(); Loaded += OnLoaded; @@ -111,10 +114,13 @@ public partial class MainSuite _openedWindows[module.WindowEnum] = window; window.Closed += (_, _) => { - _resourceLoader.StopPython(); _openedWindows.Remove(module.WindowEnum); - if (!_openedWindows.Any()) - Close(); + if (_openedWindows.Any()) + return; + + foreach (var client in _externalClients) + client.Stop(); + Close(); }; window.Show(); window.Activate(); diff --git a/Azaion.Suite/config.json b/Azaion.Suite/config.json index a7d16b8..59c158e 100644 --- a/Azaion.Suite/config.json +++ b/Azaion.Suite/config.json @@ -1,11 +1,17 @@ { - "PythonConfig": { + "InferenceClientConfig": { "ZeroMqHost": "127.0.0.1", "ZeroMqPort": 5127, "RetryCount": 25, "TimeoutSeconds": 5, "ResourcesFolder": "stage" }, + "GpsDeniedClientConfig": { + "ZeroMqHost": "127.0.0.1", + "ZeroMqPort": 5227, + "RetryCount": 25, + "TimeoutSeconds": 5 + }, "DirectoriesConfig": { "VideosDirectory": "E:\\Azaion6", "LabelsDirectory": "E:\\labels", @@ -44,7 +50,9 @@ "TrackingDistanceConfidence": 0.15, "TrackingProbabilityIncrease": 15.0, - "TrackingIntersectionThreshold": 0.8 + "TrackingIntersectionThreshold": 0.8, + + "ModelBatchSize": 2 }, "ThumbnailConfig": { "Size": "240,135", "Border": 10 }, "MapConfig": diff --git a/Azaion.Suite/config.production.json b/Azaion.Suite/config.production.json index 96c92dc..c242cec 100644 --- a/Azaion.Suite/config.production.json +++ b/Azaion.Suite/config.production.json @@ -1,8 +1,15 @@ { - "PythonConfig": { + "InferenceClientConfig": { "ZeroMqHost": "127.0.0.1", "ZeroMqPort": 5131, "RetryCount": 25, + "TimeoutSeconds": 5, + "ResourcesFolder": "stage" + }, + "GpsDeniedClientConfig": { + "ZeroMqHost": "127.0.0.1", + "ZeroMqPort": 5231, + "RetryCount": 25, "TimeoutSeconds": 5 }, "DirectoriesConfig": {