refactor external clients

put model batch size as parameter in config
This commit is contained in:
Alex Bezdieniezhnykh
2025-03-24 00:33:41 +02:00
parent 32f9de3c71
commit 6429ad62c2
28 changed files with 352 additions and 226 deletions
+3 -2
View File
@@ -226,8 +226,9 @@ public class AnnotatorEventHandler(
mediaPlayer.Stop(); mediaPlayer.Stop();
mainWindow.Title = $"Azaion Annotator - {mediaInfo.Name}"; mainWindow.Title = $"Azaion Annotator - {mediaInfo.Name}";
mainWindow.BlinkHelp(HelpTexts.HelpTextsDict[HelpTextEnum.PauseForAnnotations]); 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 //SAVE: MANUAL
@@ -5,14 +5,15 @@ namespace Azaion.Common.DTO.Config;
[MessagePackObject] [MessagePackObject]
public class AIRecognitionConfig public class AIRecognitionConfig
{ {
[Key(nameof(FramePeriodRecognition))] public int FramePeriodRecognition { get; set; } [Key("f_pr")] public int FramePeriodRecognition { get; set; }
[Key(nameof(FrameRecognitionSeconds))] public double FrameRecognitionSeconds { get; set; } [Key("f_rs")] public double FrameRecognitionSeconds { get; set; }
[Key(nameof(ProbabilityThreshold))] public double ProbabilityThreshold { get; set; } [Key("pt")] public double ProbabilityThreshold { get; set; }
[Key(nameof(TrackingDistanceConfidence))] public double TrackingDistanceConfidence { get; set; } [Key("t_dc")] public double TrackingDistanceConfidence { get; set; }
[Key(nameof(TrackingProbabilityIncrease))] public double TrackingProbabilityIncrease { get; set; } [Key("t_pi")] public double TrackingProbabilityIncrease { get; set; }
[Key(nameof(TrackingIntersectionThreshold))] public double TrackingIntersectionThreshold { get; set; } [Key("t_it")] public double TrackingIntersectionThreshold { get; set; }
[Key(nameof(Data))] public byte[] Data { get; set; } = null!; [Key("d")] public byte[] Data { get; set; } = null!;
[Key(nameof(Paths))] public List<string> Paths { get; set; } = null!; [Key("p")] public List<string> Paths { get; set; } = null!;
[Key("m_bs")] public int ModelBatchSize { get; set; } = 2;
} }
+5 -2
View File
@@ -8,7 +8,9 @@ namespace Azaion.Common.DTO.Config;
public class AppConfig 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!; public QueueConfig QueueConfig { get; set; } = null!;
@@ -85,7 +87,8 @@ public class ConfigUpdater : IConfigUpdater
//Save without sensitive info //Save without sensitive info
var publicConfig = new var publicConfig = new
{ {
PythonConfig = config.PythonConfig, InferenceClientConfig = config.InferenceClientConfig,
GpsDeniedClientConfig = config.GpsDeniedClientConfig,
DirectoriesConfig = config.DirectoriesConfig, DirectoriesConfig = config.DirectoriesConfig,
AnnotationConfig = config.AnnotationConfig, AnnotationConfig = config.AnnotationConfig,
AIRecognitionConfig = config.AIRecognitionConfig, AIRecognitionConfig = config.AIRecognitionConfig,
+29 -15
View File
@@ -100,26 +100,40 @@ public class FailsafeAnnotationsProducer
.ToListAsync(token: cancellationToken); .ToListAsync(token: cancellationToken);
var messages = new List<AnnotationCreatedMessage>(); var messages = new List<AnnotationCreatedMessage>();
var badImages = new List<string>();
foreach (var annotation in annotations) foreach (var annotation in annotations)
{ {
var image = await File.ReadAllBytesAsync(annotation.ImagePath, cancellationToken); try
var annCreateMessage = new AnnotationCreatedMessage
{ {
Name = annotation.Name, var image = await File.ReadAllBytesAsync(annotation.ImagePath, cancellationToken);
OriginalMediaName = annotation.OriginalMediaName, var annCreateMessage = new AnnotationCreatedMessage
Time = annotation.Time, {
CreatedRole = annotation.CreatedRole, Name = annotation.Name,
CreatedEmail = annotation.CreatedEmail, OriginalMediaName = annotation.OriginalMediaName,
CreatedDate = annotation.CreatedDate, Time = annotation.Time,
Status = annotation.AnnotationStatus, CreatedRole = annotation.CreatedRole,
CreatedEmail = annotation.CreatedEmail,
CreatedDate = annotation.CreatedDate,
Status = annotation.AnnotationStatus,
ImageExtension = annotation.ImageExtension, ImageExtension = annotation.ImageExtension,
Image = image, Image = image,
Detections = JsonConvert.SerializeObject(annotation.Detections), Detections = JsonConvert.SerializeObject(annotation.Detections),
Source = annotation.Source, Source = annotation.Source,
};
messages.Add(annCreateMessage);
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
badImages.Add(annotation.Name);
}
}
}; if (badImages.Any())
messages.Add(annCreateMessage); {
await db.AnnotationsQueue.Where(x => badImages.Contains(x.Name)).DeleteAsync(token: cancellationToken);
_dbFactory.SaveToDisk();
} }
return messages; return messages;
}); });
+14 -15
View File
@@ -2,12 +2,12 @@
using Azaion.Common.Database; using Azaion.Common.Database;
using Azaion.Common.DTO.Config; using Azaion.Common.DTO.Config;
using Azaion.CommonSecurity; using Azaion.CommonSecurity;
using Azaion.CommonSecurity.DTO;
using Azaion.CommonSecurity.DTO.Commands; using Azaion.CommonSecurity.DTO.Commands;
using Azaion.CommonSecurity.Services;
using MessagePack;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using NetMQ;
using NetMQ.Sockets;
namespace Azaion.Common.Services; namespace Azaion.Common.Services;
@@ -16,30 +16,29 @@ public interface IInferenceService
Task RunInference(List<string> mediaPaths, Func<AnnotationImage, Task> processAnnotation, CancellationToken ct = default); Task RunInference(List<string> mediaPaths, Func<AnnotationImage, Task> processAnnotation, CancellationToken ct = default);
} }
public class PythonInferenceService(ILogger<PythonInferenceService> logger, IOptions<PythonConfig> pythonConfigOptions, IOptions<AIRecognitionConfig> aiConfigOptions) : IInferenceService public class InferenceService(ILogger<InferenceService> logger, [FromKeyedServices(SecurityConstants.EXTERNAL_INFERENCE_PATH)] IExternalClient externalClient, IOptions<AIRecognitionConfig> aiConfigOptions) : IInferenceService
{ {
public async Task RunInference(List<string> mediaPaths, Func<AnnotationImage, Task> processAnnotation, CancellationToken ct = default) public async Task RunInference(List<string> mediaPaths, Func<AnnotationImage, Task> processAnnotation, CancellationToken ct = default)
{ {
var pythonConfig = pythonConfigOptions.Value;
var aiConfig = aiConfigOptions.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; aiConfig.Paths = mediaPaths;
dealer.SendFrame(RemoteCommand.Serialize(CommandType.Inference, aiConfig)); externalClient.Send(RemoteCommand.Create(CommandType.Inference, aiConfig));
while (!ct.IsCancellationRequested) while (!ct.IsCancellationRequested)
{ {
try try
{ {
var annotationStream = dealer.Get<AnnotationImage>(bytes => bytes.Length == 4 && Encoding.UTF8.GetString(bytes) == "DONE", ct: ct); var bytes = externalClient.GetBytes(ct: ct);
if (annotationStream == null) if (bytes == null)
break; 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<AnnotationImage>(bytes, cancellationToken: ct);
await processAnnotation(annotationImage);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -9,6 +9,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="MessagePack" Version="3.1.0" /> <PackageReference Include="MessagePack" Version="3.1.0" />
<PackageReference Include="MessagePack.Annotations" Version="3.1.0" /> <PackageReference Include="MessagePack.Annotations" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
<PackageReference Include="NetMQ" Version="4.0.1.13" /> <PackageReference Include="NetMQ" Version="4.0.1.13" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
@@ -11,10 +11,10 @@ public class RemoteCommand(CommandType commandType, byte[]? data = null)
[Key("Data")] [Key("Data")]
public byte[]? Data { get; set; } = data; public byte[]? Data { get; set; } = data;
public static byte[] Serialize<T>(CommandType commandType, T data) where T : class public static RemoteCommand Create<T>(CommandType commandType, T data) where T : class
{ {
var dataBytes = MessagePackSerializer.Serialize(data); var dataBytes = MessagePackSerializer.Serialize(data);
return MessagePackSerializer.Serialize(new RemoteCommand(commandType, dataBytes )); return new RemoteCommand(commandType, dataBytes);
} }
} }
@@ -1,11 +1,16 @@
namespace Azaion.CommonSecurity.DTO; namespace Azaion.CommonSecurity.DTO;
public class PythonConfig public abstract class ExternalClientConfig
{ {
public string ZeroMqHost { get; set; } = ""; public string ZeroMqHost { get; set; } = "";
public int ZeroMqPort { get; set; } public int ZeroMqPort { get; set; }
public double OneTryTimeoutSeconds { get; set; } public double OneTryTimeoutSeconds { get; set; }
public int RetryCount {get;set;} public int RetryCount {get;set;}
}
public class InferenceClientConfig : ExternalClientConfig
{
public string ResourcesFolder { get; set; } = ""; public string ResourcesFolder { get; set; } = "";
} }
public class GpsDeniedClientConfig : ExternalClientConfig;
+2 -1
View File
@@ -2,5 +2,6 @@
public class SecureAppConfig public class SecureAppConfig
{ {
public PythonConfig PythonConfig { get; set; } = null!; public InferenceClientConfig InferenceClientConfig { get; set; } = null!;
public GpsDeniedClientConfig GpsDeniedClientConfig { get; set; } = null!;
} }
+31 -6
View File
@@ -1,4 +1,6 @@
namespace Azaion.CommonSecurity; using Azaion.CommonSecurity.DTO;
namespace Azaion.CommonSecurity;
public class SecurityConstants public class SecurityConstants
{ {
@@ -6,13 +8,36 @@ public class SecurityConstants
public const string DUMMY_DIR = "dummy"; public const string DUMMY_DIR = "dummy";
#region PythonConfig #region ExternalClientsConfig
public const string AZAION_INFERENCE_PATH = "azaion-inference.exe"; 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_RETRY_COUNT = 25;
public const int DEFAULT_TIMEOUT_SECONDS = 5; 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
} }
@@ -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<User>();
if (user == null)
throw new Exception("Can't get user from Auth provider");
CurrentUser = user;
}
}
@@ -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<T>(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<T>(int retries = 24, int tryTimeoutSeconds = 5, CancellationToken ct = default) where T : class
{
var bytes = GetBytes(retries, tryTimeoutSeconds, ct);
return bytes != null ? MessagePackSerializer.Deserialize<T>(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> inferenceClientConfig)
: BaseZeroMqExternalClient(inferenceClientConfig.Value)
{
protected override string ClientPath => SecurityConstants.EXTERNAL_INFERENCE_PATH;
}
public class GpsDeniedExternalClient(IOptions<GpsDeniedClientConfig> gpsDeniedClientConfig)
: BaseZeroMqExternalClient(gpsDeniedClientConfig.Value)
{
protected override string ClientPath => SecurityConstants.EXTERNAL_GPS_DENIED_PATH;
}
@@ -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<User>();
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);
}
}
}
@@ -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);
}
}
-28
View File
@@ -1,28 +0,0 @@
using MessagePack;
using NetMQ;
using NetMQ.Sockets;
namespace Azaion.CommonSecurity;
public static class ZeroMqExtensions
{
public static T? Get<T>(this DealerSocket dealer, Func<byte[], bool>? 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<T>(bytes);
}
if (!ct.IsCancellationRequested)
throw new Exception($"Unable to get {typeof(T).Name} after {tryNum} retries, {tryTimeoutSeconds} seconds each");
return null;
}
}
+1
View File
@@ -9,6 +9,7 @@ cdef class AIRecognitionConfig:
cdef public bytes file_data cdef public bytes file_data
cdef public list[str] paths cdef public list[str] paths
cdef public int model_batch_size
@staticmethod @staticmethod
cdef from_msgpack(bytes data) cdef from_msgpack(bytes data)
+14 -10
View File
@@ -11,7 +11,8 @@ cdef class AIRecognitionConfig:
tracking_intersection_threshold, tracking_intersection_threshold,
file_data, file_data,
paths paths,
model_batch_size
): ):
self.frame_period_recognition = frame_period_recognition self.frame_period_recognition = frame_period_recognition
self.frame_recognition_seconds = frame_recognition_seconds self.frame_recognition_seconds = frame_recognition_seconds
@@ -23,26 +24,29 @@ cdef class AIRecognitionConfig:
self.file_data = file_data self.file_data = file_data
self.paths = paths self.paths = paths
self.model_batch_size = model_batch_size
def __str__(self): def __str__(self):
return (f'frame_seconds : {self.frame_recognition_seconds}, distance_confidence : {self.tracking_distance_confidence}, ' return (f'frame_seconds : {self.frame_recognition_seconds}, distance_confidence : {self.tracking_distance_confidence}, '
f'probability_increase : {self.tracking_probability_increase}, ' f'probability_increase : {self.tracking_probability_increase}, '
f'intersection_threshold : {self.tracking_intersection_threshold}, ' f'intersection_threshold : {self.tracking_intersection_threshold}, '
f'frame_period_recognition : {self.frame_period_recognition}, ' 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 @staticmethod
cdef from_msgpack(bytes data): cdef from_msgpack(bytes data):
unpacked = unpackb(data, strict_map_key=False) unpacked = unpackb(data, strict_map_key=False)
return AIRecognitionConfig( return AIRecognitionConfig(
unpacked.get("FramePeriodRecognition", 0), unpacked.get("f_pr", 0),
unpacked.get("FrameRecognitionSeconds", 0.0), unpacked.get("f_rs", 0.0),
unpacked.get("ProbabilityThreshold", 0.0), unpacked.get("pt", 0.0),
unpacked.get("TrackingDistanceConfidence", 0.0), unpacked.get("t_dc", 0.0),
unpacked.get("TrackingProbabilityIncrease", 0.0), unpacked.get("t_pi", 0.0),
unpacked.get("TrackingIntersectionThreshold", 0.0), unpacked.get("t_it", 0.0),
unpacked.get("Data", b''), unpacked.get("d", b''),
unpacked.get("Paths", []), unpacked.get("p", []),
unpacked.get("m_bs")
) )
+1 -1
View File
@@ -1 +1 @@
zmq_port: 5128 zmq_port: 5127
-1
View File
@@ -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 str AI_MODEL_FILE_SMALL # AI Model file (small part)
cdef bytes DONE_SIGNAL cdef bytes DONE_SIGNAL
cdef int MODEL_BATCH_SIZE
cdef log(str log_message, bytes client_id=*) cdef log(str log_message, bytes client_id=*)
-1
View File
@@ -12,7 +12,6 @@ cdef str AI_MODEL_FILE_BIG = "azaion.onnx.big"
cdef str AI_MODEL_FILE_SMALL = "azaion.onnx.small" cdef str AI_MODEL_FILE_SMALL = "azaion.onnx.small"
cdef bytes DONE_SIGNAL = b"DONE" cdef bytes DONE_SIGNAL = b"DONE"
cdef int MODEL_BATCH_SIZE = 4
cdef log(str log_message, bytes client_id=None): cdef log(str log_message, bytes client_id=None):
local_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) local_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
+2 -2
View File
@@ -132,7 +132,7 @@ cdef class Inference:
images.append(m) images.append(m)
# images first, it's faster # images first, it's faster
if len(images) > 0: 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)}...') print(f'run inference on {" ".join(chunk)}...')
self._process_images(cmd, ai_config, chunk) self._process_images(cmd, ai_config, chunk)
if len(videos) > 0: if len(videos) > 0:
@@ -158,7 +158,7 @@ cdef class Inference:
batch_frames.append(frame) batch_frames.append(frame)
batch_timestamps.append(int(v_input.get(cv2.CAP_PROP_POS_MSEC))) 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) input_blob = self.preprocess(batch_frames)
outputs = self.session.run(None, {self.model_input: input_blob}) outputs = self.session.run(None, {self.model_input: input_blob})
list_detections = self.postprocess(outputs, ai_config) list_detections = self.postprocess(outputs, ai_config)
+1
View File
@@ -8,3 +8,4 @@ msgpack
pyjwt pyjwt
zmq zmq
requests requests
pyyaml
+36 -23
View File
@@ -33,7 +33,10 @@ public partial class App
private IMediator _mediator = null!; private IMediator _mediator = null!;
private FormState _formState = 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 Stream _securedConfig = null!;
private static readonly Guid KeyPressTaskId = Guid.NewGuid(); private static readonly Guid KeyPressTaskId = Guid.NewGuid();
@@ -55,41 +58,44 @@ public partial class App
"Azaion.Dataset" "Azaion.Dataset"
]; ];
private static PythonConfig ReadPythonConfig() private static SecureAppConfig ReadSecureAppConfig()
{ {
try try
{ {
if (!File.Exists(SecurityConstants.CONFIG_PATH)) if (!File.Exists(SecurityConstants.CONFIG_PATH))
throw new FileNotFoundException(SecurityConstants.CONFIG_PATH); throw new FileNotFoundException(SecurityConstants.CONFIG_PATH);
var configStr = File.ReadAllText(SecurityConstants.CONFIG_PATH); var configStr = File.ReadAllText(SecurityConstants.CONFIG_PATH);
return JsonConvert.DeserializeObject<SecureAppConfig>(configStr)!.PythonConfig; var config = JsonConvert.DeserializeObject<SecureAppConfig>(configStr);
return config ?? SecurityConstants.DefaultSecureAppConfig;
} }
catch (Exception e) catch (Exception e)
{ {
Console.WriteLine(e); Console.WriteLine(e);
return new PythonConfig return SecurityConstants.DefaultSecureAppConfig;
{
ZeroMqHost = SecurityConstants.DEFAULT_ZMQ_HOST,
ZeroMqPort = SecurityConstants.DEFAULT_ZMQ_PORT,
OneTryTimeoutSeconds = SecurityConstants.DEFAULT_TIMEOUT_SECONDS,
RetryCount = SecurityConstants.DEFAULT_RETRY_COUNT,
ResourcesFolder = ""
};
} }
} }
private void StartLogin() private void StartLogin()
{ {
new ConfigUpdater().CheckConfig(); new ConfigUpdater().CheckConfig();
var secureAppConfig = ReadSecureAppConfig();
_inferenceClient = new InferenceExternalClient(new OptionsWrapper<InferenceClientConfig>(secureAppConfig.InferenceClientConfig));
_resourceLoader = new ResourceLoader(_inferenceClient);
_authProvider = new AuthProvider(_inferenceClient);
var login = new Login(); var login = new Login();
var pythonConfig = ReadPythonConfig(); login.Closed += (sender, args) =>
_resourceLoader = new PythonResourceLoader(pythonConfig); {
if (!login.MainSuiteOpened)
_inferenceClient.Stop();
};
login.CredentialsEntered += (_, credentials) => login.CredentialsEntered += (_, credentials) =>
{ {
credentials.Folder = pythonConfig.ResourcesFolder; credentials.Folder = secureAppConfig.InferenceClientConfig.ResourcesFolder;
_resourceLoader.Login(credentials); _authProvider.Login(credentials);
_securedConfig = _resourceLoader.LoadFileFromPython("secured-config.json"); _securedConfig = _resourceLoader.LoadFile("secured-config.json");
AppDomain.CurrentDomain.AssemblyResolve += (_, a) => AppDomain.CurrentDomain.AssemblyResolve += (_, a) =>
{ {
@@ -98,7 +104,7 @@ public partial class App
{ {
try try
{ {
var stream = _resourceLoader.LoadFileFromPython($"{assemblyName}.dll"); var stream = _resourceLoader.LoadFile($"{assemblyName}.dll");
return Assembly.Load(stream.ToArray()); return Assembly.Load(stream.ToArray());
} }
catch (Exception e) catch (Exception e)
@@ -147,18 +153,25 @@ public partial class App
services.AddSingleton<MainSuite>(); services.AddSingleton<MainSuite>();
services.AddSingleton<IHardwareService, HardwareService>(); services.AddSingleton<IHardwareService, HardwareService>();
services.AddSingleton<IResourceLoader>(_resourceLoader);
services.AddSingleton<IAuthProvider>(_resourceLoader);
services.AddSingleton<IInferenceService, PythonInferenceService>();
services.Configure<AppConfig>(context.Configuration); services.Configure<AppConfig>(context.Configuration);
services.ConfigureSection<PythonConfig>(context.Configuration);
services.ConfigureSection<QueueConfig>(context.Configuration); services.ConfigureSection<QueueConfig>(context.Configuration);
services.ConfigureSection<DirectoriesConfig>(context.Configuration); services.ConfigureSection<DirectoriesConfig>(context.Configuration);
services.ConfigureSection<AnnotationConfig>(context.Configuration); services.ConfigureSection<AnnotationConfig>(context.Configuration);
services.ConfigureSection<AIRecognitionConfig>(context.Configuration); services.ConfigureSection<AIRecognitionConfig>(context.Configuration);
services.ConfigureSection<ThumbnailConfig>(context.Configuration); services.ConfigureSection<ThumbnailConfig>(context.Configuration);
#region External Services
services.ConfigureSection<InferenceClientConfig>(context.Configuration);
services.ConfigureSection<GpsDeniedClientConfig>(context.Configuration);
services.AddKeyedSingleton<IExternalClient>(SecurityConstants.EXTERNAL_INFERENCE_PATH, _inferenceClient);
services.AddKeyedSingleton<IExternalClient, GpsDeniedExternalClient>(SecurityConstants.EXTERNAL_GPS_DENIED_PATH);
services.AddSingleton<IResourceLoader>(_resourceLoader);
services.AddSingleton<IAuthProvider>(_authProvider);
services.AddSingleton<IInferenceService, InferenceService>();
#endregion
services.AddSingleton<IConfigUpdater, ConfigUpdater>(); services.AddSingleton<IConfigUpdater, ConfigUpdater>();
services.AddSingleton<Annotator.Annotator>(); services.AddSingleton<Annotator.Annotator>();
services.AddSingleton<DatasetExplorer>(); services.AddSingleton<DatasetExplorer>();
+2 -2
View File
@@ -60,8 +60,8 @@
<Target Name="PostBuild" AfterTargets="Build"> <Target Name="PostBuild" AfterTargets="Build">
<MakeDir Directories="$(TargetDir)dummy" /> <MakeDir Directories="$(TargetDir)dummy" />
<Move SourceFiles="$(TargetDir)Azaion.Annotator.dll" DestinationFolder="$(TargetDir)dummy" /> <Copy SourceFiles="$(TargetDir)Azaion.Annotator.dll" DestinationFolder="$(TargetDir)dummy" />
<Move SourceFiles="$(TargetDir)Azaion.Dataset.dll" DestinationFolder="$(TargetDir)dummy" /> <Copy SourceFiles="$(TargetDir)Azaion.Dataset.dll" DestinationFolder="$(TargetDir)dummy" />
<Exec Command="upload.cmd $(ConfigurationName) stage" /> <Exec Command="upload.cmd $(ConfigurationName) stage" />
</Target> </Target>
+3
View File
@@ -8,6 +8,8 @@ namespace Azaion.Suite;
public partial class Login public partial class Login
{ {
public bool MainSuiteOpened { get; set; } = false;
public Login() public Login()
{ {
InitializeComponent(); InitializeComponent();
@@ -20,6 +22,7 @@ public partial class Login
LoginBtn.Cursor = Cursors.Wait; LoginBtn.Cursor = Cursors.Wait;
Cursor = Cursors.Wait; Cursor = Cursors.Wait;
CredentialsEntered?.Invoke(this, new ApiCredentials(TbEmail.Text, TbPassword.Password)); CredentialsEntered?.Invoke(this, new ApiCredentials(TbEmail.Text, TbPassword.Password));
MainSuiteOpened = true;
Close(); Close();
} }
+10 -4
View File
@@ -25,6 +25,7 @@ public partial class MainSuite
private readonly IDbFactory _dbFactory; private readonly IDbFactory _dbFactory;
private readonly Dictionary<WindowEnum, Window> _openedWindows = new(); private readonly Dictionary<WindowEnum, Window> _openedWindows = new();
private readonly IResourceLoader _resourceLoader; private readonly IResourceLoader _resourceLoader;
private readonly IEnumerable<IExternalClient> _externalClients;
private static readonly Guid SaveConfigTaskId = Guid.NewGuid(); private static readonly Guid SaveConfigTaskId = Guid.NewGuid();
public MainSuite(IOptions<AppConfig> appConfig, public MainSuite(IOptions<AppConfig> appConfig,
@@ -33,7 +34,8 @@ public partial class MainSuite
IServiceProvider sp, IServiceProvider sp,
IGalleryService galleryService, IGalleryService galleryService,
IDbFactory dbFactory, IDbFactory dbFactory,
IResourceLoader resourceLoader) IResourceLoader resourceLoader,
IEnumerable<IExternalClient> externalClients)
{ {
_configUpdater = configUpdater; _configUpdater = configUpdater;
_modules = modules; _modules = modules;
@@ -41,6 +43,7 @@ public partial class MainSuite
_galleryService = galleryService; _galleryService = galleryService;
_dbFactory = dbFactory; _dbFactory = dbFactory;
_resourceLoader = resourceLoader; _resourceLoader = resourceLoader;
_externalClients = externalClients;
_appConfig = appConfig.Value; _appConfig = appConfig.Value;
InitializeComponent(); InitializeComponent();
Loaded += OnLoaded; Loaded += OnLoaded;
@@ -111,10 +114,13 @@ public partial class MainSuite
_openedWindows[module.WindowEnum] = window; _openedWindows[module.WindowEnum] = window;
window.Closed += (_, _) => window.Closed += (_, _) =>
{ {
_resourceLoader.StopPython();
_openedWindows.Remove(module.WindowEnum); _openedWindows.Remove(module.WindowEnum);
if (!_openedWindows.Any()) if (_openedWindows.Any())
Close(); return;
foreach (var client in _externalClients)
client.Stop();
Close();
}; };
window.Show(); window.Show();
window.Activate(); window.Activate();
+10 -2
View File
@@ -1,11 +1,17 @@
{ {
"PythonConfig": { "InferenceClientConfig": {
"ZeroMqHost": "127.0.0.1", "ZeroMqHost": "127.0.0.1",
"ZeroMqPort": 5127, "ZeroMqPort": 5127,
"RetryCount": 25, "RetryCount": 25,
"TimeoutSeconds": 5, "TimeoutSeconds": 5,
"ResourcesFolder": "stage" "ResourcesFolder": "stage"
}, },
"GpsDeniedClientConfig": {
"ZeroMqHost": "127.0.0.1",
"ZeroMqPort": 5227,
"RetryCount": 25,
"TimeoutSeconds": 5
},
"DirectoriesConfig": { "DirectoriesConfig": {
"VideosDirectory": "E:\\Azaion6", "VideosDirectory": "E:\\Azaion6",
"LabelsDirectory": "E:\\labels", "LabelsDirectory": "E:\\labels",
@@ -44,7 +50,9 @@
"TrackingDistanceConfidence": 0.15, "TrackingDistanceConfidence": 0.15,
"TrackingProbabilityIncrease": 15.0, "TrackingProbabilityIncrease": 15.0,
"TrackingIntersectionThreshold": 0.8 "TrackingIntersectionThreshold": 0.8,
"ModelBatchSize": 2
}, },
"ThumbnailConfig": { "Size": "240,135", "Border": 10 }, "ThumbnailConfig": { "Size": "240,135", "Border": 10 },
"MapConfig": "MapConfig":
+8 -1
View File
@@ -1,8 +1,15 @@
{ {
"PythonConfig": { "InferenceClientConfig": {
"ZeroMqHost": "127.0.0.1", "ZeroMqHost": "127.0.0.1",
"ZeroMqPort": 5131, "ZeroMqPort": 5131,
"RetryCount": 25, "RetryCount": 25,
"TimeoutSeconds": 5,
"ResourcesFolder": "stage"
},
"GpsDeniedClientConfig": {
"ZeroMqHost": "127.0.0.1",
"ZeroMqPort": 5231,
"RetryCount": 25,
"TimeoutSeconds": 5 "TimeoutSeconds": 5
}, },
"DirectoriesConfig": { "DirectoriesConfig": {