failsafe load dlls

add user config queue offsets
throttle improvements
This commit is contained in:
Alex Bezdieniezhnykh
2025-04-17 01:19:48 +03:00
parent 0237e279a5
commit 0c66607ed7
32 changed files with 320 additions and 188 deletions
+43 -12
View File
@@ -1,5 +1,9 @@
using System.Windows;
using System.Windows.Media;
using Azaion.Common.Database;
using Azaion.Common.DTO;
using Azaion.Common.DTO.Config;
using Azaion.Common.Extensions;
namespace Azaion.Common;
@@ -21,20 +25,32 @@ public class Constants
#region AnnotatorConfig
public static readonly AnnotationConfig DefaultAnnotationConfig = new()
{
DetectionClasses = DefaultAnnotationClasses,
VideoFormats = DefaultVideoFormats,
ImageFormats = DefaultImageFormats,
AnnotationsDbFile = DEFAULT_ANNOTATIONS_DB_FILE
};
public static readonly List<DetectionClass> DefaultAnnotationClasses =
[
new() { Id = 0, Name = "Броньована техніка", ShortName = "Бронь" },
new() { Id = 1, Name = "Вантажівка", ShortName = "Вантаж" },
new() { Id = 2, Name = "Машина легкова", ShortName = "Машина" },
new() { Id = 3, Name = "Артилерія", ShortName = "Арта" },
new() { Id = 4, Name = "Тінь від техніки", ShortName = "Тінь" },
new() { Id = 5, Name = "Окопи", ShortName = "Окопи" },
new() { Id = 6, Name = "Військовий", ShortName = "Військов" },
new() { Id = 7, Name = "Накати", ShortName = "Накати" },
new() { Id = 8, Name = "Танк з захистом", ShortName = "Танк захист" },
new() { Id = 9, Name = "Дим", ShortName = "Дим" },
new() { Id = 10, Name = "Літак", ShortName = "Літак" },
new() { Id = 11, Name = "Мотоцикл", ShortName = "Мото" }
new() { Id = 0, Name = "ArmorVehicle", ShortName = "Броня", Color = "#FF0000".ToColor() },
new() { Id = 1, Name = "Truck", ShortName = "Вантаж.", Color = "#00FF00".ToColor() },
new() { Id = 2, Name = "Vehicle", ShortName = "Машина", Color = "#0000FF".ToColor() },
new() { Id = 3, Name = "Atillery", ShortName = "Арта", Color = "#FFFF00".ToColor() },
new() { Id = 4, Name = "Shadow", ShortName = "Тінь", Color = "#FF00FF".ToColor() },
new() { Id = 5, Name = "Trenches", ShortName = "Окопи", Color = "#00FFFF".ToColor() },
new() { Id = 6, Name = "MilitaryMan", ShortName = "Військов", Color = "#188021".ToColor() },
new() { Id = 7, Name = "TyreTracks", ShortName = "Накати", Color = "#800000".ToColor() },
new() { Id = 8, Name = "AdditArmoredTank", ShortName = "Танк.захист", Color = "#008000".ToColor() },
new() { Id = 9, Name = "Smoke", ShortName = "Дим", Color = "#000080".ToColor() },
new() { Id = 10, Name = "Plane", ShortName = "Літак", Color = "#000080".ToColor() },
new() { Id = 11, Name = "Moto", ShortName = "Мото", Color = "#808000".ToColor() },
new() { Id = 12, Name = "CamouflageNet", ShortName = "Сітка", Color = "#800080".ToColor() },
new() { Id = 13, Name = "CamouflageBranches", ShortName = "Гілки", Color = "#2f4f4f".ToColor() },
new() { Id = 14, Name = "Roof", ShortName = "Дах", Color = "#1e90ff".ToColor() },
new() { Id = 15, Name = "Building", ShortName = "Будівля", Color = "#ffb6c1".ToColor() }
];
public static readonly List<string> DefaultVideoFormats = ["mp4", "mov", "avi"];
@@ -49,6 +65,15 @@ public class Constants
# region AIRecognitionConfig
public static readonly AIRecognitionConfig DefaultAIRecognitionConfig = new()
{
FrameRecognitionSeconds = DEFAULT_FRAME_RECOGNITION_SECONDS,
TrackingDistanceConfidence = TRACKING_DISTANCE_CONFIDENCE,
TrackingProbabilityIncrease = TRACKING_PROBABILITY_INCREASE,
TrackingIntersectionThreshold = TRACKING_INTERSECTION_THRESHOLD,
FramePeriodRecognition = DEFAULT_FRAME_PERIOD_RECOGNITION
};
public const double DEFAULT_FRAME_RECOGNITION_SECONDS = 2;
public const double TRACKING_DISTANCE_CONFIDENCE = 0.15;
public const double TRACKING_PROBABILITY_INCREASE = 15;
@@ -60,6 +85,12 @@ public class Constants
#region Thumbnails
public static readonly ThumbnailConfig DefaultThumbnailConfig = new()
{
Size = DefaultThumbnailSize,
Border = DEFAULT_THUMBNAIL_BORDER
};
public static readonly Size DefaultThumbnailSize = new(240, 135);
public const int DEFAULT_THUMBNAIL_BORDER = 10;
+3 -22
View File
@@ -45,14 +45,7 @@ public class ConfigUpdater : IConfigUpdater
var appConfig = new AppConfig
{
AnnotationConfig = new AnnotationConfig
{
DetectionClasses = Constants.DefaultAnnotationClasses,
VideoFormats = Constants.DefaultVideoFormats,
ImageFormats = Constants.DefaultImageFormats,
AnnotationsDbFile = Constants.DEFAULT_ANNOTATIONS_DB_FILE
},
AnnotationConfig = Constants.DefaultAnnotationConfig,
UIConfig = new UIConfig
{
@@ -72,20 +65,8 @@ public class ConfigUpdater : IConfigUpdater
GpsRouteDirectory = Constants.DEFAULT_GPS_ROUTE_DIRECTORY
},
ThumbnailConfig = new ThumbnailConfig
{
Size = Constants.DefaultThumbnailSize,
Border = Constants.DEFAULT_THUMBNAIL_BORDER
},
AIRecognitionConfig = new AIRecognitionConfig
{
FrameRecognitionSeconds = Constants.DEFAULT_FRAME_RECOGNITION_SECONDS,
TrackingDistanceConfidence = Constants.TRACKING_DISTANCE_CONFIDENCE,
TrackingProbabilityIncrease = Constants.TRACKING_PROBABILITY_INCREASE,
TrackingIntersectionThreshold = Constants.TRACKING_INTERSECTION_THRESHOLD,
FramePeriodRecognition = Constants.DEFAULT_FRAME_PERIOD_RECOGNITION
}
ThumbnailConfig = Constants.DefaultThumbnailConfig,
AIRecognitionConfig = Constants.DefaultAIRecognitionConfig
};
Save(appConfig);
}
@@ -1,13 +0,0 @@
namespace Azaion.Common.DTO.Config;
public class DirectoriesConfig
{
public string VideosDirectory { get; set; } = null!;
public string LabelsDirectory { get; set; } = null!;
public string ImagesDirectory { get; set; } = null!;
public string ResultsDirectory { get; set; } = null!;
public string ThumbnailsDirectory { get; set; } = null!;
public string GpsSatDirectory { get; set; } = null!;
public string GpsRouteDirectory { get; set; } = null!;
}
@@ -12,4 +12,7 @@ public static class ColorExtensions
color.A = (byte)(MIN_ALPHA + (int)Math.Round(confidence * (MAX_ALPHA - MIN_ALPHA)));
return color;
}
public static Color ToColor(this string hexColor) =>
(Color)ColorConverter.ConvertFromString(hexColor);
}
+11 -46
View File
@@ -1,57 +1,22 @@
using System.Collections.Concurrent;
namespace Azaion.Common.Extensions;
namespace Azaion.Common.Extensions;
public static class ThrottleExt
{
private static ConcurrentDictionary<Guid, bool> _taskStates = new();
private static readonly Dictionary<Delegate, DateTime> LastExecution = new();
private static readonly object Lock = new();
public static async Task ThrottleRunFirst(Func<Task> func, Guid actionId, TimeSpan? throttleTime = null, CancellationToken cancellationToken = default)
public static async Task Throttle(this Func<Task> func, TimeSpan interval, CancellationToken ct = default)
{
if (_taskStates.ContainsKey(actionId) && _taskStates[actionId])
return;
ArgumentNullException.ThrowIfNull(func);
_taskStates[actionId] = true;
try
lock (Lock)
{
await func();
if (LastExecution.ContainsKey(func) && DateTime.UtcNow - LastExecution[func] < interval)
return;
func();
LastExecution[func] = DateTime.UtcNow;
}
catch (Exception e)
{
Console.WriteLine(e);
}
_ = Task.Run(async () =>
{
await Task.Delay(throttleTime ?? TimeSpan.FromMilliseconds(500), cancellationToken);
_taskStates[actionId] = false;
}, cancellationToken);
}
public static async Task ThrottleRunAfter(Func<Task> func, Guid actionId, TimeSpan? throttleTime = null, CancellationToken cancellationToken = default)
{
if (_taskStates.ContainsKey(actionId) && _taskStates[actionId])
return;
_taskStates[actionId] = true;
_ = Task.Run(async () =>
{
try
{
await Task.Delay(throttleTime ?? TimeSpan.FromMilliseconds(500), cancellationToken);
await func();
}
catch (Exception)
{
_taskStates[actionId] = false;
}
finally
{
_taskStates[actionId] = false;
}
}, cancellationToken);
await Task.CompletedTask;
}
}
+14 -19
View File
@@ -26,15 +26,13 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
private readonly FailsafeAnnotationsProducer _producer;
private readonly IGalleryService _galleryService;
private readonly IMediator _mediator;
private readonly IHardwareService _hardwareService;
private readonly IAuthProvider _authProvider;
private readonly IAzaionApi _api;
private readonly QueueConfig _queueConfig;
private Consumer _consumer = null!;
private readonly UIConfig _uiConfig;
private static readonly Guid SaveTaskId = Guid.NewGuid();
public AnnotationService(
IResourceLoader resourceLoader,
IDbFactory dbFactory,
FailsafeAnnotationsProducer producer,
IOptions<QueueConfig> queueConfig,
@@ -42,14 +40,13 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
IGalleryService galleryService,
IMediator mediator,
IHardwareService hardwareService,
IAuthProvider authProvider)
IAzaionApi api)
{
_dbFactory = dbFactory;
_producer = producer;
_galleryService = galleryService;
_mediator = mediator;
_hardwareService = hardwareService;
_authProvider = authProvider;
_api = api;
_queueConfig = queueConfig.Value;
_uiConfig = uiConfig.Value;
@@ -58,7 +55,7 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
private async Task Init(CancellationToken cancellationToken = default)
{
if (!_authProvider.CurrentUser.Role.IsValidator())
if (!_api.CurrentUser.Role.IsValidator())
return;
var consumerSystem = await StreamSystem.Create(new StreamSystemConfig
@@ -68,13 +65,11 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
Password = _queueConfig.ConsumerPassword
});
var offset = (await _dbFactory.Run(db => db.QueueOffsets.FirstOrDefaultAsync(
x => x.QueueName == Constants.MQ_ANNOTATIONS_QUEUE, token: cancellationToken))
)?.Offset ?? 0;
var offset = (ulong)(_api.CurrentUser.UserConfig?.QueueConfig?.AnnotationsOffset ?? 0);
_consumer = await Consumer.Create(new ConsumerConfig(consumerSystem, Constants.MQ_ANNOTATIONS_QUEUE)
{
Reference = _hardwareService.GetHardware().Hash,
Reference = _api.CurrentUser.Email,
OffsetSpec = new OffsetTypeOffset(offset + 1),
MessageHandler = async (_, _, context, message) =>
{
@@ -84,13 +79,13 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
.Set(x => x.Offset, context.Offset)
.UpdateAsync(token: cancellationToken));
await ThrottleExt.ThrottleRunAfter(() =>
await ThrottleExt.Throttle(() =>
{
_dbFactory.SaveToDisk();
return Task.CompletedTask;
}, SaveTaskId, TimeSpan.FromSeconds(5), cancellationToken);
}, TimeSpan.FromSeconds(10), cancellationToken);
if (msg.CreatedEmail == _authProvider.CurrentUser.Email) //Don't process messages by yourself
if (msg.CreatedEmail == _api.CurrentUser.Email) //Don't process messages by yourself
return;
await SaveAnnotationInner(
@@ -114,18 +109,18 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
{
a.Time = TimeSpan.FromMilliseconds(a.Milliseconds);
return await SaveAnnotationInner(DateTime.Now, a.OriginalMediaName, a.Time, a.Detections.ToList(),
SourceEnum.AI, new MemoryStream(a.Image), _authProvider.CurrentUser.Role, _authProvider.CurrentUser.Email, generateThumbnail: true, token: ct);
SourceEnum.AI, new MemoryStream(a.Image), _api.CurrentUser.Role, _api.CurrentUser.Email, generateThumbnail: true, token: ct);
}
//Manual
public async Task<Annotation> SaveAnnotation(string originalMediaName, TimeSpan time, List<Detection> detections, Stream? stream = null, CancellationToken token = default) =>
await SaveAnnotationInner(DateTime.UtcNow, originalMediaName, time, detections, SourceEnum.Manual, stream,
_authProvider.CurrentUser.Role, _authProvider.CurrentUser.Email, generateThumbnail: true, token: token);
_api.CurrentUser.Role, _api.CurrentUser.Email, generateThumbnail: true, token: token);
//Manual Validate existing
public async Task ValidateAnnotation(Annotation annotation, CancellationToken token = default) =>
await SaveAnnotationInner(DateTime.UtcNow, annotation.OriginalMediaName, annotation.Time, annotation.Detections.ToList(), SourceEnum.Manual, null,
_authProvider.CurrentUser.Role, _authProvider.CurrentUser.Email, token: token);
_api.CurrentUser.Role, _api.CurrentUser.Email, token: token);
// Manual save from Validators -> Validated -> stream: azaion-annotations-confirm
// AI, Manual save from Operators -> Created -> stream: azaion-annotations
@@ -199,11 +194,11 @@ public class AnnotationService : INotificationHandler<AnnotationsDeletedEvent>
await _producer.SendToInnerQueue(annotation, token);
await _mediator.Publish(new AnnotationCreatedEvent(annotation), token);
await ThrottleExt.ThrottleRunAfter(() =>
await ThrottleExt.Throttle(() =>
{
_dbFactory.SaveToDisk();
return Task.CompletedTask;
}, SaveTaskId, TimeSpan.FromSeconds(5), token);
}, TimeSpan.FromSeconds(5), token);
return annotation;
}
@@ -3,6 +3,7 @@ using System.IO;
using Azaion.Common.DTO;
using Azaion.Common.DTO.Config;
using Azaion.CommonSecurity;
using Azaion.CommonSecurity.DTO;
using Microsoft.Extensions.Options;
namespace Azaion.Common.Services;
+2 -1
View File
@@ -17,10 +17,11 @@ public interface IInferenceService
void StopInference();
}
public class InferenceService(ILogger<InferenceService> logger, IInferenceClient client, IOptions<AIRecognitionConfig> aiConfigOptions) : IInferenceService
public class InferenceService(ILogger<InferenceService> logger, IInferenceClient client, IAzaionApi azaionApi, IOptions<AIRecognitionConfig> aiConfigOptions) : IInferenceService
{
public async Task RunInference(List<string> mediaPaths, Func<AnnotationImage, Task> processAnnotation, CancellationToken detectToken = default)
{
client.Send(RemoteCommand.Create(CommandType.Login, azaionApi.Credentials));
var aiConfig = aiConfigOptions.Value;
aiConfig.Paths = mediaPaths;
@@ -7,6 +7,7 @@ using Azaion.Common.DTO;
using Azaion.Common.DTO.Config;
using Azaion.Common.Extensions;
using Azaion.CommonSecurity;
using Azaion.CommonSecurity.DTO;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;