gps matcher async

put cryptography lib to fixed version
fix race condition bug in queue handler
add lock to db writing and backup to file db on each write
This commit is contained in:
Alex Bezdieniezhnykh
2025-05-29 00:35:35 +03:00
parent 34ea821fb3
commit d842466594
12 changed files with 245 additions and 191 deletions
+38 -13
View File
@@ -1,15 +1,16 @@
using System.IO; using System.IO;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Threading;
using Azaion.Annotator.DTO; using Azaion.Annotator.DTO;
using Azaion.Common; using Azaion.Common;
using Azaion.Common.Database;
using Azaion.Common.DTO; using Azaion.Common.DTO;
using Azaion.Common.DTO.Config; using Azaion.Common.DTO.Config;
using Azaion.Common.Events; using Azaion.Common.Events;
using Azaion.Common.Extensions; using Azaion.Common.Extensions;
using Azaion.Common.Services; using Azaion.Common.Services;
using Azaion.CommonSecurity.DTO; using Azaion.CommonSecurity.DTO;
using Azaion.CommonSecurity.Services;
using LibVLCSharp.Shared; using LibVLCSharp.Shared;
using MediatR; using MediatR;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -27,7 +28,10 @@ public class AnnotatorEventHandler(
ILogger<AnnotatorEventHandler> logger, ILogger<AnnotatorEventHandler> logger,
IOptions<DirectoriesConfig> dirConfig, IOptions<DirectoriesConfig> dirConfig,
IOptions<AnnotationConfig> annotationConfig, IOptions<AnnotationConfig> annotationConfig,
IInferenceService inferenceService) IInferenceService inferenceService,
IDbFactory dbFactory,
IAzaionApi api,
FailsafeAnnotationsProducer producer)
: :
INotificationHandler<KeyEvent>, INotificationHandler<KeyEvent>,
INotificationHandler<AnnClassSelectedEvent>, INotificationHandler<AnnClassSelectedEvent>,
@@ -52,7 +56,7 @@ public class AnnotatorEventHandler(
{ Key.PageDown, PlaybackControlEnum.Next }, { Key.PageDown, PlaybackControlEnum.Next },
}; };
public async Task Handle(AnnClassSelectedEvent notification, CancellationToken cancellationToken) public async Task Handle(AnnClassSelectedEvent notification, CancellationToken ct)
{ {
SelectClass(notification.DetectionClass); SelectClass(notification.DetectionClass);
await Task.CompletedTask; await Task.CompletedTask;
@@ -66,7 +70,7 @@ public class AnnotatorEventHandler(
mainWindow.LvClasses.SelectNum(detClass.Id); mainWindow.LvClasses.SelectNum(detClass.Id);
} }
public async Task Handle(KeyEvent keyEvent, CancellationToken cancellationToken = default) public async Task Handle(KeyEvent keyEvent, CancellationToken ct = default)
{ {
if (keyEvent.WindowEnum != WindowEnum.Annotator) if (keyEvent.WindowEnum != WindowEnum.Annotator)
return; return;
@@ -82,7 +86,7 @@ public class AnnotatorEventHandler(
SelectClass((DetectionClass)mainWindow.LvClasses.DetectionDataGrid.Items[keyNumber.Value]!); SelectClass((DetectionClass)mainWindow.LvClasses.DetectionDataGrid.Items[keyNumber.Value]!);
if (_keysControlEnumDict.TryGetValue(key, out var value)) if (_keysControlEnumDict.TryGetValue(key, out var value))
await ControlPlayback(value, cancellationToken); await ControlPlayback(value, ct);
if (key == Key.R) if (key == Key.R)
await mainWindow.AutoDetect(); await mainWindow.AutoDetect();
@@ -91,10 +95,10 @@ public class AnnotatorEventHandler(
switch (key) switch (key)
{ {
case Key.VolumeMute when mediaPlayer.Volume == 0: case Key.VolumeMute when mediaPlayer.Volume == 0:
await ControlPlayback(PlaybackControlEnum.TurnOnVolume, cancellationToken); await ControlPlayback(PlaybackControlEnum.TurnOnVolume, ct);
break; break;
case Key.VolumeMute: case Key.VolumeMute:
await ControlPlayback(PlaybackControlEnum.TurnOffVolume, cancellationToken); await ControlPlayback(PlaybackControlEnum.TurnOffVolume, ct);
break; break;
case Key.Up: case Key.Up:
case Key.VolumeUp: case Key.VolumeUp:
@@ -112,9 +116,9 @@ public class AnnotatorEventHandler(
#endregion #endregion
} }
public async Task Handle(AnnotatorControlEvent notification, CancellationToken cancellationToken = default) public async Task Handle(AnnotatorControlEvent notification, CancellationToken ct = default)
{ {
await ControlPlayback(notification.PlaybackControl, cancellationToken); await ControlPlayback(notification.PlaybackControl, ct);
mainWindow.VideoView.Focus(); mainWindow.VideoView.Focus();
} }
@@ -201,7 +205,7 @@ public class AnnotatorEventHandler(
await Play(ct); await Play(ct);
} }
public async Task Handle(VolumeChangedEvent notification, CancellationToken cancellationToken) public async Task Handle(VolumeChangedEvent notification, CancellationToken ct)
{ {
ChangeVolume(notification.Volume); ChangeVolume(notification.Volume);
await Task.CompletedTask; await Task.CompletedTask;
@@ -277,7 +281,7 @@ public class AnnotatorEventHandler(
mainWindow.AddAnnotation(annotation); mainWindow.AddAnnotation(annotation);
} }
public Task Handle(AnnotationsDeletedEvent notification, CancellationToken cancellationToken) public async Task Handle(AnnotationsDeletedEvent notification, CancellationToken ct)
{ {
try try
{ {
@@ -306,16 +310,37 @@ public class AnnotatorEventHandler(
} }
} }
}); });
await dbFactory.DeleteAnnotations(notification.AnnotationNames, ct);
try
{
foreach (var name in notification.AnnotationNames)
{
File.Delete(Path.Combine(dirConfig.Value.ImagesDirectory, $"{name}{Constants.JPG_EXT}"));
File.Delete(Path.Combine(dirConfig.Value.LabelsDirectory, $"{name}{Constants.TXT_EXT}"));
File.Delete(Path.Combine(dirConfig.Value.ThumbnailsDirectory, $"{name}{Constants.THUMBNAIL_PREFIX}{Constants.JPG_EXT}"));
File.Delete(Path.Combine(dirConfig.Value.ResultsDirectory, $"{name}{Constants.RESULT_PREFIX}{Constants.JPG_EXT}"));
}
}
catch (Exception e)
{
logger.LogError(e, e.Message);
throw;
}
//Only validators can send Delete to the queue
if (!notification.FromQueue && api.CurrentUser.Role.IsValidator())
await producer.SendToInnerQueue(notification.AnnotationNames, AnnotationStatus.Deleted, ct);
} }
catch (Exception e) catch (Exception e)
{ {
logger.LogError(e, e.Message); logger.LogError(e, e.Message);
throw; throw;
} }
return Task.CompletedTask;
} }
public Task Handle(AnnotationAddedEvent e, CancellationToken cancellationToken) public Task Handle(AnnotationAddedEvent e, CancellationToken ct)
{ {
mainWindow.Dispatcher.Invoke(() => mainWindow.Dispatcher.Invoke(() =>
{ {
+51 -10
View File
@@ -1,10 +1,9 @@
using System.Data.SQLite; using System.Data.SQLite;
using System.Diagnostics;
using System.IO; using System.IO;
using Azaion.Common.DTO; using Azaion.Common.DTO;
using Azaion.Common.DTO.Config; using Azaion.Common.DTO.Config;
using Azaion.Common.Extensions;
using LinqToDB; using LinqToDB;
using LinqToDB.Data;
using LinqToDB.DataProvider.SQLite; using LinqToDB.DataProvider.SQLite;
using LinqToDB.Mapping; using LinqToDB.Mapping;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -16,13 +15,14 @@ namespace Azaion.Common.Database;
public interface IDbFactory public interface IDbFactory
{ {
Task<T> Run<T>(Func<AnnotationsDb, Task<T>> func); Task<T> Run<T>(Func<AnnotationsDb, Task<T>> func);
Task Run(Func<AnnotationsDb, Task> func); Task RunWrite(Func<AnnotationsDb, Task> func);
void SaveToDisk(); Task<T> RunWrite<T>(Func<AnnotationsDb, Task<T>> func);
Task DeleteAnnotations(List<string> annotationNames, CancellationToken cancellationToken = default); Task DeleteAnnotations(List<string> annotationNames, CancellationToken cancellationToken = default);
} }
public class DbFactory : IDbFactory public class DbFactory : IDbFactory
{ {
private readonly ILogger<DbFactory> _logger;
private readonly AnnotationConfig _annConfig; private readonly AnnotationConfig _annConfig;
private string MemoryConnStr => "Data Source=:memory:"; private string MemoryConnStr => "Data Source=:memory:";
@@ -33,8 +33,12 @@ public class DbFactory : IDbFactory
private readonly SQLiteConnection _fileConnection; private readonly SQLiteConnection _fileConnection;
private readonly DataOptions _fileDataOptions; private readonly DataOptions _fileDataOptions;
private static readonly SemaphoreSlim WriteSemaphore = new(1, 1);
private static readonly Guid SaveTaskId = Guid.NewGuid();
public DbFactory(IOptions<AnnotationConfig> annConfig, ILogger<DbFactory> logger) public DbFactory(IOptions<AnnotationConfig> annConfig, ILogger<DbFactory> logger)
{ {
_logger = logger;
_annConfig = annConfig.Value; _annConfig = annConfig.Value;
_memoryConnection = new SQLiteConnection(MemoryConnStr); _memoryConnection = new SQLiteConnection(MemoryConnStr);
@@ -79,26 +83,63 @@ public class DbFactory : IDbFactory
return await func(db); return await func(db);
} }
public async Task Run(Func<AnnotationsDb, Task> func) public async Task RunWrite(Func<AnnotationsDb, Task> func)
{
await WriteSemaphore.WaitAsync();
try
{ {
await using var db = new AnnotationsDb(_memoryDataOptions); await using var db = new AnnotationsDb(_memoryDataOptions);
await func(db); await func(db);
} ThrottleExt.Throttle(async () =>
public void SaveToDisk()
{ {
_memoryConnection.BackupDatabase(_fileConnection, "main", "main", -1, null, -1); _memoryConnection.BackupDatabase(_fileConnection, "main", "main", -1, null, -1);
await Task.CompletedTask;
}, SaveTaskId, TimeSpan.FromSeconds(5), true);
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
throw;
}
finally
{
WriteSemaphore.Release();
}
}
public async Task<T> RunWrite<T>(Func<AnnotationsDb, Task<T>> func)
{
await WriteSemaphore.WaitAsync();
try
{
await using var db = new AnnotationsDb(_memoryDataOptions);
var result = await func(db);
ThrottleExt.Throttle(async () =>
{
_memoryConnection.BackupDatabase(_fileConnection, "main", "main", -1, null, -1);
await Task.CompletedTask;
}, SaveTaskId, TimeSpan.FromSeconds(5), true);
return result;
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
throw;
}
finally
{
WriteSemaphore.Release();
}
} }
public async Task DeleteAnnotations(List<string> annotationNames, CancellationToken cancellationToken = default) public async Task DeleteAnnotations(List<string> annotationNames, CancellationToken cancellationToken = default)
{ {
await Run(async db => await RunWrite(async db =>
{ {
var detDeleted = await db.Detections.DeleteAsync(x => annotationNames.Contains(x.AnnotationName), token: cancellationToken); var detDeleted = await db.Detections.DeleteAsync(x => annotationNames.Contains(x.AnnotationName), token: cancellationToken);
var annDeleted = await db.Annotations.DeleteAsync(x => annotationNames.Contains(x.Name), token: cancellationToken); var annDeleted = await db.Annotations.DeleteAsync(x => annotationNames.Contains(x.Name), token: cancellationToken);
Console.WriteLine($"Deleted {detDeleted} detections, {annDeleted} annotations"); Console.WriteLine($"Deleted {detDeleted} detections, {annDeleted} annotations");
}); });
SaveToDisk();
} }
} }
+15
View File
@@ -0,0 +1,15 @@
using Polly;
public static class ResilienceExt
{
public static void WithRetry(this Action operation, int retryCount = 3, int delayMs = 150) =>
Policy.Handle<Exception>()
.WaitAndRetry(retryCount, num => TimeSpan.FromMilliseconds(num * delayMs),
(exception, timeSpan) => Console.WriteLine($"Exception: {exception}, TimeSpan: {timeSpan}"))
.Execute(operation);
public static TResult WithRetry<TResult>(this Func<TResult> operation, int retryCount = 3, int delayMs = 150) =>
Policy.Handle<Exception>()
.WaitAndRetry(retryCount, num => TimeSpan.FromMilliseconds(num * delayMs))
.Execute(operation);
}
+39 -48
View File
@@ -1,4 +1,5 @@
using System.Drawing.Imaging; using System.Drawing;
using System.Drawing.Imaging;
using System.IO; using System.IO;
using System.Net; using System.Net;
using Azaion.Common.Database; using Azaion.Common.Database;
@@ -21,7 +22,8 @@ using RabbitMQ.Stream.Client.Reliable;
namespace Azaion.Common.Services; namespace Azaion.Common.Services;
public class AnnotationService : IAnnotationService, INotificationHandler<AnnotationsDeletedEvent> // SHOULD BE ONLY ONE INSTANCE OF AnnotationService. Do not add ANY NotificationHandler to it!
public class AnnotationService : IAnnotationService
{ {
private readonly IDbFactory _dbFactory; private readonly IDbFactory _dbFactory;
private readonly FailsafeAnnotationsProducer _producer; private readonly FailsafeAnnotationsProducer _producer;
@@ -32,16 +34,16 @@ public class AnnotationService : IAnnotationService, INotificationHandler<Annota
private readonly QueueConfig _queueConfig; private readonly QueueConfig _queueConfig;
private Consumer _consumer = null!; private Consumer _consumer = null!;
private readonly UIConfig _uiConfig; private readonly UIConfig _uiConfig;
private readonly DirectoriesConfig _dirConfig; private readonly SemaphoreSlim _imageAccessSemaphore = new(1, 1);
private static readonly Guid SaveTaskId = Guid.NewGuid(); private readonly SemaphoreSlim _messageProcessingSemaphore = new(1, 1);
private static readonly Guid SaveQueueOffsetTaskId = Guid.NewGuid(); private static readonly Guid SaveQueueOffsetTaskId = Guid.NewGuid();
public AnnotationService( public AnnotationService(
IDbFactory dbFactory, IDbFactory dbFactory,
FailsafeAnnotationsProducer producer, FailsafeAnnotationsProducer producer,
IOptions<QueueConfig> queueConfig, IOptions<QueueConfig> queueConfig,
IOptions<UIConfig> uiConfig, IOptions<UIConfig> uiConfig,
IOptions<DirectoriesConfig> directoriesConfig,
IGalleryService galleryService, IGalleryService galleryService,
IMediator mediator, IMediator mediator,
IAzaionApi api, IAzaionApi api,
@@ -55,7 +57,6 @@ public class AnnotationService : IAnnotationService, INotificationHandler<Annota
_logger = logger; _logger = logger;
_queueConfig = queueConfig.Value; _queueConfig = queueConfig.Value;
_uiConfig = uiConfig.Value; _uiConfig = uiConfig.Value;
_dirConfig = directoriesConfig.Value;
Task.Run(async () => await InitQueueConsumer()).Wait(); Task.Run(async () => await InitQueueConsumer()).Wait();
} }
@@ -80,6 +81,7 @@ public class AnnotationService : IAnnotationService, INotificationHandler<Annota
OffsetSpec = new OffsetTypeOffset(offsets.AnnotationsOffset), OffsetSpec = new OffsetTypeOffset(offsets.AnnotationsOffset),
MessageHandler = async (_, _, context, message) => MessageHandler = async (_, _, context, message) =>
{ {
await _messageProcessingSemaphore.WaitAsync(cancellationToken);
try try
{ {
var email = (string)message.ApplicationProperties[nameof(User.Email)]!; var email = (string)message.ApplicationProperties[nameof(User.Email)]!;
@@ -100,7 +102,7 @@ public class AnnotationService : IAnnotationService, INotificationHandler<Annota
msg.Image == null ? null : new MemoryStream(msg.Image), msg.Image == null ? null : new MemoryStream(msg.Image),
msg.Role, msg.Role,
msg.Email, msg.Email,
fromQueue: true, context.Offset,
token: cancellationToken); token: cancellationToken);
} }
else else
@@ -124,6 +126,10 @@ public class AnnotationService : IAnnotationService, INotificationHandler<Annota
{ {
_logger.LogError(e, e.Message); _logger.LogError(e, e.Message);
} }
finally
{
_messageProcessingSemaphore.Release();
}
} }
}); });
} }
@@ -145,12 +151,12 @@ public class AnnotationService : IAnnotationService, INotificationHandler<Annota
List<Detection> detections, SourceEnum source, Stream? stream, List<Detection> detections, SourceEnum source, Stream? stream,
RoleEnum userRole, RoleEnum userRole,
string createdEmail, string createdEmail,
bool fromQueue = false, ulong? offset = null,
CancellationToken token = default) CancellationToken token = default)
{ {
var status = AnnotationStatus.Created; var status = AnnotationStatus.Created;
var fName = originalMediaName.ToTimeName(time); var fName = originalMediaName.ToTimeName(time);
var annotation = await _dbFactory.Run(async db => var annotation = await _dbFactory.RunWrite(async db =>
{ {
var ann = await db.Annotations var ann = await db.Annotations
.LoadWith(x => x.Detections) .LoadWith(x => x.Detections)
@@ -200,27 +206,40 @@ public class AnnotationService : IAnnotationService, INotificationHandler<Annota
return ann; return ann;
}); });
//Save image should be done in 1 thread only
await _imageAccessSemaphore.WaitAsync(token);
try
{
Image image = null!;
if (stream != null) if (stream != null)
{ {
var img = System.Drawing.Image.FromStream(stream); image = Image.FromStream(stream);
img.Save(annotation.ImagePath, ImageFormat.Jpeg); //todo: check png images coming from queue if (File.Exists(annotation.ImagePath))
ResilienceExt.WithRetry(() => File.Delete(annotation.ImagePath));
image.Save(annotation.ImagePath, ImageFormat.Jpeg); //todo: check png images coming from queue
} }
await YoloLabel.WriteToFile(detections, annotation.LabelPath, token); await YoloLabel.WriteToFile(detections, annotation.LabelPath, token);
await _galleryService.CreateThumbnail(annotation, token); await _galleryService.CreateThumbnail(annotation, image, token);
if (_uiConfig.GenerateAnnotatedImage) if (_uiConfig.GenerateAnnotatedImage)
await _galleryService.CreateAnnotatedImage(annotation, token); await _galleryService.CreateAnnotatedImage(annotation, image, token);
}
catch (Exception e)
{
_logger.LogError(e, $"Try to save {annotation.ImagePath}, Error: {e.Message}");
throw;
}
finally
{
_imageAccessSemaphore.Release();
}
await _mediator.Publish(new AnnotationCreatedEvent(annotation), token); await _mediator.Publish(new AnnotationCreatedEvent(annotation), token);
if (!fromQueue) //Send to queue only if we're not getting from queue already if (!offset.HasValue) //Send to queue only if we're not getting from queue already
await _producer.SendToInnerQueue([annotation.Name], status, token); await _producer.SendToInnerQueue([annotation.Name], status, token);
ThrottleExt.Throttle(async () =>
{
_dbFactory.SaveToDisk();
await Task.CompletedTask;
}, SaveTaskId, TimeSpan.FromSeconds(5), true);
return annotation; return annotation;
} }
@@ -230,7 +249,7 @@ public class AnnotationService : IAnnotationService, INotificationHandler<Annota
return; return;
var annNames = annotationNames.ToHashSet(); var annNames = annotationNames.ToHashSet();
await _dbFactory.Run(async db => await _dbFactory.RunWrite(async db =>
{ {
await db.Annotations await db.Annotations
.Where(x => annNames.Contains(x.Name)) .Where(x => annNames.Contains(x.Name))
@@ -241,34 +260,6 @@ public class AnnotationService : IAnnotationService, INotificationHandler<Annota
}); });
if (!fromQueue) if (!fromQueue)
await _producer.SendToInnerQueue(annotationNames, AnnotationStatus.Validated, token); await _producer.SendToInnerQueue(annotationNames, AnnotationStatus.Validated, token);
ThrottleExt.Throttle(async () =>
{
_dbFactory.SaveToDisk();
await Task.CompletedTask;
}, SaveTaskId, TimeSpan.FromSeconds(5), true);
}
public async Task Handle(AnnotationsDeletedEvent notification, CancellationToken ct)
{
await _dbFactory.DeleteAnnotations(notification.AnnotationNames, ct);
foreach (var name in notification.AnnotationNames)
{
File.Delete(Path.Combine(_dirConfig.ImagesDirectory, $"{name}{Constants.JPG_EXT}"));
File.Delete(Path.Combine(_dirConfig.LabelsDirectory, $"{name}{Constants.TXT_EXT}"));
File.Delete(Path.Combine(_dirConfig.ThumbnailsDirectory, $"{name}{Constants.THUMBNAIL_PREFIX}{Constants.JPG_EXT}"));
File.Delete(Path.Combine(_dirConfig.ResultsDirectory, $"{name}{Constants.RESULT_PREFIX}{Constants.JPG_EXT}"));
}
//Only validators can send Delete to the queue
if (!notification.FromQueue && _api.CurrentUser.Role.IsValidator())
await _producer.SendToInnerQueue(notification.AnnotationNames, AnnotationStatus.Deleted, ct);
ThrottleExt.Throttle(async () =>
{
_dbFactory.SaveToDisk();
await Task.CompletedTask;
}, SaveTaskId, TimeSpan.FromSeconds(5), true);
} }
} }
+8 -10
View File
@@ -62,7 +62,7 @@ public class FailsafeAnnotationsProducer
{ {
try try
{ {
var result = await _dbFactory.Run(async db => var (records, annotationsDict) = await _dbFactory.Run(async db =>
{ {
var records = await db.AnnotationsQueueRecords.OrderBy(x => x.DateTime).ToListAsync(token: ct); var records = await db.AnnotationsQueueRecords.OrderBy(x => x.DateTime).ToListAsync(token: ct);
var editedCreatedNames = records var editedCreatedNames = records
@@ -73,6 +73,8 @@ public class FailsafeAnnotationsProducer
var annotationsDict = await db.Annotations.LoadWith(x => x.Detections) var annotationsDict = await db.Annotations.LoadWith(x => x.Detections)
.Where(x => editedCreatedNames.Contains(x.Name)) .Where(x => editedCreatedNames.Contains(x.Name))
.ToDictionaryAsync(a => a.Name, token: ct); .ToDictionaryAsync(a => a.Name, token: ct);
return (records, annotationsDict);
});
var messages = new List<Message>(); var messages = new List<Message>();
foreach (var record in records) foreach (var record in records)
@@ -126,16 +128,12 @@ public class FailsafeAnnotationsProducer
} }
} }
return (messages, records); if (messages.Any())
});
if (result.messages.Any())
{ {
await _annotationProducer.Send(result.messages, CompressionType.Gzip); await _annotationProducer.Send(messages, CompressionType.Gzip);
var ids = result.records.Select(x => x.Id).ToList(); var ids = records.Select(x => x.Id).ToList();
var removed = await _dbFactory.Run(async db => await db.AnnotationsQueueRecords.DeleteAsync(x => ids.Contains(x.Id), token: ct)); var removed = await _dbFactory.RunWrite(async db => await db.AnnotationsQueueRecords.DeleteAsync(x => ids.Contains(x.Id), token: ct));
sent = true; sent = true;
_dbFactory.SaveToDisk();
} }
} }
catch (Exception e) catch (Exception e)
@@ -153,7 +151,7 @@ public class FailsafeAnnotationsProducer
{ {
if (_uiConfig.SilentDetection) if (_uiConfig.SilentDetection)
return; return;
await _dbFactory.Run(async db => await _dbFactory.RunWrite(async db =>
await db.InsertAsync(new AnnotationQueueRecord await db.InsertAsync(new AnnotationQueueRecord
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
+1 -2
View File
@@ -18,7 +18,6 @@ public class GpsMatcherService(IGpsMatcherClient gpsMatcherClient, ISatelliteDow
INotificationHandler<GPSMatcherResultEvent>, INotificationHandler<GPSMatcherResultEvent>,
INotificationHandler<GPSMatcherFinishedEvent> INotificationHandler<GPSMatcherFinishedEvent>
{ {
private readonly IGpsMatcherClient _gpsMatcherClient = gpsMatcherClient;
private readonly DirectoriesConfig _dirConfig = dirConfig.Value; private readonly DirectoriesConfig _dirConfig = dirConfig.Value;
private const int ZOOM_LEVEL = 18; private const int ZOOM_LEVEL = 18;
private const int POINTS_COUNT = 10; private const int POINTS_COUNT = 10;
@@ -69,7 +68,7 @@ public class GpsMatcherService(IGpsMatcherClient gpsMatcherClient, ISatelliteDow
.ToDictionary(x => x.Filename, x => x.Index); .ToDictionary(x => x.Filename, x => x.Index);
await satelliteTileDownloader.GetTiles(_currentLat, _currentLon, SATELLITE_RADIUS_M, ZOOM_LEVEL, _detectToken); await satelliteTileDownloader.GetTiles(_currentLat, _currentLon, SATELLITE_RADIUS_M, ZOOM_LEVEL, _detectToken);
_gpsMatcherClient.StartMatching(new StartMatchingEvent gpsMatcherClient.StartMatching(new StartMatchingEvent
{ {
ImagesCount = POINTS_COUNT, ImagesCount = POINTS_COUNT,
Latitude = _currentLat, Latitude = _currentLat,
+15 -14
View File
@@ -61,7 +61,7 @@ public class GalleryService(
{ {
foreach(var file in new DirectoryInfo(_dirConfig.ThumbnailsDirectory).GetFiles()) foreach(var file in new DirectoryInfo(_dirConfig.ThumbnailsDirectory).GetFiles())
file.Delete(); file.Delete();
await dbFactory.Run(async db => await dbFactory.RunWrite(async db =>
{ {
await db.Detections.DeleteAsync(x => true, token: cancellationToken); await db.Detections.DeleteAsync(x => true, token: cancellationToken);
await db.Annotations.DeleteAsync(x => true, token: cancellationToken); await db.Annotations.DeleteAsync(x => true, token: cancellationToken);
@@ -157,7 +157,7 @@ public class GalleryService(
if (!thumbnails.Contains(fName)) if (!thumbnails.Contains(fName))
await CreateThumbnail(annotation, cancellationToken); await CreateThumbnail(annotation, cancellationToken: cancellationToken);
} }
catch (Exception e) catch (Exception e)
@@ -198,24 +198,23 @@ public class GalleryService(
.Select(x => x.Value) .Select(x => x.Value)
.ToList(); .ToList();
await dbFactory.Run(async db => await dbFactory.RunWrite(async db =>
{ {
await db.BulkCopyAsync(copyOptions, annotationsToInsert); await db.BulkCopyAsync(copyOptions, annotationsToInsert);
await db.BulkCopyAsync(copyOptions, annotationsToInsert.SelectMany(x => x.Detections)); await db.BulkCopyAsync(copyOptions, annotationsToInsert.SelectMany(x => x.Detections));
}); });
dbFactory.SaveToDisk();
_updateLock.Release(); _updateLock.Release();
} }
} }
public async Task CreateThumbnail(Annotation annotation, CancellationToken cancellationToken = default) public async Task CreateThumbnail(Annotation annotation, Image? originalImage = null, CancellationToken cancellationToken = default)
{ {
try try
{ {
var width = (int)_thumbnailConfig.Size.Width; var width = (int)_thumbnailConfig.Size.Width;
var height = (int)_thumbnailConfig.Size.Height; var height = (int)_thumbnailConfig.Size.Height;
var originalImage = Image.FromStream(new MemoryStream(await File.ReadAllBytesAsync(annotation.ImagePath, cancellationToken))); originalImage ??= Image.FromStream(new MemoryStream(await File.ReadAllBytesAsync(annotation.ImagePath, cancellationToken)));
var bitmap = new Bitmap(width, height); var bitmap = new Bitmap(width, height);
@@ -282,10 +281,9 @@ public class GalleryService(
logger.LogError(e, e.Message); logger.LogError(e, e.Message);
} }
} }
public async Task CreateAnnotatedImage(Annotation annotation, Image? originalImage = null, CancellationToken token = default)
public async Task CreateAnnotatedImage(Annotation annotation, CancellationToken token)
{ {
var originalImage = Image.FromStream(new MemoryStream(await File.ReadAllBytesAsync(annotation.ImagePath, token))); originalImage ??= Image.FromStream(new MemoryStream(await File.ReadAllBytesAsync(annotation.ImagePath, token)));
using var g = Graphics.FromImage(originalImage); using var g = Graphics.FromImage(originalImage);
foreach (var detection in annotation.Detections) foreach (var detection in annotation.Detections)
@@ -299,17 +297,20 @@ public class GalleryService(
var label = detection.Confidence >= 0.995 ? detClass.UIName : $"{detClass.UIName}: {detection.Confidence * 100:F0}%"; var label = detection.Confidence >= 0.995 ? detClass.UIName : $"{detClass.UIName}: {detection.Confidence * 100:F0}%";
g.DrawTextBox(label, new PointF((float)(det.X + det.Width / 2.0), (float)(det.Y - 24)), brush, Brushes.Black); g.DrawTextBox(label, new PointF((float)(det.X + det.Width / 2.0), (float)(det.Y - 24)), brush, Brushes.Black);
} }
originalImage.Save(Path.Combine(_dirConfig.ResultsDirectory, $"{annotation.Name}{Constants.RESULT_PREFIX}.jpg"), ImageFormat.Jpeg);
var imagePath = Path.Combine(_dirConfig.ResultsDirectory, $"{annotation.Name}{Constants.RESULT_PREFIX}.jpg");
if (File.Exists(imagePath))
ResilienceExt.WithRetry(() => File.Delete(imagePath));
originalImage.Save(imagePath, ImageFormat.Jpeg);
} }
} }
public interface IGalleryService public interface IGalleryService
{ {
event ThumbnailsUpdatedEventHandler? ThumbnailsUpdate; event ThumbnailsUpdatedEventHandler? ThumbnailsUpdate;
double ProcessedThumbnailsPercentage { get; set; } Task CreateThumbnail(Annotation annotation, Image? originalImage = null, CancellationToken cancellationToken = default);
Task CreateThumbnail(Annotation annotation, CancellationToken cancellationToken = default);
Task RefreshThumbnails(); Task RefreshThumbnails();
Task ClearThumbnails(CancellationToken cancellationToken = default); Task ClearThumbnails(CancellationToken cancellationToken = default);
Task CreateAnnotatedImage(Annotation annotation, Image? originalImage = null, CancellationToken token = default);
Task CreateAnnotatedImage(Annotation annotation, CancellationToken token);
} }
+10 -17
View File
@@ -32,22 +32,15 @@ public class StartMatchingEvent
public class GpsMatcherClient : IGpsMatcherClient public class GpsMatcherClient : IGpsMatcherClient
{ {
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly GpsDeniedClientConfig _gpsDeniedClientConfig; private readonly string _requestAddress;
private string _requestAddress;
private readonly RequestSocket _requestSocket = new(); private readonly RequestSocket _requestSocket = new();
private string _subscriberAddress; private readonly string _subscriberAddress;
private readonly SubscriberSocket _subscriberSocket = new(); private readonly SubscriberSocket _subscriberSocket = new();
public GpsMatcherClient(IMediator mediator, IOptions<GpsDeniedClientConfig> gpsDeniedClientConfig) public GpsMatcherClient(IMediator mediator, IOptions<GpsDeniedClientConfig> gpsConfig)
{ {
_mediator = mediator; _mediator = mediator;
_gpsDeniedClientConfig = gpsDeniedClientConfig.Value;
Start();
}
private void Start(CancellationToken ct = default)
{
try try
{ {
using var process = new Process(); using var process = new Process();
@@ -71,16 +64,16 @@ public class GpsMatcherClient : IGpsMatcherClient
//throw; //throw;
} }
_requestAddress = $"tcp://{_gpsDeniedClientConfig.ZeroMqHost}:{_gpsDeniedClientConfig.ZeroMqPort}"; _requestAddress = $"tcp://{gpsConfig.Value.ZeroMqHost}:{gpsConfig.Value.ZeroMqPort}";
_requestSocket.Connect(_requestAddress); _requestSocket.Connect(_requestAddress);
_subscriberAddress = $"tcp://{_gpsDeniedClientConfig.ZeroMqHost}:{_gpsDeniedClientConfig.ZeroMqSubscriberPort}"; _subscriberAddress = $"tcp://{gpsConfig.Value.ZeroMqHost}:{gpsConfig.Value.ZeroMqSubscriberPort}";
_subscriberSocket.Connect(_subscriberAddress); _subscriberSocket.Connect(_subscriberAddress);
_subscriberSocket.Subscribe(""); _subscriberSocket.Subscribe("");
_subscriberSocket.ReceiveReady += async (_, e) => await ProcessClientCommand(e.Socket, ct); _subscriberSocket.ReceiveReady += async (_, e) => await ProcessClientCommand(e.Socket);
} }
private async Task ProcessClientCommand(NetMQSocket socket, CancellationToken ct) private async Task ProcessClientCommand(NetMQSocket socket)
{ {
while (socket.TryReceiveFrameString(TimeSpan.Zero, out var str)) while (socket.TryReceiveFrameString(TimeSpan.Zero, out var str))
{ {
@@ -90,10 +83,10 @@ public class GpsMatcherClient : IGpsMatcherClient
switch (str) switch (str)
{ {
case "FINISHED": case "FINISHED":
await _mediator.Publish(new GPSMatcherFinishedEvent(), ct); await _mediator.Publish(new GPSMatcherFinishedEvent());
break; break;
case "OK": case "OK":
await _mediator.Publish(new GPSMatcherJobAcceptedEvent(), ct); await _mediator.Publish(new GPSMatcherJobAcceptedEvent());
break; break;
default: default:
var parts = str.Split(','); var parts = str.Split(',');
@@ -107,7 +100,7 @@ public class GpsMatcherClient : IGpsMatcherClient
Latitude = double.Parse(parts[2]), Latitude = double.Parse(parts[2]),
Longitude = double.Parse(parts[3]), Longitude = double.Parse(parts[3]),
MatchType = parts[4] MatchType = parts[4]
}, ct); });
break; break;
} }
} }
@@ -25,9 +25,7 @@ namespace Azaion.Dataset.Controls
return Math.Max(0, calculatedWidth); // Ensure width is not negative return Math.Max(0, calculatedWidth); // Ensure width is not negative
} }
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
{ [value];
throw new NotImplementedException();
}
} }
} }
+9 -15
View File
@@ -106,21 +106,6 @@ public partial class DatasetExplorer
new List<DetectionClass> { new() {Id = -1, Name = "All", ShortName = "All"}} new List<DetectionClass> { new() {Id = -1, Name = "All", ShortName = "All"}}
.Concat(_annotationConfig.DetectionClasses)); .Concat(_annotationConfig.DetectionClasses));
LvClasses.Init(AllDetectionClasses); LvClasses.Init(AllDetectionClasses);
_dbFactory.Run(db =>
{
var allAnnotations = db.Annotations
.LoadWith(x => x.Detections)
.OrderBy(x => x.AnnotationStatus)
.ThenByDescending(x => x.CreatedDate)
.ToList();
foreach (var annotation in allAnnotations)
AddAnnotationToDict(annotation);
return null!;
});
DataContext = this;
} }
private async void OnLoaded(object sender, RoutedEventArgs e) private async void OnLoaded(object sender, RoutedEventArgs e)
@@ -138,6 +123,15 @@ public partial class DatasetExplorer
ExplorerEditor.CurrentAnnClass = LvClasses.CurrentDetectionClass ?? _annotationConfig.DetectionClasses.First(); ExplorerEditor.CurrentAnnClass = LvClasses.CurrentDetectionClass ?? _annotationConfig.DetectionClasses.First();
var allAnnotations = await _dbFactory.Run(async db =>
await db.Annotations.LoadWith(x => x.Detections)
.OrderBy(x => x.AnnotationStatus)
.ThenByDescending(x => x.CreatedDate)
.ToListAsync());
foreach (var annotation in allAnnotations)
AddAnnotationToDict(annotation);
await ReloadThumbnails(); await ReloadThumbnails();
LoadClassDistribution(); LoadClassDistribution();
+1 -1
View File
@@ -3,7 +3,7 @@ Cython
opencv-python==4.10.0.84 opencv-python==4.10.0.84
numpy numpy
onnxruntime-gpu onnxruntime-gpu
cryptography cryptography==44.0.2
psutil psutil
msgpack msgpack
pyjwt pyjwt
-1
View File
@@ -139,7 +139,6 @@ public partial class MainSuite
private void OnFormClosed(object? sender, EventArgs e) private void OnFormClosed(object? sender, EventArgs e)
{ {
_configUpdater.Save(_appConfig); _configUpdater.Save(_appConfig);
_dbFactory.SaveToDisk();
foreach (var window in _openedWindows) foreach (var window in _openedWindows)
window.Value.Close(); window.Value.Close();