mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 19:46:29 +00:00
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:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user