add ramdisk, load AI model to ramdisk and start recognition from it

rewrite zmq to DEALER and ROUTER
add GET_USER command to get CurrentUser from Python
all auth is on the python side
inference run and validate annotations on python
This commit is contained in:
Alex Bezdieniezhnykh
2025-01-29 17:45:26 +02:00
parent 82b3b526a7
commit 62623b7123
55 changed files with 945 additions and 895 deletions
+29 -21
View File
@@ -22,29 +22,31 @@ namespace Azaion.Common.Services;
public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
{
private readonly AzaionApiClient _apiClient;
private readonly IDbFactory _dbFactory;
private readonly FailsafeAnnotationsProducer _producer;
private readonly IGalleryService _galleryService;
private readonly IMediator _mediator;
private readonly IHardwareService _hardwareService;
private readonly IAuthProvider _authProvider;
private readonly QueueConfig _queueConfig;
private Consumer _consumer = null!;
public AnnotationService(AzaionApiClient apiClient,
public AnnotationService(
IResourceLoader resourceLoader,
IDbFactory dbFactory,
FailsafeAnnotationsProducer producer,
IOptions<QueueConfig> queueConfig,
IGalleryService galleryService,
IMediator mediator,
IHardwareService hardwareService)
IHardwareService hardwareService,
IAuthProvider authProvider)
{
_apiClient = apiClient;
_dbFactory = dbFactory;
_producer = producer;
_galleryService = galleryService;
_mediator = mediator;
_hardwareService = hardwareService;
_authProvider = authProvider;
_queueConfig = queueConfig.Value;
Task.Run(async () => await Init()).Wait();
@@ -73,7 +75,8 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
await SaveAnnotationInner(
msg.CreatedDate,
msg.Name,
msg.OriginalMediaName,
msg.Time,
msg.ImageExtension,
JsonConvert.DeserializeObject<List<Detection>>(msg.Detections) ?? [],
msg.Source,
@@ -98,36 +101,39 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
});
}
//AI / Manual
public async Task<Annotation> SaveAnnotation(string fName, string imageExtension, List<Detection> detections, SourceEnum source, Stream? stream = null, CancellationToken token = default) =>
await SaveAnnotationInner(DateTime.UtcNow, fName, imageExtension, detections, source, stream, _apiClient.User.Role, _apiClient.User.Email, generateThumbnail: true, token);
//AI
public async Task<Annotation> SaveAnnotation(AnnotationImage a, CancellationToken cancellationToken = default)
{
a.Time = TimeSpan.FromMilliseconds(a.Milliseconds);
a.Name = a.OriginalMediaName.ToTimeName(a.Time);
return await SaveAnnotationInner(DateTime.Now, a.OriginalMediaName, a.Time, ".jpg", a.Detections.ToList(),
a.Source, new MemoryStream(a.Image), a.CreatedRole, a.CreatedEmail, generateThumbnail: true, cancellationToken);
}
//Manual
public async Task<Annotation> SaveAnnotation(string originalMediaName, TimeSpan time, string imageExtension, List<Detection> detections, SourceEnum source, Stream? stream = null, CancellationToken token = default) =>
await SaveAnnotationInner(DateTime.UtcNow, originalMediaName, time, imageExtension, detections, source, stream,
_authProvider.CurrentUser.Role, _authProvider.CurrentUser.Email, generateThumbnail: true, token);
//Manual Validate existing
public async Task ValidateAnnotation(Annotation annotation, CancellationToken token = default) =>
await SaveAnnotationInner(DateTime.UtcNow, annotation.Name, annotation.ImageExtension, annotation.Detections.ToList(), SourceEnum.Manual, null, _apiClient.User.Role, _apiClient.User.Email,
generateThumbnail: false, token);
await SaveAnnotationInner(DateTime.UtcNow, annotation.OriginalMediaName, annotation.Time, annotation.ImageExtension, annotation.Detections.ToList(), SourceEnum.Manual, null,
_authProvider.CurrentUser.Role, _authProvider.CurrentUser.Email, generateThumbnail: false, token);
// //Queue (only from operators)
// public async Task Consume(AnnotationCreatedMessage message, CancellationToken cancellationToken = default)
// {
//
// }
private async Task<Annotation> SaveAnnotationInner(DateTime createdDate, string fName, string imageExtension, List<Detection> detections, SourceEnum source, Stream? stream,
private async Task<Annotation> SaveAnnotationInner(DateTime createdDate, string originalMediaName, TimeSpan time, string imageExtension, List<Detection> detections, SourceEnum source, Stream? stream,
RoleEnum userRole,
string createdEmail,
bool generateThumbnail = false,
CancellationToken token = default)
{
//Flow for roles:
// Operator or (AI from any role) -> Created
// Validator, Admin & Manual -> Validated
AnnotationStatus status;
var fName = originalMediaName.ToTimeName(time);
var annotation = await _dbFactory.Run(async db =>
{
var ann = await db.Annotations.FirstOrDefaultAsync(x => x.Name == fName, token: token);
// Manual Save from Validators -> Validated
// otherwise Created
status = userRole.IsValidator() && source == SourceEnum.Manual
? AnnotationStatus.Validated
: AnnotationStatus.Created;
@@ -149,6 +155,8 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
{
CreatedDate = createdDate,
Name = fName,
OriginalMediaName = originalMediaName,
Time = time,
ImageExtension = imageExtension,
CreatedEmail = createdEmail,
CreatedRole = userRole,
+3 -2
View File
@@ -76,7 +76,7 @@ public class FailsafeAnnotationsProducer
await _annotationConfirmProducer.Send(validatedMessages, CompressionType.Gzip);
await _dbFactory.Run(async db =>
await db.AnnotationsQueue.DeleteAsync(aq => messagesChunk.Any(x => aq.Name == x.Name), token: cancellationToken));
await db.AnnotationsQueue.DeleteAsync(aq => messagesChunk.Any(x => aq.Name == x.OriginalMediaName), token: cancellationToken));
sent = true;
}
catch (Exception e)
@@ -106,7 +106,8 @@ public class FailsafeAnnotationsProducer
var annCreateMessage = new AnnotationCreatedMessage
{
Name = annotation.Name,
OriginalMediaName = annotation.OriginalMediaName,
Time = annotation.Time,
CreatedRole = annotation.CreatedRole,
CreatedEmail = annotation.CreatedEmail,
CreatedDate = annotation.CreatedDate,
+45 -13
View File
@@ -8,6 +8,7 @@ using Azaion.Common.Database;
using Azaion.Common.DTO;
using Azaion.Common.DTO.Config;
using Azaion.Common.DTO.Queue;
using Azaion.Common.Extensions;
using Azaion.CommonSecurity.DTO;
using LinqToDB;
using LinqToDB.Data;
@@ -75,7 +76,6 @@ public class GalleryService(
var missedAnnotations = new ConcurrentBag<Annotation>();
try
{
var prefixLen = Constants.THUMBNAIL_PREFIX.Length;
var thumbnails = ThumbnailsDirectory.GetFiles()
@@ -105,9 +105,37 @@ public class GalleryService(
return;
var detections = (await YoloLabel.ReadFromFile(labelName, cancellationToken)).Select(x => new Detection(fName, x)).ToList();
//get names and time
var fileName = Path.GetFileNameWithoutExtension(file.Name);
var strings = fileName.Split("_");
var timeStr = strings.LastOrDefault();
string originalMediaName;
TimeSpan time;
//For some reason, TimeSpan.ParseExact doesn't work on every platform.
if (!string.IsNullOrEmpty(timeStr) &&
timeStr.Length == 6 &&
int.TryParse(timeStr[..1], out var hours) &&
int.TryParse(timeStr[1..3], out var minutes) &&
int.TryParse(timeStr[3..5], out var seconds) &&
int.TryParse(timeStr[5..], out var milliseconds))
{
time = new TimeSpan(0, hours, minutes, seconds, milliseconds * 100);
originalMediaName = fileName[..^7];
}
else
{
originalMediaName = fileName;
time = TimeSpan.FromSeconds(0);
}
var annotation = new Annotation
{
Name = fName,
Time = time,
OriginalMediaName = originalMediaName,
Name = file.Name.ToFName(),
ImageExtension = Path.GetExtension(file.Name),
Detections = detections,
CreatedDate = File.GetCreationTimeUtc(file.FullName),
@@ -129,18 +157,22 @@ public class GalleryService(
logger.LogError(e, $"Failed to generate thumbnail for {file.Name}! Error: {e.Message}");
}
},
new ParallelOptions
new ParallelOptions
{
ProgressFn = async num =>
{
ProgressFn = async num =>
{
Console.WriteLine($"Processed {num} item by Thread {Environment.CurrentManagedThreadId}");
ProcessedThumbnailsPercentage = imagesCount == 0 ? 0 : Math.Min(100, num * 100 / (double)imagesCount);
ThumbnailsUpdate?.Invoke(ProcessedThumbnailsPercentage);
await Task.CompletedTask;
},
CpuUtilPercent = 100,
ProgressUpdateInterval = 200
});
Console.WriteLine($"Processed {num} item by Thread {Environment.CurrentManagedThreadId}");
ProcessedThumbnailsPercentage = imagesCount == 0 ? 0 : Math.Min(100, num * 100 / (double)imagesCount);
ThumbnailsUpdate?.Invoke(ProcessedThumbnailsPercentage);
await Task.CompletedTask;
},
CpuUtilPercent = 100,
ProgressUpdateInterval = 200
});
}
catch (Exception e)
{
logger.LogError(e, $"Failed to refresh thumbnails! Error: {e.Message}");
}
finally
{
@@ -0,0 +1,53 @@
using System.Text;
using Azaion.Common.Database;
using Azaion.Common.DTO.Config;
using Azaion.CommonSecurity;
using Azaion.CommonSecurity.DTO.Commands;
using Azaion.CommonSecurity.Services;
using MessagePack;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NetMQ;
using NetMQ.Sockets;
namespace Azaion.Common.Services;
public interface IInferenceService
{
Task RunInference(string mediaPath, Func<AnnotationImage, CancellationToken, Task> processAnnotation, CancellationToken ct = default);
}
public class PythonInferenceService(ILogger<PythonInferenceService> logger, IOptions<AIRecognitionConfig> aiConfigOptions) : IInferenceService
{
public async Task RunInference(string mediaPath, Func<AnnotationImage, CancellationToken, Task> processAnnotation, CancellationToken ct = default)
{
using var dealer = new DealerSocket();
var clientId = Guid.NewGuid();
dealer.Options.Identity = Encoding.UTF8.GetBytes(clientId.ToString("N"));
dealer.Connect($"tcp://{SecurityConstants.ZMQ_HOST}:{SecurityConstants.ZMQ_PORT}");
var data = MessagePackSerializer.Serialize(aiConfigOptions.Value);
dealer.SendFrame(MessagePackSerializer.Serialize(new RemoteCommand(CommandType.Inference, mediaPath, data)));
while (true)
{
byte[] bytes = [];
try
{
var annotationStream = dealer.Get<AnnotationImage>(out bytes);
if (annotationStream == null)
{
if (bytes.Length == 4 && Encoding.UTF8.GetString(bytes) == "DONE")
break;
continue;
}
await processAnnotation(annotationStream, ct);
}
catch (Exception e)
{
logger.LogError(e, e.Message);
}
}
}
}