mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 17:46:36 +00:00
failsafe load dlls
add user config queue offsets throttle improvements
This commit is contained in:
+43
-12
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user