From 8b94837f18b2acf73971b9a284ed25d6a1012ad5 Mon Sep 17 00:00:00 2001 From: Alex Bezdieniezhnykh Date: Sat, 28 Dec 2024 15:51:27 +0200 Subject: [PATCH] add offset fixes add visual validation border and validate functionality --- Azaion.Annotator/Annotator.xaml.cs | 26 +++--- Azaion.Annotator/AnnotatorEventHandler.cs | 4 +- Azaion.Annotator/DTO/MediatrEvents.cs | 9 +- Azaion.Common/Constants.cs | 3 - Azaion.Common/DTO/AnnotationCreatedEvent.cs | 3 +- Azaion.Common/DTO/AnnotationImageView.cs | 2 + Azaion.Common/DTO/Annotator2ControlEvent.cs | 13 +++ Azaion.Common/DTO/Label.cs | 2 +- Azaion.Common/DTO/PlaybackControlEnum.cs | 3 +- .../DTO/Queue/AnnotationCreatedMessage.cs | 3 +- Azaion.Common/{DTO => Database}/Annotation.cs | 10 +-- Azaion.Common/Database/AnnotationName.cs | 6 ++ Azaion.Common/Database/AnnotationsDb.cs | 1 + Azaion.Common/Database/DbFactory.cs | 15 ++++ Azaion.Common/Database/QueueOffset.cs | 7 ++ .../Extensions/ThrottleExtensions.cs | 26 ++++-- Azaion.Common/Services/AnnotationService.cs | 55 ++++++++---- Azaion.Common/Services/GalleryService.cs | 5 -- .../DTO}/IEnumerableExtensions.cs | 0 Azaion.CommonSecurity/DTO/RoleEnum.cs | 10 ++- Azaion.CommonSecurity/SecurityConstants.cs | 1 + .../Services/ResourceLoader.cs | 2 +- Azaion.Dataset/DatasetExplorer.xaml | 84 ++++++++++++------- Azaion.Dataset/DatasetExplorer.xaml.cs | 47 +++++++---- Azaion.Dataset/DatasetExplorerEventHandler.cs | 29 +++++-- Azaion.Suite/App.xaml.cs | 9 +- Azaion.Suite/MainSuite.xaml.cs | 4 +- 27 files changed, 251 insertions(+), 128 deletions(-) create mode 100644 Azaion.Common/DTO/Annotator2ControlEvent.cs rename Azaion.Common/{DTO => Database}/Annotation.cs (89%) create mode 100644 Azaion.Common/Database/AnnotationName.cs create mode 100644 Azaion.Common/Database/QueueOffset.cs rename {Azaion.Common/Extensions => Azaion.CommonSecurity/DTO}/IEnumerableExtensions.cs (100%) diff --git a/Azaion.Annotator/Annotator.xaml.cs b/Azaion.Annotator/Annotator.xaml.cs index 7f99275..ac5f528 100644 --- a/Azaion.Annotator/Annotator.xaml.cs +++ b/Azaion.Annotator/Annotator.xaml.cs @@ -151,7 +151,7 @@ public partial class Annotator } }; - LvFiles.MouseDoubleClick += async (_, _) => await _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.Play)); + LvFiles.MouseDoubleClick += async (_, _) => await _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.Play)); LvClasses.SelectionChanged += (_, _) => { @@ -238,7 +238,7 @@ public partial class Annotator _appConfig.AnnotationConfig.LeftPanelWidth = MainGrid.ColumnDefinitions.FirstOrDefault()!.Width.Value; _appConfig.AnnotationConfig.RightPanelWidth = MainGrid.ColumnDefinitions.LastOrDefault()!.Width.Value; - await ThrottleExt.Throttle(() => + await ThrottleExt.ThrottleRunFirst(() => { _configUpdater.Save(_appConfig); return Task.CompletedTask; @@ -478,21 +478,21 @@ public partial class Annotator private void PlayClick(object sender, RoutedEventArgs e) { - _mediator.Publish(new PlaybackControlEvent(_mediaPlayer.CanPause ? PlaybackControlEnum.Pause : PlaybackControlEnum.Play)); + _mediator.Publish(new AnnotatorControlEvent(_mediaPlayer.CanPause ? PlaybackControlEnum.Pause : PlaybackControlEnum.Play)); } - private void PauseClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.Pause)); - private void StopClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.Stop)); + private void PauseClick(object sender, RoutedEventArgs e) => _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.Pause)); + private void StopClick(object sender, RoutedEventArgs e) => _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.Stop)); - private void PreviousFrameClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.PreviousFrame)); - private void NextFrameClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.NextFrame)); + private void PreviousFrameClick(object sender, RoutedEventArgs e) => _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.PreviousFrame)); + private void NextFrameClick(object sender, RoutedEventArgs e) => _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.NextFrame)); - private void SaveAnnotationsClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.SaveAnnotations)); + private void SaveAnnotationsClick(object sender, RoutedEventArgs e) => _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.SaveAnnotations)); - private void RemoveSelectedClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.RemoveSelectedAnns)); - private void RemoveAllClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.RemoveAllAnns)); - private void TurnOffVolume(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.TurnOffVolume)); - private void TurnOnVolume(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.TurnOnVolume)); + private void RemoveSelectedClick(object sender, RoutedEventArgs e) => _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.RemoveSelectedAnns)); + private void RemoveAllClick(object sender, RoutedEventArgs e) => _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.RemoveAllAnns)); + private void TurnOffVolume(object sender, RoutedEventArgs e) => _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.TurnOffVolume)); + private void TurnOnVolume(object sender, RoutedEventArgs e) => _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.TurnOnVolume)); private void OpenHelpWindowClick(object sender, RoutedEventArgs e) { @@ -517,7 +517,7 @@ public partial class Annotator if (LvFiles.SelectedIndex == -1) LvFiles.SelectedIndex = 0; - await _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.Play)); + await _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.Play)); _mediaPlayer.SetPause(true); var manualCancellationSource = new CancellationTokenSource(); diff --git a/Azaion.Annotator/AnnotatorEventHandler.cs b/Azaion.Annotator/AnnotatorEventHandler.cs index f366394..76f5932 100644 --- a/Azaion.Annotator/AnnotatorEventHandler.cs +++ b/Azaion.Annotator/AnnotatorEventHandler.cs @@ -26,7 +26,7 @@ public class AnnotatorEventHandler( : INotificationHandler, INotificationHandler, - INotificationHandler, + INotificationHandler, INotificationHandler { private const int STEP = 20; @@ -105,7 +105,7 @@ public class AnnotatorEventHandler( #endregion } - public async Task Handle(PlaybackControlEvent notification, CancellationToken cancellationToken = default) + public async Task Handle(AnnotatorControlEvent notification, CancellationToken cancellationToken = default) { await ControlPlayback(notification.PlaybackControl, cancellationToken); mainWindow.VideoView.Focus(); diff --git a/Azaion.Annotator/DTO/MediatrEvents.cs b/Azaion.Annotator/DTO/MediatrEvents.cs index 6af6d8c..f8a7f04 100644 --- a/Azaion.Annotator/DTO/MediatrEvents.cs +++ b/Azaion.Annotator/DTO/MediatrEvents.cs @@ -1,14 +1,7 @@ -using System.Windows.Input; -using Azaion.Common.DTO; -using MediatR; +using MediatR; namespace Azaion.Annotator.DTO; -public class PlaybackControlEvent(PlaybackControlEnum playbackControlEnum) : INotification -{ - public PlaybackControlEnum PlaybackControl { get; set; } = playbackControlEnum; -} - public class VolumeChangedEvent(int volume) : INotification { public int Volume { get; set; } = volume; diff --git a/Azaion.Common/Constants.cs b/Azaion.Common/Constants.cs index b8a2572..aa3cb22 100644 --- a/Azaion.Common/Constants.cs +++ b/Azaion.Common/Constants.cs @@ -85,11 +85,8 @@ public class Constants #region Queue - public const string MQ_DIRECT_TYPE = "direct"; public const string MQ_ANNOTATIONS_QUEUE = "azaion-annotations"; public const string MQ_ANNOTATIONS_CONFIRM_QUEUE = "azaion-annotations-confirm"; - public const string ANNOTATION_PRODUCER = "AnnotationsProducer"; - public const string ANNOTATION_CONFIRM_PRODUCER = "AnnotationsConfirmProducer"; #endregion diff --git a/Azaion.Common/DTO/AnnotationCreatedEvent.cs b/Azaion.Common/DTO/AnnotationCreatedEvent.cs index c4e755f..99539a3 100644 --- a/Azaion.Common/DTO/AnnotationCreatedEvent.cs +++ b/Azaion.Common/DTO/AnnotationCreatedEvent.cs @@ -1,4 +1,5 @@ -using MediatR; +using Azaion.Common.Database; +using MediatR; namespace Azaion.Common.DTO; diff --git a/Azaion.Common/DTO/AnnotationImageView.cs b/Azaion.Common/DTO/AnnotationImageView.cs index 2e74cee..983b866 100644 --- a/Azaion.Common/DTO/AnnotationImageView.cs +++ b/Azaion.Common/DTO/AnnotationImageView.cs @@ -2,6 +2,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Windows.Media.Imaging; +using Azaion.Common.Database; using Azaion.Common.Extensions; namespace Azaion.Common.DTO; @@ -27,6 +28,7 @@ public class AnnotationImageView(Annotation annotation) : INotifyPropertyChanged } public string ImageName => Path.GetFileName(Annotation.ImagePath); + public bool IsSeed => Annotation.AnnotationStatus == AnnotationStatus.Created; public void Delete() { diff --git a/Azaion.Common/DTO/Annotator2ControlEvent.cs b/Azaion.Common/DTO/Annotator2ControlEvent.cs new file mode 100644 index 0000000..ccbac36 --- /dev/null +++ b/Azaion.Common/DTO/Annotator2ControlEvent.cs @@ -0,0 +1,13 @@ +using MediatR; + +namespace Azaion.Common.DTO; + +public class AnnotatorControlEvent(PlaybackControlEnum playbackControlEnum) : INotification +{ + public PlaybackControlEnum PlaybackControl { get; set; } = playbackControlEnum; +} + +public class DatasetExplorerControlEvent(PlaybackControlEnum playbackControlEnum) : INotification +{ + public PlaybackControlEnum PlaybackControl { get; set; } = playbackControlEnum; +} diff --git a/Azaion.Common/DTO/Label.cs b/Azaion.Common/DTO/Label.cs index cf27ebe..a481efe 100644 --- a/Azaion.Common/DTO/Label.cs +++ b/Azaion.Common/DTO/Label.cs @@ -186,7 +186,7 @@ public class YoloLabel : Label public class Detection : YoloLabel { - public string AnnotationName { get; set; } + public string AnnotationName { get; set; } = null!; public double? Probability { get; set; } //For db diff --git a/Azaion.Common/DTO/PlaybackControlEnum.cs b/Azaion.Common/DTO/PlaybackControlEnum.cs index 88bb508..808c635 100644 --- a/Azaion.Common/DTO/PlaybackControlEnum.cs +++ b/Azaion.Common/DTO/PlaybackControlEnum.cs @@ -15,5 +15,6 @@ public enum PlaybackControlEnum TurnOnVolume = 10, Previous = 11, Next = 12, - Close = 13 + Close = 13, + ValidateAnnotations = 15 } \ No newline at end of file diff --git a/Azaion.Common/DTO/Queue/AnnotationCreatedMessage.cs b/Azaion.Common/DTO/Queue/AnnotationCreatedMessage.cs index 57cf87f..b68a7da 100644 --- a/Azaion.Common/DTO/Queue/AnnotationCreatedMessage.cs +++ b/Azaion.Common/DTO/Queue/AnnotationCreatedMessage.cs @@ -1,4 +1,5 @@ -using Azaion.CommonSecurity.DTO; +using Azaion.Common.Database; +using Azaion.CommonSecurity.DTO; namespace Azaion.Common.DTO.Queue; using MessagePack; diff --git a/Azaion.Common/DTO/Annotation.cs b/Azaion.Common/Database/Annotation.cs similarity index 89% rename from Azaion.Common/DTO/Annotation.cs rename to Azaion.Common/Database/Annotation.cs index 9fbfd68..3a155f5 100644 --- a/Azaion.Common/DTO/Annotation.cs +++ b/Azaion.Common/Database/Annotation.cs @@ -1,11 +1,10 @@ using System.IO; -using System.Windows.Media.Imaging; +using Azaion.Common.DTO; using Azaion.Common.DTO.Config; using Azaion.Common.DTO.Queue; -using Azaion.Common.Extensions; using Azaion.CommonSecurity.DTO; -namespace Azaion.Common.DTO; +namespace Azaion.Common.Database; public class Annotation { @@ -46,9 +45,4 @@ public enum AnnotationStatus None = 0, Created = 10, Validated = 20 -} - -public class AnnotationName -{ - public string Name { get; set; } = null!; } \ No newline at end of file diff --git a/Azaion.Common/Database/AnnotationName.cs b/Azaion.Common/Database/AnnotationName.cs new file mode 100644 index 0000000..709066f --- /dev/null +++ b/Azaion.Common/Database/AnnotationName.cs @@ -0,0 +1,6 @@ +namespace Azaion.Common.Database; + +public class AnnotationName +{ + public string Name { get; set; } = null!; +} \ No newline at end of file diff --git a/Azaion.Common/Database/AnnotationsDb.cs b/Azaion.Common/Database/AnnotationsDb.cs index 92df84c..69ed6ae 100644 --- a/Azaion.Common/Database/AnnotationsDb.cs +++ b/Azaion.Common/Database/AnnotationsDb.cs @@ -9,4 +9,5 @@ public class AnnotationsDb(DataOptions dataOptions) : DataConnection(dataOptions public ITable Annotations => this.GetTable(); public ITable AnnotationsQueue => this.GetTable(); public ITable Detections => this.GetTable(); + public ITable QueueOffsets => this.GetTable(); } \ No newline at end of file diff --git a/Azaion.Common/Database/DbFactory.cs b/Azaion.Common/Database/DbFactory.cs index ce72dd4..7614e4a 100644 --- a/Azaion.Common/Database/DbFactory.cs +++ b/Azaion.Common/Database/DbFactory.cs @@ -4,6 +4,7 @@ using System.IO; using Azaion.Common.DTO; using Azaion.Common.DTO.Config; using LinqToDB; +using LinqToDB.Data; using LinqToDB.DataProvider.SQLite; using LinqToDB.Mapping; using Microsoft.Extensions.Logging; @@ -63,6 +64,20 @@ public class DbFactory : IDbFactory db.CreateTable(); db.CreateTable(); db.CreateTable(); + db.CreateTable(); + db.QueueOffsets.BulkCopy(new List + { + new() + { + Offset = 0, + QueueName = Constants.MQ_ANNOTATIONS_QUEUE + }, + new() + { + Offset = 0, + QueueName = Constants.MQ_ANNOTATIONS_CONFIRM_QUEUE + } + }); } public async Task Run(Func> func) diff --git a/Azaion.Common/Database/QueueOffset.cs b/Azaion.Common/Database/QueueOffset.cs new file mode 100644 index 0000000..1fe3d0f --- /dev/null +++ b/Azaion.Common/Database/QueueOffset.cs @@ -0,0 +1,7 @@ +namespace Azaion.Common.Database; + +public class QueueOffset +{ + public string QueueName { get; set; } = null!; + public ulong Offset { get; set; } +} \ No newline at end of file diff --git a/Azaion.Common/Extensions/ThrottleExtensions.cs b/Azaion.Common/Extensions/ThrottleExtensions.cs index 06f4f50..7a38aee 100644 --- a/Azaion.Common/Extensions/ThrottleExtensions.cs +++ b/Azaion.Common/Extensions/ThrottleExtensions.cs @@ -2,18 +2,34 @@ public static class ThrottleExt { - private static bool _throttleOn; - public static async Task Throttle(this Func func, TimeSpan? throttleTime = null, CancellationToken cancellationToken = default) + private static bool _throttleRunFirstOn; + public static async Task ThrottleRunFirst(this Func func, TimeSpan? throttleTime = null, CancellationToken cancellationToken = default) { - if (_throttleOn) + if (_throttleRunFirstOn) return; - _throttleOn = true; + _throttleRunFirstOn = true; await func(); _ = Task.Run(async () => { await Task.Delay(throttleTime ?? TimeSpan.FromMilliseconds(500), cancellationToken); - _throttleOn = false; + _throttleRunFirstOn = false; }, cancellationToken); } + + private static bool _throttleRunAfter; + public static async Task ThrottleRunAfter(this Func func, TimeSpan? throttleTime = null, CancellationToken cancellationToken = default) + { + if (_throttleRunAfter) + return; + + _throttleRunAfter = true; + _ = Task.Run(async () => + { + await Task.Delay(throttleTime ?? TimeSpan.FromMilliseconds(500), cancellationToken); + await func(); + _throttleRunAfter = false; + }, cancellationToken); + } + } \ No newline at end of file diff --git a/Azaion.Common/Services/AnnotationService.cs b/Azaion.Common/Services/AnnotationService.cs index c6e3a9f..c05491f 100644 --- a/Azaion.Common/Services/AnnotationService.cs +++ b/Azaion.Common/Services/AnnotationService.cs @@ -5,6 +5,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 Azaion.CommonSecurity.Services; using LinqToDB; @@ -25,6 +26,7 @@ public class AnnotationService private readonly FailsafeAnnotationsProducer _producer; private readonly IGalleryService _galleryService; private readonly IMediator _mediator; + private readonly IHardwareService _hardwareService; private readonly QueueConfig _queueConfig; private Consumer _consumer = null!; @@ -33,19 +35,21 @@ public class AnnotationService FailsafeAnnotationsProducer producer, IOptions queueConfig, IGalleryService galleryService, - IMediator mediator) + IMediator mediator, + IHardwareService hardwareService) { _apiClient = apiClient; _dbFactory = dbFactory; _producer = producer; _galleryService = galleryService; _mediator = mediator; + _hardwareService = hardwareService; _queueConfig = queueConfig.Value; Task.Run(async () => await Init()).Wait(); } - private async Task Init() + private async Task Init(CancellationToken cancellationToken = default) { var consumerSystem = await StreamSystem.Create(new StreamSystemConfig { @@ -53,11 +57,28 @@ public class AnnotationService UserName = _queueConfig.ConsumerUsername, Password = _queueConfig.ConsumerPassword }); + var offset = (await _dbFactory.Run(db => db.QueueOffsets.FirstOrDefaultAsync( + x => x.QueueName == Constants.MQ_ANNOTATIONS_QUEUE, token: cancellationToken)) + )?.Offset ?? 0; _consumer = await Consumer.Create(new ConsumerConfig(consumerSystem, Constants.MQ_ANNOTATIONS_QUEUE) { - OffsetSpec = new OffsetTypeFirst(), - MessageHandler = async (stream, _, _, message) => - await Consume(MessagePackSerializer.Deserialize(message.Data.Contents)), + Reference = _hardwareService.GetHardware().Hash, + OffsetSpec = new OffsetTypeOffset(offset + 1), + MessageHandler = async (stream, consumer, context, message) => + { + await Consume(MessagePackSerializer.Deserialize(message.Data.Contents), cancellationToken); + await _dbFactory.Run(async db => await db.QueueOffsets + .Where(x => x.QueueName == Constants.MQ_ANNOTATIONS_QUEUE) + .Set(x => x.Offset, context.Offset) + .UpdateAsync(token: cancellationToken)); + + await ThrottleExt.ThrottleRunAfter(() => + { + _dbFactory.SaveToDisk(); + return Task.CompletedTask; + }, TimeSpan.FromSeconds(3), cancellationToken); + + } }); } @@ -65,6 +86,10 @@ public class AnnotationService public async Task SaveAnnotation(string fName, string imageExtension, List detections, SourceEnum source, Stream? stream = null, CancellationToken token = default) => await SaveAnnotationInner(DateTime.UtcNow, fName, imageExtension, detections, source, stream, _apiClient.User.Role, _apiClient.User.Email, token); + //Manual + 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, token); + //Queue (only from operators) public async Task Consume(AnnotationCreatedMessage message, CancellationToken cancellationToken = default) { @@ -84,24 +109,20 @@ public class AnnotationService } private async Task SaveAnnotationInner(DateTime createdDate, string fName, string imageExtension, List detections, SourceEnum source, Stream? stream, - RoleEnum createdRole, + RoleEnum userRole, string createdEmail, CancellationToken token = default) { //Flow for roles: - // Operator: - // sourceEnum: (manual, ai) - // Validator: - // sourceEnum: (manual) if was in received.json then else - // sourceEnum: (queue, AI) if queue CreatedMessage with the same user - do nothing Add to received.json + // Operator or (AI from any role) -> Created + // Validator, Admin & Manual -> Validated - var classes = detections.Select(x => x.ClassNumber).Distinct().ToList() ?? []; AnnotationStatus status; var annotation = await _dbFactory.Run(async db => { var ann = await db.Annotations.FirstOrDefaultAsync(x => x.Name == fName, token: token); - status = ann?.AnnotationStatus == AnnotationStatus.Created && createdRole == RoleEnum.Validator + status = userRole.IsValidator() && source == SourceEnum.Manual ? AnnotationStatus.Validated : AnnotationStatus.Created; @@ -110,7 +131,6 @@ public class AnnotationService if (ann != null) await db.Annotations .Where(x => x.Name == fName) - .Set(x => x.Classes, classes) .Set(x => x.Source, source) .Set(x => x.AnnotationStatus, status) .UpdateAsync(token: token); @@ -122,7 +142,7 @@ public class AnnotationService Name = fName, ImageExtension = imageExtension, CreatedEmail = createdEmail, - CreatedRole = createdRole, + CreatedRole = userRole, AnnotationStatus = status, Source = source, Detections = detections @@ -142,5 +162,10 @@ public class AnnotationService await _producer.SendToQueue(annotation, token); await _mediator.Publish(new AnnotationCreatedEvent(annotation), token); + await ThrottleExt.ThrottleRunAfter(() => + { + _dbFactory.SaveToDisk(); + return Task.CompletedTask; + }, TimeSpan.FromSeconds(5), token); } } \ No newline at end of file diff --git a/Azaion.Common/Services/GalleryService.cs b/Azaion.Common/Services/GalleryService.cs index cc97cc5..2dc8b75 100644 --- a/Azaion.Common/Services/GalleryService.cs +++ b/Azaion.Common/Services/GalleryService.cs @@ -150,11 +150,6 @@ public class GalleryService( }; await dbFactory.Run(async db => { - var xx = missedAnnotations.GroupBy(x => x.Name) - .Where(gr => gr.Count() > 1) - .ToList(); - foreach (var gr in xx) - Console.WriteLine(gr.Key); await db.BulkCopyAsync(copyOptions, missedAnnotations); await db.BulkCopyAsync(copyOptions, missedAnnotations.SelectMany(x => x.Detections)); }); diff --git a/Azaion.Common/Extensions/IEnumerableExtensions.cs b/Azaion.CommonSecurity/DTO/IEnumerableExtensions.cs similarity index 100% rename from Azaion.Common/Extensions/IEnumerableExtensions.cs rename to Azaion.CommonSecurity/DTO/IEnumerableExtensions.cs diff --git a/Azaion.CommonSecurity/DTO/RoleEnum.cs b/Azaion.CommonSecurity/DTO/RoleEnum.cs index b1b518e..ea8a062 100644 --- a/Azaion.CommonSecurity/DTO/RoleEnum.cs +++ b/Azaion.CommonSecurity/DTO/RoleEnum.cs @@ -1,4 +1,6 @@ -namespace Azaion.CommonSecurity.DTO; +using Azaion.Common.Extensions; + +namespace Azaion.CommonSecurity.DTO; public enum RoleEnum { @@ -10,3 +12,9 @@ public enum RoleEnum ResourceUploader = 50, //Uploading dll and ai models ApiAdmin = 1000 //everything } + +public static class RoleEnumExtensions +{ + public static bool IsValidator(this RoleEnum role) => + role.In(RoleEnum.Validator, RoleEnum.Admin, RoleEnum.ApiAdmin); +} diff --git a/Azaion.CommonSecurity/SecurityConstants.cs b/Azaion.CommonSecurity/SecurityConstants.cs index 9f2bba3..2ad3b56 100644 --- a/Azaion.CommonSecurity/SecurityConstants.cs +++ b/Azaion.CommonSecurity/SecurityConstants.cs @@ -4,6 +4,7 @@ public class SecurityConstants { public const string CONFIG_PATH = "config.json"; + public const string DUMMY_DIR = "dummy"; #region ApiConfig public const string DEFAULT_API_URL = "https://api.azaion.com/"; diff --git a/Azaion.CommonSecurity/Services/ResourceLoader.cs b/Azaion.CommonSecurity/Services/ResourceLoader.cs index d390b4f..645d16a 100644 --- a/Azaion.CommonSecurity/Services/ResourceLoader.cs +++ b/Azaion.CommonSecurity/Services/ResourceLoader.cs @@ -31,7 +31,7 @@ public class ResourceLoader(AzaionApiClient api, ApiCredentials credentials) : I { Console.WriteLine(e); var currentLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; - var dllPath = Path.Combine(currentLocation, "dummy", $"{assemblyName}.dll"); + var dllPath = Path.Combine(currentLocation, SecurityConstants.DUMMY_DIR, $"{assemblyName}.dll"); return Assembly.LoadFile(dllPath); } } diff --git a/Azaion.Dataset/DatasetExplorer.xaml b/Azaion.Dataset/DatasetExplorer.xaml index 9e3257e..a4c0b10 100644 --- a/Azaion.Dataset/DatasetExplorer.xaml +++ b/Azaion.Dataset/DatasetExplorer.xaml @@ -13,22 +13,37 @@ - - - - - - - - + + + + + + + + + + + + + @@ -109,20 +124,29 @@ - База іконок: - - - - + diff --git a/Azaion.Dataset/DatasetExplorer.xaml.cs b/Azaion.Dataset/DatasetExplorer.xaml.cs index 89cc93b..2856126 100644 --- a/Azaion.Dataset/DatasetExplorer.xaml.cs +++ b/Azaion.Dataset/DatasetExplorer.xaml.cs @@ -1,5 +1,4 @@ using System.Collections.ObjectModel; -using System.IO; using System.Windows; using System.Windows.Input; using System.Windows.Media; @@ -8,6 +7,8 @@ using Azaion.Common.Database; using Azaion.Common.DTO; using Azaion.Common.DTO.Config; using Azaion.Common.Services; +using Azaion.CommonSecurity.DTO; +using Azaion.CommonSecurity.Services; using LinqToDB; using MediatR; using Microsoft.Extensions.Logging; @@ -26,13 +27,15 @@ public partial class DatasetExplorer : INotificationHandler> _annotationsDict; public ObservableCollection SelectedAnnotations { get; set; } = new(); - private ObservableCollection AllAnnotationClasses { get; set; } = new(); + public ObservableCollection AllAnnotationClasses { get; set; } = new(); - public Dictionary LabelsCache { get; set; } = new(); + private Dictionary LabelsCache { get; set; } = new(); private int _tempSelectedClassIdx = 0; private readonly IGalleryService _galleryService; private readonly IDbFactory _dbFactory; + private readonly IMediator _mediator; + private readonly AzaionApiClient _apiClient; public bool ThumbnailLoading { get; set; } @@ -44,13 +47,17 @@ public partial class DatasetExplorer : INotificationHandler logger, IGalleryService galleryService, FormState formState, - IDbFactory dbFactory) + IDbFactory dbFactory, + IMediator mediator, + AzaionApiClient apiClient) { _directoriesConfig = directoriesConfig.Value; _annotationConfig = annotationConfig.Value; _logger = logger; _galleryService = galleryService; _dbFactory = dbFactory; + _mediator = mediator; + _apiClient = apiClient; InitializeComponent(); Loaded += OnLoaded; @@ -73,14 +80,11 @@ public partial class DatasetExplorer : INotificationHandler { StatusText.Text = $"Обрано: {ThumbnailsView.SelectedItems.Count} | {ThumbnailsView.SelectedIndex} / {SelectedAnnotations.Count}"; + ValidateBtn.Visibility = ThumbnailsView.SelectedItems.Cast().Any(x => x.IsSeed) + ? Visibility.Visible + : Visibility.Hidden; }; - ExplorerEditor.GetTimeFunc = () => Constants.GetTime(CurrentAnnotation!.Annotation.ImagePath); - galleryService.ThumbnailsUpdate += thumbnailsPercentage => - { - Dispatcher.Invoke(() => RefreshThumbBar.Value = thumbnailsPercentage); - }; - } private async void OnLoaded(object sender, RoutedEventArgs e) @@ -135,7 +139,6 @@ public partial class DatasetExplorer : INotificationHandler - AddThumbnail(notification.Annotation); -} \ No newline at end of file + private async void ValidateAnnotationsClick(object sender, RoutedEventArgs e) + { + try + { + await _mediator.Publish(new DatasetExplorerControlEvent(PlaybackControlEnum.ValidateAnnotations)); + } + catch (Exception ex) + { + _logger.LogError(ex, ex.Message); + } + } +} diff --git a/Azaion.Dataset/DatasetExplorerEventHandler.cs b/Azaion.Dataset/DatasetExplorerEventHandler.cs index d837673..f7e23a5 100644 --- a/Azaion.Dataset/DatasetExplorerEventHandler.cs +++ b/Azaion.Dataset/DatasetExplorerEventHandler.cs @@ -1,26 +1,32 @@ using System.IO; using System.Windows.Input; using Azaion.Common.DTO; -using Azaion.Common.DTO.Config; using Azaion.Common.DTO.Queue; using Azaion.Common.Services; using MediatR; -using Microsoft.Extensions.Options; namespace Azaion.Dataset; public class DatasetExplorerEventHandler( DatasetExplorer datasetExplorer, - AnnotationService annotationService) : INotificationHandler + AnnotationService annotationService) + : INotificationHandler, + INotificationHandler { private readonly Dictionary _keysControlEnumDict = new() { { Key.Enter, PlaybackControlEnum.SaveAnnotations }, { Key.Delete, PlaybackControlEnum.RemoveSelectedAnns }, { Key.X, PlaybackControlEnum.RemoveAllAnns }, - { Key.Escape, PlaybackControlEnum.Close } + { Key.Escape, PlaybackControlEnum.Close }, + { Key.A, PlaybackControlEnum.ValidateAnnotations} }; + public async Task Handle(DatasetExplorerControlEvent notification, CancellationToken cancellationToken) + { + await HandleControl(notification.PlaybackControl, cancellationToken); + } + public async Task Handle(KeyEvent keyEvent, CancellationToken cancellationToken) { if (keyEvent.WindowEnum != WindowEnum.DatasetExplorer) @@ -37,11 +43,11 @@ public class DatasetExplorerEventHandler( else { if (datasetExplorer.Switcher.SelectedIndex == 1 && _keysControlEnumDict.TryGetValue(key, out var value)) - await HandleControl(value); + await HandleControl(value, cancellationToken); } } - private async Task HandleControl(PlaybackControlEnum controlEnum) + private async Task HandleControl(PlaybackControlEnum controlEnum, CancellationToken cancellationToken = default) { switch (controlEnum) { @@ -55,7 +61,7 @@ public class DatasetExplorerEventHandler( var detections = datasetExplorer.ExplorerEditor.CurrentDetections .Select(x => new Detection(fName, x.GetLabel(datasetExplorer.ExplorerEditor.RenderSize))) .ToList(); - await annotationService.SaveAnnotation(fName, extension, detections, SourceEnum.Manual); + await annotationService.SaveAnnotation(fName, extension, detections, SourceEnum.Manual, token: cancellationToken); datasetExplorer.SwitchTab(toEditor: false); break; case PlaybackControlEnum.RemoveSelectedAnns: @@ -67,6 +73,15 @@ public class DatasetExplorerEventHandler( case PlaybackControlEnum.Close: datasetExplorer.SwitchTab(toEditor: false); break; + case PlaybackControlEnum.ValidateAnnotations: + var annotations = datasetExplorer.ThumbnailsView.SelectedItems.Cast() + .Select(x => x.Annotation) + .ToList(); + foreach (var annotation in annotations) + { + await annotationService.ValidateAnnotation(annotation, cancellationToken); + } + break; } } } diff --git a/Azaion.Suite/App.xaml.cs b/Azaion.Suite/App.xaml.cs index a31cb75..1bb4322 100644 --- a/Azaion.Suite/App.xaml.cs +++ b/Azaion.Suite/App.xaml.cs @@ -142,16 +142,9 @@ public partial class App { var args = (KeyEventArgs)e; var keyEvent = new KeyEvent(sender, args, _formState.ActiveWindow); - _ = ThrottleExt.Throttle(() => _mediator.Publish(keyEvent), TimeSpan.FromMilliseconds(50)); + _ = ThrottleExt.ThrottleRunFirst(() => _mediator.Publish(keyEvent), TimeSpan.FromMilliseconds(50)); } - private readonly Dictionary _uiElementToWindowEnum = new() - { - { "LibVLCSharp.WPF.ForegroundWindow", WindowEnum.Annotator }, - { "Azaion.Annotator.Annotator", WindowEnum.Annotator }, - { "Azaion.Dataset.DatasetExplorer", WindowEnum.DatasetExplorer } - }; - protected override async void OnExit(ExitEventArgs e) { base.OnExit(e); diff --git a/Azaion.Suite/MainSuite.xaml.cs b/Azaion.Suite/MainSuite.xaml.cs index 9552c29..1203f75 100644 --- a/Azaion.Suite/MainSuite.xaml.cs +++ b/Azaion.Suite/MainSuite.xaml.cs @@ -118,11 +118,11 @@ public partial class MainSuite private async Task SaveUserSettings() { - await ThrottleExt.Throttle(() => + await ThrottleExt.ThrottleRunFirst(() => { _configUpdater.Save(_appConfig); return Task.CompletedTask; - }, TimeSpan.FromSeconds(5)); + }, TimeSpan.FromSeconds(2)); } private void OnFormClosed(object? sender, EventArgs e)