From 1bc1d81fde07f8c65e9d6df057107f95406f6ea1 Mon Sep 17 00:00:00 2001 From: Alex Bezdieniezhnykh Date: Wed, 15 Jan 2025 16:41:42 +0200 Subject: [PATCH] small fixes, renames --- Azaion.Annotator/Annotator.xaml.cs | 111 +++++++++--------- Azaion.Annotator/AnnotatorEventHandler.cs | 27 ++++- .../Extensions/VLCFrameExtractor.cs | 2 +- Azaion.Annotator/YOLODetector.cs | 10 +- Azaion.Common/DTO/FormState.cs | 5 +- Azaion.Common/DTO/MediaFileInfo.cs | 2 + Azaion.Common/Database/Annotation.cs | 15 ++- Azaion.Common/Database/DbFactory.cs | 14 ++- Azaion.Common/Extensions/BitmapExtensions.cs | 8 +- Azaion.Common/Services/AnnotationService.cs | 42 +++---- Azaion.Common/Services/FailsafeProducer.cs | 6 +- .../Services/ResourceLoader.cs | 35 +----- Azaion.Dataset/DatasetExplorer.xaml.cs | 42 +------ Azaion.Dataset/DatasetExplorerEventHandler.cs | 39 +++++- Azaion.Suite/App.xaml.cs | 35 +++++- Azaion.Test/DictTest.cs | 22 ++++ 16 files changed, 234 insertions(+), 181 deletions(-) diff --git a/Azaion.Annotator/Annotator.xaml.cs b/Azaion.Annotator/Annotator.xaml.cs index f5d36f5..9142ef5 100644 --- a/Azaion.Annotator/Annotator.xaml.cs +++ b/Azaion.Annotator/Annotator.xaml.cs @@ -28,7 +28,7 @@ using MediaPlayer = LibVLCSharp.Shared.MediaPlayer; namespace Azaion.Annotator; -public partial class Annotator : INotificationHandler +public partial class Annotator { private readonly AppConfig _appConfig; private readonly LibVLC _libVLC; @@ -52,8 +52,8 @@ public partial class Annotator : INotificationHandler private readonly TimeSpan _thresholdAfter = TimeSpan.FromMilliseconds(300); - private ObservableCollection AllMediaFiles { get; set; } = new(); - private ObservableCollection FilteredMediaFiles { get; set; } = new(); + public ObservableCollection AllMediaFiles { get; set; } = new(); + public ObservableCollection FilteredMediaFiles { get; set; } = new(); public IntervalTree TimedAnnotations { get; set; } = new(); private AutodetectDialog _autoDetectDialog = new() { Topmost = true }; @@ -96,7 +96,7 @@ public partial class Annotator : INotificationHandler try { _appConfig.DirectoriesConfig.VideosDirectory = TbFolder.Text; - ReloadFiles(); + await ReloadFiles(); await SaveUserSettings(); } catch (Exception e) @@ -335,24 +335,12 @@ public partial class Annotator : INotificationHandler _formState.AnnotationResults.Insert(index, new AnnotationResult(_appConfig.AnnotationConfig.DetectionClassesDict, annotation)); } - private void ReloadFiles() + private async Task ReloadFiles() { var dir = new DirectoryInfo(_appConfig.DirectoriesConfig.VideosDirectory); if (!dir.Exists) return; - var labelNames = new DirectoryInfo(_appConfig.DirectoriesConfig.LabelsDirectory).GetFiles() - .Select(x => - { - var name = Path.GetFileNameWithoutExtension(x.Name); - return name.Length > 8 - ? name[..^7] - : name; - }) - .GroupBy(x => x) - .Select(gr => gr.Key) - .ToDictionary(x => x); - var videoFiles = dir.GetFiles(_appConfig.AnnotationConfig.VideoFormats.ToArray()).Select(x => { using var media = new Media(_libVLC, x.FullName); @@ -361,22 +349,32 @@ public partial class Annotator : INotificationHandler { Name = x.Name, Path = x.FullName, - MediaType = MediaTypes.Video, - HasAnnotations = labelNames.ContainsKey(Path.GetFileNameWithoutExtension(x.Name).Replace(" ", "")) + MediaType = MediaTypes.Video }; media.ParsedChanged += (_, _) => fInfo.Duration = TimeSpan.FromMilliseconds(media.Duration); return fInfo; }).ToList(); - var imageFiles = dir.GetFiles(_appConfig.AnnotationConfig.ImageFormats.ToArray()).Select(x => new MediaFileInfo - { - Name = x.Name, - Path = x.FullName, - MediaType = MediaTypes.Image, - HasAnnotations = labelNames.ContainsKey(Path.GetFileNameWithoutExtension(x.Name).Replace(" ", "")) - }); + var imageFiles = dir.GetFiles(_appConfig.AnnotationConfig.ImageFormats.ToArray()) + .Select(x => new MediaFileInfo + { + Name = x.Name, + Path = x.FullName, + MediaType = MediaTypes.Image + }); + var allFiles = videoFiles.Concat(imageFiles).ToList(); - AllMediaFiles = new ObservableCollection(videoFiles.Concat(imageFiles).ToList()); + var allFileNames = allFiles.Select(x => x.FName).ToList(); + + var labelsDict = await _dbFactory.Run(async db => await db.Annotations + .GroupBy(x => x.Name.Substring(0, x.Name.Length - 7)) + .Where(x => allFileNames.Contains(x.Key)) + .ToDictionaryAsync(x => x.Key, x => x.Key)); + + foreach (var mediaFile in allFiles) + mediaFile.HasAnnotations = labelsDict.ContainsKey(mediaFile.FName); + + AllMediaFiles = new ObservableCollection(allFiles); LvFiles.ItemsSource = AllMediaFiles; BlinkHelp(AllMediaFiles.Count == 0 @@ -406,6 +404,8 @@ public partial class Annotator : INotificationHandler _mediaPlayer.SetPause(true); _mediaPlayer.Time = timeMilliseconds; VideoSlider.Value = _mediaPlayer.Position * 100; + + StatusClock.Text = $"{TimeSpan.FromMilliseconds(_mediaPlayer.Time):mm\\:ss} / {_formState.CurrentVideoLength:mm\\:ss}"; } private void SeekTo(TimeSpan time) => @@ -435,7 +435,7 @@ public partial class Annotator : INotificationHandler _appConfig.DirectoriesConfig.VideosDirectory = dlg.FileName; TbFolder.Text = dlg.FileName; - ReloadFiles(); + await ReloadFiles(); await SaveUserSettings(); } @@ -443,6 +443,7 @@ public partial class Annotator : INotificationHandler { FilteredMediaFiles = new ObservableCollection(AllMediaFiles.Where(x => x.Name.ToLower().Contains(TbFilter.Text.ToLower())).ToList()); LvFiles.ItemsSource = FilteredMediaFiles; + LvFiles.ItemsSource = FilteredMediaFiles; } private void PlayClick(object sender, RoutedEventArgs e) @@ -487,7 +488,7 @@ public partial class Annotator : INotificationHandler LvFiles.SelectedIndex = 0; await _mediator.Publish(new AnnotatorControlEvent(PlaybackControlEnum.Play)); - _mediaPlayer.SetPause(true); + _mediaPlayer.Stop(); var manualCancellationSource = new CancellationTokenSource(); var token = manualCancellationSource.Token; @@ -571,28 +572,34 @@ public partial class Annotator : INotificationHandler Console.WriteLine($"Detect time: {timeframe.Time}"); try { - var fName = _formState.GetTimeName(timeframe.Time); - var detections = await _aiDetector.Detect(fName, timeframe.Stream, token); - var isValid = IsValidDetection(timeframe.Time, detections); + var fName = _formState.GetTimeName(timeframe.Time); + var detections = await _aiDetector.Detect(fName, timeframe.Stream, token); - if (timeframe.Time.TotalSeconds > prevSeekTime + 1) + var isValid = IsValidDetection(timeframe.Time, detections); + Console.WriteLine($"Detection time: {timeframe.Time}"); + + var log = string.Join(Environment.NewLine, detections.Select(det => + $"{_appConfig.AnnotationConfig.DetectionClassesDict[det.ClassNumber].Name}: " + + $"xy=({det.CenterX:F2},{det.CenterY:F2}), " + + $"size=({det.Width:F2}, {det.Height:F2}), " + + $"prob: {det.Probability:F1}%")); + + log = $"Detection time: {timeframe.Time}, Valid: {isValid}. {Environment.NewLine} {log}"; + Dispatcher.Invoke(() => _autoDetectDialog.Log(log)); + + if (timeframe.Time.TotalMilliseconds > prevSeekTime + 250) { Dispatcher.Invoke(() => SeekTo(timeframe.Time)); - prevSeekTime = timeframe.Time.TotalSeconds; + prevSeekTime = timeframe.Time.TotalMilliseconds; if (!isValid) //Show frame anyway { - var bitmap = new BitmapImage(); - bitmap.BeginInit(); - timeframe.Stream.Seek(0, SeekOrigin.Begin); - bitmap.StreamSource = timeframe.Stream; - bitmap.CacheOption = BitmapCacheOption.OnLoad; - bitmap.EndInit(); - bitmap.Freeze(); - Dispatcher.Invoke(() => { Editor.RemoveAllAnns(); - Editor.Background = new ImageBrush { ImageSource = bitmap }; + Editor.Background = new ImageBrush + { + ImageSource = timeframe.Stream.OpenImage() + }; }); } } @@ -602,11 +609,12 @@ public partial class Annotator : INotificationHandler mediaInfo.HasAnnotations = true; await ProcessDetection(timeframe, ".jpg", detections, token); + await timeframe.Stream.DisposeAsync(); } catch (Exception ex) { - _logger.LogError(ex, ex.Message); - await manualCancellationSource.CancelAsync(); + _logger.LogError(ex, ex.Message); + await manualCancellationSource.CancelAsync(); } } } @@ -665,7 +673,6 @@ public partial class Annotator : INotificationHandler { try { - var time = timeframe.Time; var fName = _formState.GetTimeName(timeframe.Time); var annotation = await _annotationService.SaveAnnotation(fName, imageExtension, detections, SourceEnum.AI, timeframe.Stream, token); @@ -682,8 +689,6 @@ public partial class Annotator : INotificationHandler $"prob: {det.Probability:F1}%")); Dispatcher.Invoke(() => _autoDetectDialog.Log(log)); - - } catch (Exception e) { @@ -691,14 +696,4 @@ public partial class Annotator : INotificationHandler } }); } - - public async Task Handle(AnnotationsDeletedEvent notification, CancellationToken cancellationToken) - { - var annResDict = _formState.AnnotationResults.ToDictionary(x => x.Annotation.Name, x => x); - foreach (var ann in notification.Annotations) - { - _formState.AnnotationResults.Remove(annResDict[ann.Name]); - TimedAnnotations.Remove(ann); - } - } } diff --git a/Azaion.Annotator/AnnotatorEventHandler.cs b/Azaion.Annotator/AnnotatorEventHandler.cs index 89c02ae..6b7016a 100644 --- a/Azaion.Annotator/AnnotatorEventHandler.cs +++ b/Azaion.Annotator/AnnotatorEventHandler.cs @@ -28,7 +28,8 @@ public class AnnotatorEventHandler( INotificationHandler, INotificationHandler, INotificationHandler, - INotificationHandler + INotificationHandler, + INotificationHandler { private const int STEP = 20; private const int LARGE_STEP = 5000; @@ -269,4 +270,28 @@ public class AnnotatorEventHandler( var annotation = await annotationService.SaveAnnotation(fName, imageExtension, currentDetections, SourceEnum.Manual, token: cancellationToken); mainWindow.AddAnnotation(annotation); } + + public async Task Handle(AnnotationsDeletedEvent notification, CancellationToken cancellationToken) + { + var annResDict = formState.AnnotationResults.ToDictionary(x => x.Annotation.Name, x => x); + foreach (var ann in notification.Annotations) + { + if (!annResDict.TryGetValue(ann.Name, out var value)) + continue; + + formState.AnnotationResults.Remove(value); + mainWindow.TimedAnnotations.Remove(ann); + } + + if (formState.AnnotationResults.Count == 0) + { + var media = mainWindow.AllMediaFiles.FirstOrDefault(x => x.Name == formState.CurrentMedia?.Name); + if (media != null) + { + media.HasAnnotations = false; + mainWindow.LvFiles.Items.Refresh(); + } + + } + } } diff --git a/Azaion.Annotator/Extensions/VLCFrameExtractor.cs b/Azaion.Annotator/Extensions/VLCFrameExtractor.cs index 7c52943..6dd4a32 100644 --- a/Azaion.Annotator/Extensions/VLCFrameExtractor.cs +++ b/Azaion.Annotator/Extensions/VLCFrameExtractor.cs @@ -73,7 +73,7 @@ public class VLCFrameExtractor(LibVLC libVLC, IOptions conf using var outputImage = surface.Snapshot(); using var data = outputImage.Encode(SKEncodedImageFormat.Jpeg, 85); - using var ms = new MemoryStream(); + var ms = new MemoryStream(); data.SaveTo(ms); yield return (frameInfo.Time, ms); diff --git a/Azaion.Annotator/YOLODetector.cs b/Azaion.Annotator/YOLODetector.cs index 883cf6c..2aba93a 100644 --- a/Azaion.Annotator/YOLODetector.cs +++ b/Azaion.Annotator/YOLODetector.cs @@ -1,9 +1,8 @@ -using System.IO; -using Azaion.Annotator.DTO; +using System.Diagnostics; +using System.IO; using Azaion.Annotator.Extensions; using Azaion.Common.DTO; using Azaion.Common.DTO.Config; -using Azaion.Common.Services; using Azaion.CommonSecurity.Services; using Compunet.YoloV8; using Microsoft.Extensions.Options; @@ -34,11 +33,10 @@ public class YOLODetector(IOptions recognitionConfig, IReso } imageStream.Seek(0, SeekOrigin.Begin); - var image = Image.Load(imageStream); + + using var image = Image.Load(imageStream); var result = await _predictor.DetectAsync(image); - var imageSize = new System.Windows.Size(image.Width, image.Height); - var detections = result.Select(d => { var label = new YoloLabel(new CanvasLabel(d.Name.Id, d.Bounds.X, d.Bounds.Y, d.Bounds.Width, d.Bounds.Height), imageSize, imageSize); diff --git a/Azaion.Common/DTO/FormState.cs b/Azaion.Common/DTO/FormState.cs index a6dcf7d..94b8146 100644 --- a/Azaion.Common/DTO/FormState.cs +++ b/Azaion.Common/DTO/FormState.cs @@ -1,5 +1,4 @@ using System.Collections.ObjectModel; -using System.IO; using System.Windows; namespace Azaion.Common.DTO; @@ -7,9 +6,7 @@ namespace Azaion.Common.DTO; public class FormState { public MediaFileInfo? CurrentMedia { get; set; } - public string VideoName => string.IsNullOrEmpty(CurrentMedia?.Name) - ? "" - : Path.GetFileNameWithoutExtension(CurrentMedia.Name).Replace(" ", ""); + public string VideoName => CurrentMedia?.FName ?? ""; public string CurrentMrl { get; set; } = null!; public Size CurrentVideoSize { get; set; } diff --git a/Azaion.Common/DTO/MediaFileInfo.cs b/Azaion.Common/DTO/MediaFileInfo.cs index fcaf6bc..8aa3bd5 100644 --- a/Azaion.Common/DTO/MediaFileInfo.cs +++ b/Azaion.Common/DTO/MediaFileInfo.cs @@ -8,4 +8,6 @@ public class MediaFileInfo public string DurationStr => $"{Duration:h\\:mm\\:ss}"; public bool HasAnnotations { get; set; } public MediaTypes MediaType { get; set; } + + public string FName => System.IO.Path.GetFileNameWithoutExtension(Name).Replace(" ", ""); } \ No newline at end of file diff --git a/Azaion.Common/Database/Annotation.cs b/Azaion.Common/Database/Annotation.cs index 03351fd..dc7813b 100644 --- a/Azaion.Common/Database/Annotation.cs +++ b/Azaion.Common/Database/Annotation.cs @@ -32,13 +32,15 @@ public class Annotation public double Lat { get; set; } public double Lon { get; set; } - public List Classes => Detections.Select(x => x.ClassNumber).ToList(); - public string ImagePath => Path.Combine(_imagesDir, $"{Name}{ImageExtension}"); - public string LabelPath => Path.Combine(_labelsDir, $"{Name}.txt"); - public string ThumbPath => Path.Combine(_thumbDir, $"{Name}{Constants.THUMBNAIL_PREFIX}.jpg"); + #region Calculated + public List Classes => Detections.Select(x => x.ClassNumber).ToList(); + public string ImagePath => Path.Combine(_imagesDir, $"{Name}{ImageExtension}"); + public string LabelPath => Path.Combine(_labelsDir, $"{Name}.txt"); + public string ThumbPath => Path.Combine(_thumbDir, $"{Name}{Constants.THUMBNAIL_PREFIX}.jpg"); + public string OriginalMediaName => $"{Name[..^7]}"; - private TimeSpan? _time; - public TimeSpan Time + private TimeSpan? _time; + public TimeSpan Time { get { @@ -60,6 +62,7 @@ public class Annotation return _time.Value; } } + #endregion Calculated } diff --git a/Azaion.Common/Database/DbFactory.cs b/Azaion.Common/Database/DbFactory.cs index a9f27bc..bd6b97a 100644 --- a/Azaion.Common/Database/DbFactory.cs +++ b/Azaion.Common/Database/DbFactory.cs @@ -39,10 +39,10 @@ public class DbFactory : IDbFactory _memoryConnection = new SQLiteConnection(MemoryConnStr); _memoryConnection.Open(); _memoryDataOptions = new DataOptions() - .UseDataProvider(SQLiteTools.GetDataProvider()) - .UseConnection(_memoryConnection) - .UseMappingSchema(AnnotationsDbSchemaHolder.MappingSchema); - //.UseTracing(TraceLevel.Info, t => logger.LogInformation(t.SqlText)); + .UseDataProvider(SQLiteTools.GetDataProvider()) + .UseConnection(_memoryConnection) + .UseMappingSchema(AnnotationsDbSchemaHolder.MappingSchema) + ;//.UseTracing(TraceLevel.Info, t => logger.LogInformation(t.SqlText)); _fileConnection = new SQLiteConnection(FileConnStr); @@ -50,7 +50,6 @@ public class DbFactory : IDbFactory .UseDataProvider(SQLiteTools.GetDataProvider()) .UseConnection(_fileConnection) .UseMappingSchema(AnnotationsDbSchemaHolder.MappingSchema); - _ = _fileDataOptions.UseTracing(TraceLevel.Info, t => logger.LogInformation(t.SqlText)); if (!File.Exists(_annConfig.AnnotationsDbFile)) CreateDb(); @@ -122,6 +121,11 @@ public static class AnnotationsDbSchemaHolder .HasTableName(Constants.ANNOTATIONS_TABLENAME) .HasPrimaryKey(x => x.Name) .Ignore(x => x.Time) + .Ignore(x => x.Classes) + .Ignore(x => x.ImagePath) + .Ignore(x => x.LabelPath) + .Ignore(x => x.ThumbPath) + .Ignore(x => x.OriginalMediaName) .Association(a => a.Detections, (a, d) => a.Name == d.AnnotationName); builder.Entity() diff --git a/Azaion.Common/Extensions/BitmapExtensions.cs b/Azaion.Common/Extensions/BitmapExtensions.cs index d749feb..4e13bee 100644 --- a/Azaion.Common/Extensions/BitmapExtensions.cs +++ b/Azaion.Common/Extensions/BitmapExtensions.cs @@ -7,8 +7,14 @@ public static class BitmapExtensions { public static async Task OpenImage(this string imagePath) { - var image = new BitmapImage(); await using var stream = File.OpenRead(imagePath); + return OpenImage(stream); + } + + public static BitmapImage OpenImage(this Stream stream) + { + stream.Seek(0, SeekOrigin.Begin); + var image = new BitmapImage(); image.BeginInit(); image.CacheOption = BitmapCacheOption.OnLoad; image.StreamSource = stream; diff --git a/Azaion.Common/Services/AnnotationService.cs b/Azaion.Common/Services/AnnotationService.cs index 61cb462..613f3d7 100644 --- a/Azaion.Common/Services/AnnotationService.cs +++ b/Azaion.Common/Services/AnnotationService.cs @@ -67,7 +67,22 @@ public class AnnotationService : INotificationHandler OffsetSpec = new OffsetTypeOffset(offset + 1), MessageHandler = async (stream, consumer, context, message) => { - await Consume(MessagePackSerializer.Deserialize(message.Data.Contents), cancellationToken); + var msg = MessagePackSerializer.Deserialize(message.Data.Contents); + if (msg.CreatedRole != RoleEnum.Operator) //Process only operator's messages + return; + + await SaveAnnotationInner( + msg.CreatedDate, + msg.Name, + msg.ImageExtension, + JsonConvert.DeserializeObject>(msg.Detections) ?? [], + msg.Source, + new MemoryStream(msg.Image), + msg.CreatedRole, + msg.CreatedEmail, + generateThumbnail: true, + cancellationToken); + await _dbFactory.Run(async db => await db.QueueOffsets .Where(x => x.QueueName == Constants.MQ_ANNOTATIONS_QUEUE) .Set(x => x.Offset, context.Offset) @@ -92,24 +107,11 @@ public class AnnotationService : INotificationHandler await SaveAnnotationInner(DateTime.UtcNow, annotation.Name, annotation.ImageExtension, annotation.Detections.ToList(), SourceEnum.Manual, null, _apiClient.User.Role, _apiClient.User.Email, generateThumbnail: false, token); - //Queue (only from operators) - public async Task Consume(AnnotationCreatedMessage message, CancellationToken cancellationToken = default) - { - if (message.CreatedRole != RoleEnum.Operator) //Process only operator's messages - return; - - await SaveAnnotationInner( - message.CreatedDate, - message.Name, - message.ImageExtension, - JsonConvert.DeserializeObject>(message.Detections) ?? [], - message.Source, - new MemoryStream(message.Image), - message.CreatedRole, - message.CreatedEmail, - generateThumbnail: true, - cancellationToken); - } + // //Queue (only from operators) + // public async Task Consume(AnnotationCreatedMessage message, CancellationToken cancellationToken = default) + // { + // + // } private async Task SaveAnnotationInner(DateTime createdDate, string fName, string imageExtension, List detections, SourceEnum source, Stream? stream, RoleEnum userRole, @@ -168,7 +170,7 @@ public class AnnotationService : INotificationHandler if (generateThumbnail) await _galleryService.CreateThumbnail(annotation, token); - await _producer.SendToQueue(annotation, token); + await _producer.SendToInnerQueue(annotation, token); await _mediator.Publish(new AnnotationCreatedEvent(annotation), token); await ThrottleExt.ThrottleRunAfter(() => diff --git a/Azaion.Common/Services/FailsafeProducer.cs b/Azaion.Common/Services/FailsafeProducer.cs index 1fa990b..525fc4f 100644 --- a/Azaion.Common/Services/FailsafeProducer.cs +++ b/Azaion.Common/Services/FailsafeProducer.cs @@ -53,7 +53,7 @@ public class FailsafeAnnotationsProducer await Init(cancellationToken); while (!cancellationToken.IsCancellationRequested) { - var messages = await GetFromQueue(cancellationToken); + var messages = await GetFromInnerQueue(cancellationToken); foreach (var messagesChunk in messages.Chunk(10)) //Sending by 10 { var sent = false; @@ -91,7 +91,7 @@ public class FailsafeAnnotationsProducer } } - private async Task> GetFromQueue(CancellationToken cancellationToken = default) + private async Task> GetFromInnerQueue(CancellationToken cancellationToken = default) { return await _dbFactory.Run(async db => { @@ -124,7 +124,7 @@ public class FailsafeAnnotationsProducer }); } - public async Task SendToQueue(Annotation annotation, CancellationToken cancellationToken = default) + public async Task SendToInnerQueue(Annotation annotation, CancellationToken cancellationToken = default) { await _dbFactory.Run(async db => await db.InsertAsync(new AnnotationName { Name = annotation.Name }, token: cancellationToken)); diff --git a/Azaion.CommonSecurity/Services/ResourceLoader.cs b/Azaion.CommonSecurity/Services/ResourceLoader.cs index 645d16a..0831fcd 100644 --- a/Azaion.CommonSecurity/Services/ResourceLoader.cs +++ b/Azaion.CommonSecurity/Services/ResourceLoader.cs @@ -1,47 +1,14 @@ -using System.Reflection; -using Azaion.CommonSecurity.DTO; +using Azaion.CommonSecurity.DTO; namespace Azaion.CommonSecurity.Services; public interface IResourceLoader { Task Load(string fileName, CancellationToken cancellationToken = default); - Assembly? LoadAssembly(string asmName); } public class ResourceLoader(AzaionApiClient api, ApiCredentials credentials) : IResourceLoader { - private static readonly List EncryptedResources = - [ - "Azaion.Annotator", - "Azaion.Dataset" - ]; - - public Assembly? LoadAssembly(string resourceName) - { - var assemblyName = resourceName.Split(',').First(); - if (EncryptedResources.Contains(assemblyName)) - { - try - { - var stream = Load($"{assemblyName}.dll").GetAwaiter().GetResult(); - return Assembly.Load(stream.ToArray()); - } - catch (Exception e) - { - Console.WriteLine(e); - var currentLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; - var dllPath = Path.Combine(currentLocation, SecurityConstants.DUMMY_DIR, $"{assemblyName}.dll"); - return Assembly.LoadFile(dllPath); - } - } - - var loadedAssembly = AppDomain.CurrentDomain.GetAssemblies() - .FirstOrDefault(a => a.GetName().Name == assemblyName); - - return loadedAssembly; - } - public async Task Load(string fileName, CancellationToken cancellationToken = default) { var hardwareService = new HardwareService(); diff --git a/Azaion.Dataset/DatasetExplorer.xaml.cs b/Azaion.Dataset/DatasetExplorer.xaml.cs index 1292462..b9fb4f7 100644 --- a/Azaion.Dataset/DatasetExplorer.xaml.cs +++ b/Azaion.Dataset/DatasetExplorer.xaml.cs @@ -16,7 +16,7 @@ using Color = ScottPlot.Color; namespace Azaion.Dataset; -public partial class DatasetExplorer : INotificationHandler, INotificationHandler +public partial class DatasetExplorer { private readonly ILogger _logger; private readonly AnnotationConfig _annotationConfig; @@ -27,13 +27,13 @@ public partial class DatasetExplorer : INotificationHandler AllDetectionClasses { get; set; } = new(); public ObservableCollection SelectedAnnotations { get; set; } = new(); - + public readonly Dictionary SelectedAnnotationDict = new(); private int _tempSelectedClassIdx = 0; private readonly IGalleryService _galleryService; private readonly IDbFactory _dbFactory; private readonly IMediator _mediator; - private readonly Dictionary _selectedAnnotationDict = new(); + public bool ThumbnailLoading { get; set; } @@ -144,7 +144,7 @@ public partial class DatasetExplorer : INotificationHandler x.Name).ToList(); - var annThumbs = _selectedAnnotationDict - .Where(x => names.Contains(x.Key)) - .Select(x => x.Value) - .ToList(); - foreach (var annThumb in annThumbs) - { - SelectedAnnotations.Remove(annThumb); - _selectedAnnotationDict.Remove(annThumb.Annotation.Name); + SelectedAnnotationDict.Add(annThumb.Annotation.Name, annThumb); } } diff --git a/Azaion.Dataset/DatasetExplorerEventHandler.cs b/Azaion.Dataset/DatasetExplorerEventHandler.cs index f9948f6..bdd8afd 100644 --- a/Azaion.Dataset/DatasetExplorerEventHandler.cs +++ b/Azaion.Dataset/DatasetExplorerEventHandler.cs @@ -10,9 +10,11 @@ namespace Azaion.Dataset; public class DatasetExplorerEventHandler( DatasetExplorer datasetExplorer, - AnnotationService annotationService) - : INotificationHandler, - INotificationHandler + AnnotationService annotationService) : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler { private readonly Dictionary _keysControlEnumDict = new() { @@ -83,4 +85,35 @@ public class DatasetExplorerEventHandler( break; } } + + public async Task Handle(AnnotationCreatedEvent notification, CancellationToken cancellationToken) + { + var annotation = notification.Annotation; + var selectedClass = ((DetectionClass?)datasetExplorer.LvClasses.SelectedItem)?.Id; + if (selectedClass == null) + return; + + //TODO: For editing existing need to handle updates + datasetExplorer.AddAnnotationToDict(annotation); + if (annotation.Classes.Contains(selectedClass.Value)) + { + var annThumb = new AnnotationThumbnail(annotation); + datasetExplorer.SelectedAnnotations.Add(annThumb); + datasetExplorer.SelectedAnnotationDict.Add(annThumb.Annotation.Name, annThumb); + } + } + + public async Task Handle(AnnotationsDeletedEvent notification, CancellationToken cancellationToken) + { + var names = notification.Annotations.Select(x => x.Name).ToList(); + var annThumbs = datasetExplorer.SelectedAnnotationDict + .Where(x => names.Contains(x.Key)) + .Select(x => x.Value) + .ToList(); + foreach (var annThumb in annThumbs) + { + datasetExplorer.SelectedAnnotations.Remove(annThumb); + datasetExplorer.SelectedAnnotationDict.Remove(annThumb.Annotation.Name); + } + } } diff --git a/Azaion.Suite/App.xaml.cs b/Azaion.Suite/App.xaml.cs index f6e98cf..0adadcd 100644 --- a/Azaion.Suite/App.xaml.cs +++ b/Azaion.Suite/App.xaml.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Reflection; using System.Windows; using System.Windows.Threading; using Azaion.Annotator; @@ -48,6 +49,12 @@ public partial class App StartLogin(); } + private readonly List _encryptedResources = + [ + "Azaion.Annotator", + "Azaion.Dataset" + ]; + private void StartLogin() { new ConfigUpdater().CheckConfig(); @@ -57,7 +64,30 @@ public partial class App _apiClient = AzaionApiClient.Create(args); _resourceLoader = new ResourceLoader(_apiClient, args); _securedConfig = await _resourceLoader.Load("secured-config.json"); - AppDomain.CurrentDomain.AssemblyResolve += (_, a) => _resourceLoader.LoadAssembly(a.Name); + AppDomain.CurrentDomain.AssemblyResolve += (_, a) => + { + var assemblyName = a.Name.Split(',').First(); + if (_encryptedResources.Contains(assemblyName)) + { + try + { + var stream = _resourceLoader.Load($"{assemblyName}.dll").GetAwaiter().GetResult(); + return Assembly.Load(stream.ToArray()); + } + catch (Exception e) + { + Console.WriteLine(e); + var currentLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; + var dllPath = Path.Combine(currentLocation, SecurityConstants.DUMMY_DIR, $"{assemblyName}.dll"); + return Assembly.LoadFile(dllPath); + } + } + + var loadedAssembly = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.GetName().Name == assemblyName); + + return loadedAssembly; + }; StartMain(); await _host.StartAsync(); @@ -108,7 +138,8 @@ public partial class App services.AddSingleton(); services.AddMediatR(c => c.RegisterServicesFromAssemblies( typeof(Annotator.Annotator).Assembly, - typeof(DatasetExplorer).Assembly)); + typeof(DatasetExplorer).Assembly, + typeof(AnnotationService).Assembly)); services.AddSingleton(_ => new LibVLC()); services.AddSingleton(); services.AddSingleton(sp => diff --git a/Azaion.Test/DictTest.cs b/Azaion.Test/DictTest.cs index c9e5a7b..2d57809 100644 --- a/Azaion.Test/DictTest.cs +++ b/Azaion.Test/DictTest.cs @@ -1,4 +1,5 @@ using Azaion.Common.DTO; +using FluentAssertions; using Xunit; namespace Azaion.Annotator.Test; @@ -17,4 +18,25 @@ public class DictTest new YoloLabel(0, 1, 3, 2, 1) ]; } + + [Theory] + [InlineData(null, 0)] + [InlineData(new int[]{}, 0)] + [InlineData(new int[]{1, 2, 5}, 1)] + [InlineData(new int[]{3, -2, 5}, -2)] + [InlineData(new int[]{3, -2, 2, 4}, 2)] + [InlineData(new int[]{3, -2, -100, 2, 4}, 2)] + public void ComputeClosestToZeroTest(int[] ts, int expected) => + ComputeClosestToZero(ts).Should().Be(expected); + + private int ComputeClosestToZero(int[]? ts) + { + if (ts is null || ts.Length == 0) + return 0; + + return ts + .OrderBy(Math.Abs) + .ThenByDescending(x => x) // 2 -2 3 4 -10 + .FirstOrDefault(); + } } \ No newline at end of file