add db WIP 2, 80%

refactor, renames
This commit is contained in:
Alex Bezdieniezhnykh
2024-12-24 06:07:13 +02:00
parent 5fa18aa514
commit 48c9ccbfda
32 changed files with 499 additions and 459 deletions
+2 -2
View File
@@ -176,11 +176,11 @@
</GridView>
</ListView.View>
</ListView>
<controls1:AnnotationClasses
<controls1:DetectionClasses
x:Name="LvClasses"
Grid.Column="0"
Grid.Row="4">
</controls1:AnnotationClasses>
</controls1:DetectionClasses>
<GridSplitter
Background="DarkGray"
+43 -37
View File
@@ -1,6 +1,5 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Drawing.Imaging;
using System.IO;
using System.Windows;
using System.Windows.Controls;
@@ -8,13 +7,14 @@ using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Azaion.Annotator.DTO;
using Azaion.Annotator.Extensions;
using Azaion.Common;
using Azaion.Common.DTO;
using Azaion.Common.DTO.Config;
using Azaion.Common.DTO.Queue;
using Azaion.Common.Extensions;
using Azaion.Common.Services;
using LibVLCSharp.Shared;
using MediatR;
using Microsoft.WindowsAPICodePack.Dialogs;
@@ -40,9 +40,10 @@ public partial class Annotator
private readonly ILogger<Annotator> _logger;
private readonly VLCFrameExtractor _vlcFrameExtractor;
private readonly IAIDetector _aiDetector;
private readonly AnnotationService _annotationService;
private readonly CancellationTokenSource _cancellationTokenSource = new();
private ObservableCollection<AnnotationClass> AnnotationClasses { get; set; } = new();
private ObservableCollection<DetectionClass> AnnotationClasses { get; set; } = new();
private bool _suspendLayout;
private readonly TimeSpan _thresholdBefore = TimeSpan.FromMilliseconds(100);
@@ -52,7 +53,7 @@ public partial class Annotator
private ObservableCollection<MediaFileInfo> AllMediaFiles { get; set; } = new();
private ObservableCollection<MediaFileInfo> FilteredMediaFiles { get; set; } = new();
public IntervalTree<TimeSpan, List<YoloLabel>> Annotations { get; set; } = new();
public IntervalTree<TimeSpan, List<Detection>> Detections { get; set; } = new();
private AutodetectDialog _autoDetectDialog = new() { Topmost = true };
public Annotator(
@@ -64,7 +65,8 @@ public partial class Annotator
HelpWindow helpWindow,
ILogger<Annotator> logger,
VLCFrameExtractor vlcFrameExtractor,
IAIDetector aiDetector)
IAIDetector aiDetector,
AnnotationService annotationService)
{
InitializeComponent();
_appConfig = appConfig.Value;
@@ -77,6 +79,7 @@ public partial class Annotator
_logger = logger;
_vlcFrameExtractor = vlcFrameExtractor;
_aiDetector = aiDetector;
_annotationService = annotationService;
Loaded += OnLoaded;
Closed += OnFormClosed;
@@ -98,7 +101,7 @@ public partial class Annotator
ReloadFiles();
AnnotationClasses = new ObservableCollection<AnnotationClass>(_appConfig.AnnotationConfig.AnnotationClasses);
AnnotationClasses = new ObservableCollection<DetectionClass>(_appConfig.AnnotationConfig.AnnotationClasses);
LvClasses.ItemsSource = AnnotationClasses;
LvClasses.SelectedIndex = 0;
@@ -152,7 +155,7 @@ public partial class Annotator
LvClasses.SelectionChanged += (_, _) =>
{
var selectedClass = (AnnotationClass)LvClasses.SelectedItem;
var selectedClass = (DetectionClass)LvClasses.SelectedItem;
Editor.CurrentAnnClass = selectedClass;
_mediator.Publish(new AnnClassSelectedEvent(selectedClass));
};
@@ -188,7 +191,7 @@ public partial class Annotator
OpenAnnotationResult((AnnotationResult)DgAnnotations.SelectedItem);
break;
case Key.Delete:
var result = MessageBox.Show(Application.Current.MainWindow, "Чи дійсно видалити аннотації?","Підтвердження видалення", MessageBoxButton.OKCancel, MessageBoxImage.Question);
var result = MessageBox.Show("Чи дійсно видалити аннотації?","Підтвердження видалення", MessageBoxButton.OKCancel, MessageBoxImage.Question);
if (result != MessageBoxResult.OK)
return;
@@ -202,7 +205,7 @@ public partial class Annotator
File.Delete(Path.Combine(_appConfig.DirectoriesConfig.LabelsDirectory, $"{imgName}.txt"));
File.Delete(thumbnailPath);
_formState.AnnotationResults.Remove(annotationResult);
Annotations.Remove(Annotations.Query(annotationResult.Time));
Detections.Remove(Detections.Query(annotationResult.Time));
}
break;
}
@@ -251,7 +254,7 @@ public partial class Annotator
Editor.ClearExpiredAnnotations(time);
});
var annotations = Annotations.Query(time).SelectMany(x => x).Select(x => new Detection(x));
var annotations = Detections.Query(time).SelectMany(x => x).Select(x => new Detection(_formState.GetTimeName(time), x));
AddAnnotationsToCanvas(time, annotations);
}
@@ -285,7 +288,7 @@ public partial class Annotator
private async Task ReloadAnnotations(CancellationToken ct = default)
{
_formState.AnnotationResults.Clear();
Annotations.Clear();
Detections.Clear();
Editor.RemoveAllAnns();
var labelDir = new DirectoryInfo(_appConfig.DirectoriesConfig.LabelsDirectory);
@@ -294,22 +297,21 @@ public partial class Annotator
var labelFiles = labelDir.GetFiles($"{_formState.VideoName}_??????.txt");
foreach (var file in labelFiles)
{
var name = Path.GetFileNameWithoutExtension(file.Name);
var time = Constants.GetTime(name);
await AddAnnotations(time, await YoloLabel.ReadFromFile(file.FullName, ct), ct);
}
await AddAnnotations(Path.GetFileNameWithoutExtension(file.Name), await YoloLabel.ReadFromFile(file.FullName, ct), ct);
}
public async Task AddAnnotations(TimeSpan? time, List<YoloLabel> annotations, CancellationToken ct = default)
=> await AddAnnotations(time, annotations.Select(x => new Detection(x)).ToList(), ct);
//Load from yolo label file
public async Task AddAnnotations(string name, List<YoloLabel> annotations, CancellationToken ct = default)
=> await AddAnnotations(name, annotations.Select(x => new Detection(name, x)).ToList(), ct);
public async Task AddAnnotations(TimeSpan? time, List<Detection> detections, CancellationToken ct = default)
//Add manually
public async Task AddAnnotations(string name, List<Detection> detections, CancellationToken ct = default)
{
var time = Constants.GetTime(name);
var timeValue = time ?? TimeSpan.FromMinutes(0);
var previousAnnotations = Annotations.Query(timeValue);
Annotations.Remove(previousAnnotations);
Annotations.Add(timeValue.Subtract(_thresholdBefore), timeValue.Add(_thresholdAfter), detections.Cast<YoloLabel>().ToList());
var previousAnnotations = Detections.Query(timeValue);
Detections.Remove(previousAnnotations);
Detections.Add(timeValue.Subtract(_thresholdBefore), timeValue.Add(_thresholdAfter), detections);
var existingResult = _formState.AnnotationResults.FirstOrDefault(x => x.Time == time);
if (existingResult != null)
@@ -345,15 +347,15 @@ public partial class Annotator
return (-1).ToColor();
return colorNumber >= detectionClasses.Count
? _appConfig.AnnotationConfig.AnnotationClassesDict[detectionClasses.LastOrDefault()].Color
: _appConfig.AnnotationConfig.AnnotationClassesDict[detectionClasses[colorNumber]].Color;
? _appConfig.AnnotationConfig.DetectionClassesDict[detectionClasses.LastOrDefault()].Color
: _appConfig.AnnotationConfig.DetectionClassesDict[detectionClasses[colorNumber]].Color;
}
var detectionClasses = detections.Select(x => x.ClassNumber).Distinct().ToList();
annotationResult.ClassName = detectionClasses.Count > 1
? string.Join(", ", detectionClasses.Select(x => _appConfig.AnnotationConfig.AnnotationClassesDict[x].ShortName))
: _appConfig.AnnotationConfig.AnnotationClassesDict[detectionClasses.FirstOrDefault()].Name;
? string.Join(", ", detectionClasses.Select(x => _appConfig.AnnotationConfig.DetectionClassesDict[x].ShortName))
: _appConfig.AnnotationConfig.DetectionClassesDict[detectionClasses.FirstOrDefault()].Name;
annotationResult.ClassColor0 = GetAnnotationClass(detectionClasses, 0);
annotationResult.ClassColor1 = GetAnnotationClass(detectionClasses, 1);
@@ -442,8 +444,8 @@ public partial class Annotator
// private void AddClassBtnClick(object sender, RoutedEventArgs e)
// {
// LvClasses.IsReadOnly = false;
// AnnotationClasses.Add(new AnnotationClass(AnnotationClasses.Count));
// LvClasses.SelectedIndex = AnnotationClasses.Count - 1;
// DetectionClasses.Add(new DetectionClass(DetectionClasses.Count));
// LvClasses.SelectedIndex = DetectionClasses.Count - 1;
// }
private async void OpenFolderItemClick(object sender, RoutedEventArgs e) => await OpenFolder();
private async void OpenFolderButtonClick(object sender, RoutedEventArgs e) => await OpenFolder();
@@ -578,9 +580,10 @@ public partial class Annotator
{
try
{
var fName = Path.GetFileNameWithoutExtension(mediaInfo.Path);
var stream = new FileStream(mediaInfo.Path, FileMode.Open);
var detections = await _aiDetector.Detect(stream, token);
await ProcessDetection((TimeSpan.FromMilliseconds(0), stream), detections, token);
var detections = await _aiDetector.Detect(fName, stream, token);
await ProcessDetection((TimeSpan.FromMilliseconds(0), stream), Path.GetExtension(mediaInfo.Path), detections, token);
if (detections.Count != 0)
mediaInfo.HasAnnotations = true;
}
@@ -599,7 +602,8 @@ public partial class Annotator
Console.WriteLine($"Detect time: {timeframe.Time}");
try
{
var detections = await _aiDetector.Detect(timeframe.Stream, token);
var fName = _formState.GetTimeName(timeframe.Time);
var detections = await _aiDetector.Detect(fName, timeframe.Stream, token);
var isValid = IsValidDetection(timeframe.Time, detections);
if (timeframe.Time.TotalSeconds > prevSeekTime + 1)
@@ -628,7 +632,7 @@ public partial class Annotator
continue;
mediaInfo.HasAnnotations = true;
await ProcessDetection(timeframe, detections, token);
await ProcessDetection(timeframe, "jpg", detections, token);
}
catch (Exception ex)
{
@@ -685,7 +689,7 @@ public partial class Annotator
return false;
}
private async Task ProcessDetection((TimeSpan Time, Stream Stream) timeframe, List<Detection> detections, CancellationToken token = default)
private async Task ProcessDetection((TimeSpan Time, Stream Stream) timeframe, string imageExtension, List<Detection> detections, CancellationToken token = default)
{
_previousDetection = (timeframe.Time, detections);
await Dispatcher.Invoke(async () =>
@@ -695,22 +699,24 @@ public partial class Annotator
var time = timeframe.Time;
var fName = _formState.GetTimeName(timeframe.Time);
var imgPath = Path.Combine(_appConfig.DirectoriesConfig.ImagesDirectory, $"{fName}.jpg");
var imgPath = Path.Combine(_appConfig.DirectoriesConfig.ImagesDirectory, $"{fName}.{imageExtension}");
Editor.Background = new ImageBrush { ImageSource = await imgPath.OpenImage() };
Editor.RemoveAllAnns();
AddAnnotationsToCanvas(time, detections, true);
await AddAnnotations(timeframe.Time, detections, token);
await AddAnnotations(fName, detections, token);
await _annotationService.SaveAnnotation(fName, imageExtension, detections, SourceEnum.AI, timeframe.Stream, token);
var log = string.Join(Environment.NewLine, detections.Select(det =>
$"{_appConfig.AnnotationConfig.AnnotationClassesDict[det.ClassNumber].Name}: " +
$"{_appConfig.AnnotationConfig.DetectionClassesDict[det.ClassNumber].Name}: " +
$"xy=({det.CenterX:F2},{det.CenterY:F2}), " +
$"size=({det.Width:F2}, {det.Height:F2}), " +
$"prob: {det.Probability:F1}%"));
Dispatcher.Invoke(() => _autoDetectDialog.Log(log));
await _mediator.Publish(new ImageCreatedEvent(imgPath), token);
}
catch (Exception e)
{
+13 -15
View File
@@ -21,7 +21,6 @@ public class AnnotatorEventHandler(
Annotator mainWindow,
FormState formState,
AnnotationService annotationService,
IMediator mediator,
ILogger<AnnotatorEventHandler> logger,
IOptions<DirectoriesConfig> dirConfig)
:
@@ -48,15 +47,15 @@ public class AnnotatorEventHandler(
public async Task Handle(AnnClassSelectedEvent notification, CancellationToken cancellationToken)
{
SelectClass(notification.AnnotationClass);
SelectClass(notification.DetectionClass);
await Task.CompletedTask;
}
private void SelectClass(AnnotationClass annClass)
private void SelectClass(DetectionClass annClass)
{
mainWindow.Editor.CurrentAnnClass = annClass;
foreach (var ann in mainWindow.Editor.CurrentAnns.Where(x => x.IsSelected))
ann.AnnotationClass = annClass;
foreach (var ann in mainWindow.Editor.CurrentDetections.Where(x => x.IsSelected))
ann.DetectionClass = annClass;
mainWindow.LvClasses.SelectedIndex = annClass.Id;
}
@@ -73,7 +72,7 @@ public class AnnotatorEventHandler(
if ((int)key >= (int)Key.NumPad1 && (int)key <= (int)Key.NumPad9)
keyNumber = key - Key.NumPad1;
if (keyNumber.HasValue)
SelectClass((AnnotationClass)mainWindow.LvClasses.Items[keyNumber.Value]!);
SelectClass((DetectionClass)mainWindow.LvClasses.Items[keyNumber.Value]!);
if (_keysControlEnumDict.TryGetValue(key, out var value))
await ControlPlayback(value, cancellationToken);
@@ -144,7 +143,7 @@ public class AnnotatorEventHandler(
mainWindow.SeekTo(mediaPlayer.Time + step);
break;
case PlaybackControlEnum.SaveAnnotations:
await SaveAnnotations();
await SaveAnnotations(cancellationToken);
break;
case PlaybackControlEnum.RemoveSelectedAnns:
@@ -229,18 +228,19 @@ public class AnnotatorEventHandler(
var time = formState.BackgroundTime ?? TimeSpan.FromMilliseconds(mediaPlayer.Time);
var fName = formState.GetTimeName(time);
var currentAnns = mainWindow.Editor.CurrentAnns
.Select(x => new YoloLabel(x.Info, mainWindow.Editor.RenderSize, formState.BackgroundTime.HasValue ? mainWindow.Editor.RenderSize : formState.CurrentVideoSize))
var currentDetections = mainWindow.Editor.CurrentDetections
.Select(x => new Detection(fName, x.GetLabel(mainWindow.Editor.RenderSize, formState.BackgroundTime.HasValue ? mainWindow.Editor.RenderSize : formState.CurrentVideoSize)))
.ToList();
await mainWindow.AddAnnotations(time, currentAnns, cancellationToken);
await mainWindow.AddAnnotations(fName, currentDetections, cancellationToken);
formState.CurrentMedia.HasAnnotations = mainWindow.Annotations.Count != 0;
formState.CurrentMedia.HasAnnotations = mainWindow.Detections.Count != 0;
mainWindow.LvFiles.Items.Refresh();
mainWindow.Editor.RemoveAllAnns();
var isVideo = formState.CurrentMedia.MediaType == MediaTypes.Video;
var imgPath = Path.Combine(dirConfig.Value.ImagesDirectory, $"{fName}{(isVideo ? ".jpg" : Path.GetExtension(formState.CurrentMedia.Path))}");
var imageExtension = isVideo ? ".jpg" : Path.GetExtension(formState.CurrentMedia.Path);
var imgPath = Path.Combine(dirConfig.Value.ImagesDirectory, $"{fName}{imageExtension}");
if (isVideo)
{
@@ -267,8 +267,6 @@ public class AnnotatorEventHandler(
File.Copy(formState.CurrentMedia.Path, imgPath, overwrite: true);
NextMedia();
}
await annotationService.SaveAnnotation(fName, currentAnns, SourceEnum.Manual, token: cancellationToken);
await mediator.Publish(new ImageCreatedEvent(imgPath), cancellationToken);
await annotationService.SaveAnnotation(fName, imageExtension, currentDetections, SourceEnum.Manual, token: cancellationToken);
}
}
@@ -3,7 +3,7 @@ using MediatR;
namespace Azaion.Annotator.DTO;
public class AnnClassSelectedEvent(AnnotationClass annotationClass) : INotification
public class AnnClassSelectedEvent(DetectionClass detectionClass) : INotification
{
public AnnotationClass AnnotationClass { get; } = annotationClass;
public DetectionClass DetectionClass { get; } = detectionClass;
}
@@ -1,19 +0,0 @@
using System.IO;
using System.Windows.Media.Imaging;
namespace Azaion.Annotator.Extensions;
public static class BitmapExtensions
{
public static async Task<BitmapImage> OpenImage(this string imagePath)
{
var image = new BitmapImage();
await using var stream = File.OpenRead(imagePath);
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
return image;
}
}
+3 -3
View File
@@ -15,7 +15,7 @@ namespace Azaion.Annotator;
public interface IAIDetector
{
Task<List<Detection>> Detect(Stream imageStream, CancellationToken cancellationToken = default);
Task<List<Detection>> Detect(string fName, Stream imageStream, CancellationToken cancellationToken = default);
}
public class YOLODetector(IOptions<AIRecognitionConfig> recognitionConfig, IResourceLoader resourceLoader) : IAIDetector, IDisposable
@@ -25,7 +25,7 @@ public class YOLODetector(IOptions<AIRecognitionConfig> recognitionConfig, IReso
private const string YOLO_MODEL = "azaion.onnx";
public async Task<List<Detection>> Detect(Stream imageStream, CancellationToken cancellationToken)
public async Task<List<Detection>> Detect(string fName, Stream imageStream, CancellationToken cancellationToken)
{
if (_predictor == null)
{
@@ -42,7 +42,7 @@ public class YOLODetector(IOptions<AIRecognitionConfig> recognitionConfig, IReso
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);
return new Detection(label, (double?)d.Confidence * 100);
return new Detection(fName, label, (double?)d.Confidence * 100);
}).ToList();
return FilterOverlapping(detections);