From 596f6db217460ac081a0aed8d5fc4ede4b7c741a Mon Sep 17 00:00:00 2001 From: Alex Bezdieniezhnykh Date: Fri, 25 Oct 2024 00:17:24 +0300 Subject: [PATCH] add ai recognition: stage 1, works, but doesn't show --- Azaion.Annotator/AIDetector.cs | 54 ++ Azaion.Annotator/App.xaml.cs | 1 + Azaion.Annotator/Azaion.Annotator.csproj | 1 + Azaion.Annotator/DTO/Config.cs | 27 +- Azaion.Annotator/DTO/MediatrEvents.cs | 2 + Azaion.Annotator/DatasetExplorer.xaml.cs | 2 +- .../Extensions/VLCFrameExtractor.cs | 127 ++++ Azaion.Annotator/GalleryManager.cs | 25 +- Azaion.Annotator/MainWindow.xaml | 611 ++++++++++-------- Azaion.Annotator/MainWindow.xaml.cs | 52 +- Azaion.Annotator/MainWindowEventHandler.cs | 18 + Azaion.Annotator/config.json | 3 +- README.md | 4 + 13 files changed, 591 insertions(+), 336 deletions(-) create mode 100644 Azaion.Annotator/AIDetector.cs create mode 100644 Azaion.Annotator/Extensions/VLCFrameExtractor.cs diff --git a/Azaion.Annotator/AIDetector.cs b/Azaion.Annotator/AIDetector.cs new file mode 100644 index 0000000..fabde90 --- /dev/null +++ b/Azaion.Annotator/AIDetector.cs @@ -0,0 +1,54 @@ +using System.Diagnostics; +using System.IO; +using Azaion.Annotator.DTO; +using Azaion.Annotator.Extensions; +using Compunet.YoloV8; +using LibVLCSharp.Shared; +using MediatR; + +namespace Azaion.Annotator; + +public class AIDetector(Config config, MediaPlayer mediaPlayer, VLCFrameExtractor frameExtractor) + : IRequestHandler> +{ + public async Task> Handle(AIDetectEvent request, CancellationToken cancellationToken) + { + using var predictor = new YoloPredictor(config.AIModelPath); + await frameExtractor.Start(async stream => + { + stream.Seek(0, SeekOrigin.Begin); + var sw = Stopwatch.StartNew(); + var result = await predictor.DetectAsync(stream); + sw.Stop(); + var log = string.Join("|", result.Select(det => + $"{det.Name.Id}.{det.Name.Name}: xy=({det.Bounds.X},{det.Bounds.Y}), size=({det.Bounds.Width}, {det.Bounds.Height}), Prob: {det.Confidence*100:F1}%")); + log += $". Inf time: {sw.ElapsedMilliseconds} ms"; + Console.WriteLine(log); + }); + + while (mediaPlayer.IsPlaying) + { + + try + { + // using var thumbnail = await mediaPlayer.Media.GenerateThumbnail(time: 200, + // speed: ThumbnailerSeekSpeed.Fast, + // width: 1280, + // height: resultHeight, + // crop: false, + // pictureType: PictureType.Argb) + // + // mediaPlayer.TakeSnapshot(0, TEMP_IMG, 1280, resultHeight); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + //var result = predictor.Detect(); + } + + return new List(); + } + +} diff --git a/Azaion.Annotator/App.xaml.cs b/Azaion.Annotator/App.xaml.cs index afc9b8e..dd1674d 100644 --- a/Azaion.Annotator/App.xaml.cs +++ b/Azaion.Annotator/App.xaml.cs @@ -49,6 +49,7 @@ public partial class App : Application services.AddSingleton(); services.AddSingleton(sp => sp.GetRequiredService().Get()); services.AddSingleton(); + services.AddSingleton(); }) .UseSerilog() .Build(); diff --git a/Azaion.Annotator/Azaion.Annotator.csproj b/Azaion.Annotator/Azaion.Annotator.csproj index 326a6f3..a9f52aa 100644 --- a/Azaion.Annotator/Azaion.Annotator.csproj +++ b/Azaion.Annotator/Azaion.Annotator.csproj @@ -30,6 +30,7 @@ + diff --git a/Azaion.Annotator/DTO/Config.cs b/Azaion.Annotator/DTO/Config.cs index 6cb0969..48fa0d1 100644 --- a/Azaion.Annotator/DTO/Config.cs +++ b/Azaion.Annotator/DTO/Config.cs @@ -9,34 +9,35 @@ namespace Azaion.Annotator.DTO; public class Config { - public const string ThumbnailPrefix = "_thumb"; - public const string ThumbnailsCacheFile = "thumbnails.cache"; + public const string THUMBNAIL_PREFIX = "_thumb"; + public const string THUMBNAILS_CACHE_FILE = "thumbnails.cache"; - public string VideosDirectory { get; set; } - public string LabelsDirectory { get; set; } - public string ImagesDirectory { get; set; } - public string ResultsDirectory { get; set; } - public string ThumbnailsDirectory { get; set; } - public string UnknownImages { get; set; } + public string VideosDirectory { get; set; } = null!; + public string LabelsDirectory { get; set; } = null!; + public string ImagesDirectory { get; set; } = null!; + public string ResultsDirectory { get; set; } = null!; + public string ThumbnailsDirectory { get; set; } = null!; + public string UnknownImages { get; set; } = null!; public List AnnotationClasses { get; set; } = []; private Dictionary? _annotationClassesDict; public Dictionary AnnotationClassesDict => _annotationClassesDict ??= AnnotationClasses.ToDictionary(x => x.Id); - public WindowConfig MainWindowConfig { get; set; } - public WindowConfig DatasetExplorerConfig { get; set; } + public WindowConfig MainWindowConfig { get; set; } = null!; + public WindowConfig DatasetExplorerConfig { get; set; } = null!; public double LeftPanelWidth { get; set; } public double RightPanelWidth { get; set; } public bool ShowHelpOnStart { get; set; } - public List VideoFormats { get; set; } - public List ImageFormats { get; set; } + public List VideoFormats { get; set; } = null!; + public List ImageFormats { get; set; } = null!; - public ThumbnailConfig ThumbnailConfig { get; set; } + public ThumbnailConfig ThumbnailConfig { get; set; } = null!; public int? LastSelectedExplorerClass { get; set; } + public string AIModelPath { get; set; } = null!; } public class WindowConfig diff --git a/Azaion.Annotator/DTO/MediatrEvents.cs b/Azaion.Annotator/DTO/MediatrEvents.cs index 36340fd..775171a 100644 --- a/Azaion.Annotator/DTO/MediatrEvents.cs +++ b/Azaion.Annotator/DTO/MediatrEvents.cs @@ -18,3 +18,5 @@ public class VolumeChangedEvent(int volume) : INotification { public int Volume { get; set; } = volume; } + +public class AIDetectEvent : IRequest>; diff --git a/Azaion.Annotator/DatasetExplorer.xaml.cs b/Azaion.Annotator/DatasetExplorer.xaml.cs index 64ea924..6ca0851 100644 --- a/Azaion.Annotator/DatasetExplorer.xaml.cs +++ b/Azaion.Annotator/DatasetExplorer.xaml.cs @@ -293,7 +293,7 @@ public partial class DatasetExplorer { try { - var name = Path.GetFileNameWithoutExtension(thumbnail)[..^Config.ThumbnailPrefix.Length]; + var name = Path.GetFileNameWithoutExtension(thumbnail)[..^Config.THUMBNAIL_PREFIX.Length]; var imagePath = Path.Combine(_config.ImagesDirectory, name); foreach (var f in _config.ImageFormats) { diff --git a/Azaion.Annotator/Extensions/VLCFrameExtractor.cs b/Azaion.Annotator/Extensions/VLCFrameExtractor.cs new file mode 100644 index 0000000..1c61fd2 --- /dev/null +++ b/Azaion.Annotator/Extensions/VLCFrameExtractor.cs @@ -0,0 +1,127 @@ +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Runtime.InteropServices; +using Azaion.Annotator.DTO; +using LibVLCSharp.Shared; +using SixLabors.ImageSharp.Drawing; +using SkiaSharp; + +namespace Azaion.Annotator.Extensions; + +public class VLCFrameExtractor(LibVLC libVLC, MainWindow mainWindow) +{ + private const uint RGBA_BYTES = 4; + private const int PLAYBACK_RATE = 3; + private const uint DEFAULT_WIDTH = 1280; + + private uint _pitch; // Number of bytes per "line", aligned to x32. + private uint _lines; // Number of lines in the buffer, aligned to x32. + private uint _width; // Thumbnail width + private uint _height; // Thumbnail height + private uint _videoFPS; + private Func _frameProcessFn = null!; + + private MediaPlayer _mediaPlayer = null!; + + private static uint Align32(uint size) + { + if (size % 32 == 0) + return size; + return (size / 32 + 1) * 32;// Align on the next multiple of 32 + } + + private static SKBitmap? _currentBitmap; + private static readonly ConcurrentQueue FilesToProcess = new(); + private static long _frameCounter; + + public async Task Start(Func frameProcessFn) + { + _frameProcessFn = frameProcessFn; + var processingCancellationTokenSource = new CancellationTokenSource(); + + _mediaPlayer = new MediaPlayer(libVLC); + _mediaPlayer.Stopped += (s, e) => processingCancellationTokenSource.CancelAfter(1); + + using var media = new Media(libVLC, ((MediaFileInfo)mainWindow.LvFiles.SelectedItem).Path); + await media.Parse(cancellationToken: processingCancellationTokenSource.Token); + var videoTrack = media.Tracks.FirstOrDefault(x => x.Data.Video.Width != 0); + _width = videoTrack.Data.Video.Width; + _height = videoTrack.Data.Video.Height; + _videoFPS = videoTrack.Data.Video.FrameRateNum; + + //rescaling to DEFAULT_WIDTH + _height = (uint)(DEFAULT_WIDTH * _height / (double)_width); + _width = DEFAULT_WIDTH; + + _pitch = Align32(_width * RGBA_BYTES); + _lines = Align32(_height); + _mediaPlayer.Play(media); + _mediaPlayer.SetRate(3); + + try + { + media.AddOption(":no-audio"); + _mediaPlayer.SetVideoFormat("RV32", _width, _height, _pitch); + _mediaPlayer.SetVideoCallbacks(Lock, null, Display); + await ProcessThumbnailsAsync(processingCancellationTokenSource.Token); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + _mediaPlayer.Stop(); + _mediaPlayer.Dispose(); + } + } + + private async Task ProcessThumbnailsAsync(CancellationToken token) + { + _frameCounter = 0; + var surface = SKSurface.Create(new SKImageInfo((int) _width, (int) _height)); + while (!token.IsCancellationRequested) + { + if (FilesToProcess.TryDequeue(out var bitmap)) + { + if (bitmap == null) + continue; + + surface.Canvas.DrawBitmap(bitmap, 0, 0); // Effectively crops the original bitmap to get only the visible area + + using var outputImage = surface.Snapshot(); + using var data = outputImage.Encode(SKEncodedImageFormat.Jpeg, 85); + using var ms = new MemoryStream(); + data.SaveTo(ms); + if (_frameProcessFn != null) + await _frameProcessFn(ms); + + Console.WriteLine($"Time: {TimeSpan.FromMilliseconds(_mediaPlayer.Time):mm\\:ss} Queue size: {FilesToProcess.Count}"); + bitmap.Dispose(); + } + else + { + await Task.Delay(TimeSpan.FromSeconds(1), token); + } + } + _mediaPlayer.Dispose(); + } + + private IntPtr Lock(IntPtr opaque, IntPtr planes) + { + _currentBitmap = new SKBitmap(new SKImageInfo((int)(_pitch / RGBA_BYTES), (int)_lines, SKColorType.Bgra8888)); + Marshal.WriteIntPtr(planes, _currentBitmap.GetPixels()); + return IntPtr.Zero; + } + + private void Display(IntPtr opaque, IntPtr picture) + { + if (_frameCounter % (int)(_videoFPS / 3.0) == 0) + FilesToProcess.Enqueue(_currentBitmap); + else + _currentBitmap?.Dispose(); + + _currentBitmap = null; + _frameCounter++; + } +} \ No newline at end of file diff --git a/Azaion.Annotator/GalleryManager.cs b/Azaion.Annotator/GalleryManager.cs index 95c9beb..30261af 100644 --- a/Azaion.Annotator/GalleryManager.cs +++ b/Azaion.Annotator/GalleryManager.cs @@ -49,7 +49,7 @@ public class GalleryManager : IGalleryManager { _config = config; _logger = logger; - _thumbnailsCacheFile = Path.Combine(config.ThumbnailsDirectory, Config.ThumbnailsCacheFile); + _thumbnailsCacheFile = Path.Combine(config.ThumbnailsDirectory, Config.THUMBNAILS_CACHE_FILE); } public void ClearThumbnails() @@ -63,7 +63,7 @@ public class GalleryManager : IGalleryManager await _updateLock.WaitAsync(); try { - var prefixLen = Config.ThumbnailPrefix.Length; + var prefixLen = Config.THUMBNAIL_PREFIX.Length; var thumbnails = ThumbnailsDirectory.GetFiles() .Select(x => Path.GetFileNameWithoutExtension(x.Name)[..^prefixLen]) @@ -122,7 +122,7 @@ public class GalleryManager : IGalleryManager await File.WriteAllTextAsync(_thumbnailsCacheFile, labelsCacheStr); } - public async Task CreateThumbnail(string imgPath, CancellationToken cancellationToken = default) + public async Task CreateThumbnail(string imgPath, CancellationToken cancellationToken = default) { var width = (int)_config.ThumbnailConfig.Size.Width; var height = (int)_config.ThumbnailConfig.Size.Height; @@ -144,9 +144,9 @@ public class GalleryManager : IGalleryManager { File.Move(imgPath, Path.Combine(_config.UnknownImages, imgName)); _logger.LogInformation($"No labels found for image {imgName}! Moved image to the {_config.UnknownImages} folder."); - return; + return null; } - var labels = (await YoloLabel.ReadFromFile(labelName)) + var labels = (await YoloLabel.ReadFromFile(labelName, cancellationToken)) .Select(x => new CanvasLabel(x, size, size)) .ToList(); @@ -204,13 +204,16 @@ public class GalleryManager : IGalleryManager g.FillRectangle(brush, rectangle); } + var thumbnailName = Path.Combine(ThumbnailsDirectory.FullName, $"{Path.GetFileNameWithoutExtension(imgPath)}{Config.THUMBNAIL_PREFIX}.jpg"); + bitmap.Save(thumbnailName, ImageFormat.Jpeg); - - if (bitmap != null) + return new ThumbnailDto { - var thumbnailName = Path.Combine(ThumbnailsDirectory.FullName, $"{Path.GetFileNameWithoutExtension(imgPath)}{Config.ThumbnailPrefix}.jpg"); - bitmap.Save(thumbnailName, ImageFormat.Jpeg); - } + ThumbnailPath = thumbnailName, + ImagePath = imgPath, + LabelPath = labelName, + ImageDate = File.GetCreationTimeUtc(imgPath) + }; } } @@ -220,7 +223,7 @@ public interface IGalleryManager double ThumbnailsPercentage { get; set; } Task SaveLabelsCache(); ConcurrentDictionary LabelsCache { get; set; } - Task CreateThumbnail(string imgPath, CancellationToken cancellationToken = default); + Task CreateThumbnail(string imgPath, CancellationToken cancellationToken = default); Task RefreshThumbnails(); void ClearThumbnails(); } \ No newline at end of file diff --git a/Azaion.Annotator/MainWindow.xaml b/Azaion.Annotator/MainWindow.xaml index 0ae2c11..63f67e1 100644 --- a/Azaion.Annotator/MainWindow.xaml +++ b/Azaion.Annotator/MainWindow.xaml @@ -46,242 +46,259 @@ - - + + - - - - - - - - - - - - - - - - - - - - - + + Name="MainGrid" + ShowGridLines="False" + Background="Black" + HorizontalAlignment="Stretch"> + + + + + + + + + + - + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + + - - - - - - - - - + + + + + + + + + + + + @@ -302,7 +319,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Azaion.Annotator/MainWindow.xaml.cs b/Azaion.Annotator/MainWindow.xaml.cs index 77a4d12..7effc54 100644 --- a/Azaion.Annotator/MainWindow.xaml.cs +++ b/Azaion.Annotator/MainWindow.xaml.cs @@ -83,15 +83,7 @@ public partial class MainWindow { Core.Initialize(); InitControls(); - - _ = Task.Run(async () => - { - while (true) - { - await _galleryManager.RefreshThumbnails(); - await Task.Delay(30000); - } - }); + _ = Task.Run(async () => await _galleryManager.RefreshThumbnails()); _suspendLayout = true; @@ -123,10 +115,10 @@ public partial class MainWindow LvClasses.ItemsSource = AnnotationClasses; LvClasses.SelectedIndex = 0; - if (LvFiles.Items.IsEmpty) + if (LvFiles.Items.IsEmpty) BlinkHelp(HelpTexts.HelpTextsDict[HelpTextEnum.Initial]); - - if (_config.ShowHelpOnStart) + + if (_config.ShowHelpOnStart) _helpWindow.Show(); } @@ -141,6 +133,7 @@ public partial class MainWindow Dispatcher.Invoke(() => StatusHelp.Text = ""); await Task.Delay(200); } + Dispatcher.Invoke(() => StatusHelp.Text = helpText); }); } @@ -171,7 +164,7 @@ public partial class MainWindow }; LvFiles.MouseDoubleClick += async (_, _) => await _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.Play)); - + LvClasses.SelectionChanged += (_, _) => { var selectedClass = (AnnotationClass)LvClasses.SelectedItem; @@ -194,7 +187,7 @@ public partial class MainWindow SizeChanged += async (_, _) => await SaveUserSettings(); LocationChanged += async (_, _) => await SaveUserSettings(); StateChanged += async (_, _) => await SaveUserSettings(); - + Editor.FormState = _formState; Editor.Mediator = _mediator; DgAnnotations.ItemsSource = _formState.AnnotationResults; @@ -243,13 +236,13 @@ public partial class MainWindow var labelDir = new DirectoryInfo(_config.LabelsDirectory); if (!labelDir.Exists) return; - + var labelFiles = labelDir.GetFiles($"{_formState.VideoName}_??????.txt"); foreach (var file in labelFiles) { var name = Path.GetFileNameWithoutExtension(file.Name); var time = _formState.GetTime(name); - await AddAnnotation(time, await YoloLabel.ReadFromFile(file.FullName)); + await AddAnnotation(time, await YoloLabel.ReadFromFile(file.FullName, cancellationToken)); } } @@ -267,11 +260,11 @@ public partial class MainWindow _formState.AnnotationResults.Remove(existingResult); var dict = _formState.AnnotationResults - .Select((x,i) => new { x.Time, Index = i }) + .Select((x, i) => new { x.Time, Index = i }) .ToDictionary(x => x.Time, x => x.Index); var index = dict.Where(x => x.Key < timeValue) - .OrderBy(x => x.Key - timeValue) + .OrderBy(x => timeValue - x.Key) .Select(x => x.Value + 1) .FirstOrDefault(); @@ -299,7 +292,7 @@ public partial class MainWindow var videoFiles = dir.GetFiles(_config.VideoFormats.ToArray()).Select(x => { - var media = new Media(_libVLC, x.FullName); + using var media = new Media(_libVLC, x.FullName); media.Parse(); var fInfo = new MediaFileInfo { @@ -325,7 +318,7 @@ public partial class MainWindow TbFolder.Text = _config.VideosDirectory; BlinkHelp(AllMediaFiles.Count == 0 - ? HelpTexts.HelpTextsDict[HelpTextEnum.Initial] + ? HelpTexts.HelpTextsDict[HelpTextEnum.Initial] : HelpTexts.HelpTextsDict[HelpTextEnum.PlayVideo]); } @@ -346,6 +339,7 @@ public partial class MainWindow // } private async void OpenFolderItemClick(object sender, RoutedEventArgs e) => await OpenFolder(); private async void OpenFolderButtonClick(object sender, RoutedEventArgs e) => await OpenFolder(); + private async Task OpenFolder() { var dlg = new CommonOpenFileDialog @@ -365,6 +359,7 @@ public partial class MainWindow ReloadFiles(); } + private void TbFilter_OnTextChanged(object sender, TextChangedEventArgs e) { FilteredMediaFiles = new ObservableCollection(AllMediaFiles.Where(x => x.Name.ToLower().Contains(TbFilter.Text.ToLower())).ToList()); @@ -387,15 +382,22 @@ public partial class MainWindow private void StopClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(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 NextFrameClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(PlaybackControlEnum.NextFrame)); private void SaveAnnotationsClick(object sender, RoutedEventArgs e) => _mediator.Publish(new PlaybackControlEvent(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 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 async void AutoDetect(object sender, RoutedEventArgs e) + { + if (LvFiles.SelectedItem == null) + return; + await _mediator.Send(new AIDetectEvent()); + } + private void OpenHelpWindowClick(object sender, RoutedEventArgs e) { _helpWindow.Show(); @@ -410,7 +412,7 @@ public partial class MainWindow var dgRow = ItemsControl.ContainerFromElement((DataGrid)sender, (args.OriginalSource as DependencyObject)!) as DataGridRow; var res = (AnnotationResult)dgRow!.Item; _mediaPlayer.SetPause(true); - _mediaPlayer.Time = (long)res.Time.TotalMilliseconds;// + 250; + _mediaPlayer.Time = (long)res.Time.TotalMilliseconds; // + 250; ShowTimeAnnotations(res.Time); }; } diff --git a/Azaion.Annotator/MainWindowEventHandler.cs b/Azaion.Annotator/MainWindowEventHandler.cs index 9902348..e4e8691 100644 --- a/Azaion.Annotator/MainWindowEventHandler.cs +++ b/Azaion.Annotator/MainWindowEventHandler.cs @@ -19,6 +19,9 @@ public class MainWindowEventHandler : private readonly MainWindow _mainWindow; private readonly FormState _formState; private readonly Config _config; + private readonly IMediator _mediator; + private readonly IGalleryManager _galleryManager; + private readonly DatasetExplorer _datasetExplorer; private readonly ILogger _logger; private const int STEP = 20; @@ -42,6 +45,9 @@ public class MainWindowEventHandler : MainWindow mainWindow, FormState formState, Config config, + IMediator mediator, + IGalleryManager galleryManager, + DatasetExplorer datasetExplorer, ILogger logger) { _libVLC = libVLC; @@ -49,6 +55,9 @@ public class MainWindowEventHandler : _mainWindow = mainWindow; _formState = formState; _config = config; + _mediator = mediator; + _galleryManager = galleryManager; + _datasetExplorer = datasetExplorer; _logger = logger; } @@ -85,6 +94,9 @@ public class MainWindowEventHandler : if (_keysControlEnumDict.TryGetValue(key, out var value)) await ControlPlayback(value); + if (key == Key.A) + await _mediator.Send( new AIDetectEvent(), cancellationToken); + await VolumeControl(key); } @@ -259,5 +271,11 @@ public class MainWindowEventHandler : File.Copy(_formState.CurrentMedia.Path, destinationPath, overwrite: true); await NextMedia(); } + + var thumbnailDto = await _galleryManager.CreateThumbnail(destinationPath); + var selectedClass = ((AnnotationClass?)_datasetExplorer.LvClasses.SelectedItem)?.Id; + + if (selectedClass != null && (selectedClass == -1 || currentAnns.Any(x => x.ClassNumber == selectedClass))) + _datasetExplorer.ThumbnailsDtos.Insert(0, thumbnailDto); } } \ No newline at end of file diff --git a/Azaion.Annotator/config.json b/Azaion.Annotator/config.json index 5f1260f..78f538d 100644 --- a/Azaion.Annotator/config.json +++ b/Azaion.Annotator/config.json @@ -35,5 +35,6 @@ "RightPanelWidth": 300, "ShowHelpOnStart": false, "VideoFormats": ["mov", "mp4"], - "ImageFormats": ["jpg", "jpeg", "png", "bmp", "gif"] + "ImageFormats": ["jpg", "jpeg", "png", "bmp", "gif"], + "AIModelPath": "D:\\dev\\azaion\\azaion_2024-09-19.onnx" } \ No newline at end of file diff --git a/README.md b/README.md index 07ead90..cea4b88 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ Azaion.Annotator is a tool for annotation videos. Works on Windows only for now + +Install: +1. C# dotnet with wpf +2. CUDNN \ No newline at end of file