From 71006a2462f39083730569171529e6e480f7ef77 Mon Sep 17 00:00:00 2001 From: Oleksandr Bezdieniezhnykh Date: Tue, 16 Jul 2024 14:06:05 +0300 Subject: [PATCH] Add annotationResult and put them into json --- Azaion.Annotator.Test/DictTest.cs | 8 +- Azaion.Annotator/App.xaml.cs | 18 ++- Azaion.Annotator/Azaion.Annotator.csproj | 6 + .../Controls/AnnotationControl.cs | 8 +- Azaion.Annotator/Controls/CanvasEditor.cs | 43 ++--- Azaion.Annotator/DTO/AnnotationInfo.cs | 121 -------------- Azaion.Annotator/DTO/AnnotationResult.cs | 28 ++++ Azaion.Annotator/DTO/Config.cs | 7 +- Azaion.Annotator/DTO/FormState.cs | 25 ++- Azaion.Annotator/DTO/Label.cs | 153 ++++++++++++++++++ Azaion.Annotator/MainWindow.xaml | 50 +++++- Azaion.Annotator/MainWindow.xaml.cs | 97 ++++++----- Azaion.Annotator/PlayerControlHandler.cs | 121 ++++++++------ Azaion.Annotator/config.json | 7 +- New Text Document.txt | 5 + анотації. довідка.txt | 0 16 files changed, 445 insertions(+), 252 deletions(-) delete mode 100644 Azaion.Annotator/DTO/AnnotationInfo.cs create mode 100644 Azaion.Annotator/DTO/AnnotationResult.cs create mode 100644 Azaion.Annotator/DTO/Label.cs create mode 100644 New Text Document.txt create mode 100644 анотації. довідка.txt diff --git a/Azaion.Annotator.Test/DictTest.cs b/Azaion.Annotator.Test/DictTest.cs index 02c4023..5f8ba53 100644 --- a/Azaion.Annotator.Test/DictTest.cs +++ b/Azaion.Annotator.Test/DictTest.cs @@ -5,16 +5,16 @@ namespace Azaion.Annotator.Test; public class DictTest { - public Dictionary> Annotations = new(); + public Dictionary> Annotations = new(); [Fact] public void DictAddTest() { - Annotations["sd"] = [new AnnotationInfo(1, 2, 2, 2, 2)]; + Annotations["sd"] = [new YoloLabel(1, 2, 2, 2, 2)]; Annotations["sd"] = [ - new AnnotationInfo(1, 2, 2, 2, 2), - new AnnotationInfo(0, 1, 3, 2, 1) + new YoloLabel(1, 2, 2, 2, 2), + new YoloLabel(0, 1, 3, 2, 1) ]; } } \ No newline at end of file diff --git a/Azaion.Annotator/App.xaml.cs b/Azaion.Annotator/App.xaml.cs index 726c496..bfac05a 100644 --- a/Azaion.Annotator/App.xaml.cs +++ b/Azaion.Annotator/App.xaml.cs @@ -4,19 +4,33 @@ using System.Windows; using System.Windows.Input; using System.Windows.Threading; using Azaion.Annotator.DTO; +using Azaion.Annotator.Extensions; using LibVLCSharp.Shared; using MediatR; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Serilog; +using ILogger = Microsoft.Extensions.Logging.ILogger; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Azaion.Annotator; public partial class App : Application { private readonly ServiceProvider _serviceProvider; - private IMediator _mediator; + private readonly IMediator _mediator; public App() { + Log.Logger = new LoggerConfiguration() + .Enrich.FromLogContext() + .MinimumLevel.Warning() + .WriteTo.Console() + .WriteTo.File( + path: "Logs/log.txt", + rollingInterval: RollingInterval.Day) + .CreateLogger(); + var services = new ServiceCollection(); services.AddSingleton(); services.AddSingleton(); @@ -37,7 +51,7 @@ public partial class App : Application private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) { - File.AppendAllText("logs.txt", e.Exception.Message); + Log.Logger.Error(e.Exception, e.Exception.Message); e.Handled = true; } diff --git a/Azaion.Annotator/Azaion.Annotator.csproj b/Azaion.Annotator/Azaion.Annotator.csproj index af35717..f0f557c 100644 --- a/Azaion.Annotator/Azaion.Annotator.csproj +++ b/Azaion.Annotator/Azaion.Annotator.csproj @@ -15,7 +15,13 @@ + + + + + + diff --git a/Azaion.Annotator/Controls/AnnotationControl.cs b/Azaion.Annotator/Controls/AnnotationControl.cs index 118e305..5810b88 100644 --- a/Azaion.Annotator/Controls/AnnotationControl.cs +++ b/Azaion.Annotator/Controls/AnnotationControl.cs @@ -14,7 +14,8 @@ public class AnnotationControl : Border private readonly Grid _grid; private readonly TextBlock _classNameLabel; - + public TimeSpan Time { get; set; } + private AnnotationClass _annotationClass = null!; public AnnotationClass AnnotationClass { @@ -40,8 +41,9 @@ public class AnnotationControl : Border } } - public AnnotationControl(AnnotationClass annotationClass, Action resizeStart) + public AnnotationControl(AnnotationClass annotationClass, TimeSpan time, Action resizeStart) { + Time = time; _resizeStart = resizeStart; _classNameLabel = new TextBlock { @@ -104,5 +106,5 @@ public class AnnotationControl : Border return rect; } - public AnnotationInfo Info => new(AnnotationClass.Id, Canvas.GetLeft(this), Canvas.GetTop(this), Width, Height); + public CanvasLabel Info => new(AnnotationClass.Id, Canvas.GetLeft(this), Canvas.GetTop(this), Width, Height); } diff --git a/Azaion.Annotator/Controls/CanvasEditor.cs b/Azaion.Annotator/Controls/CanvasEditor.cs index 4ab9184..ffd5f21 100644 --- a/Azaion.Annotator/Controls/CanvasEditor.cs +++ b/Azaion.Annotator/Controls/CanvasEditor.cs @@ -1,6 +1,5 @@ using System.Windows; using System.Windows.Controls; -using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; @@ -26,7 +25,8 @@ public class CanvasEditor : Canvas private AnnotationControl _curAnn = null!; private const int MIN_SIZE = 20; - + private readonly TimeSpan _viewThreshold = TimeSpan.FromMilliseconds(400); + public FormState FormState { get; set; } = null!; public IMediator Mediator { get; set; } = null!; @@ -283,30 +283,28 @@ public class CanvasEditor : Canvas var height = Math.Abs(endPos.Y - _newAnnotationStartPos.Y); if (width < MIN_SIZE || height < MIN_SIZE) return; - - var annotationControl = new AnnotationControl(CurrentAnnClass, AnnotationResizeStart) + + var mainWindow = (MainWindow)((Window)((Grid)Parent).Parent).Owner; + var time = TimeSpan.FromMilliseconds(mainWindow.VideoView.MediaPlayer.Time); + CreateAnnotation(CurrentAnnClass, time, new CanvasLabel { Width = width, - Height = height - }; - annotationControl.MouseDown += AnnotationPositionStart; - SetLeft(annotationControl, Math.Min(endPos.X, _newAnnotationStartPos.X)); - SetTop(annotationControl, Math.Min(endPos.Y, _newAnnotationStartPos.Y)); - Children.Add(annotationControl); - CurrentAnns.Add(annotationControl); - _newAnnotationRect.Fill = new SolidColorBrush(CurrentAnnClass.Color); + Height = height, + X = Math.Min(endPos.X, _newAnnotationStartPos.X), + Y = Math.Min(endPos.Y, _newAnnotationStartPos.Y) + }); } - public AnnotationControl CreateAnnotation(AnnotationClass annClass, AnnotationInfo info) + public AnnotationControl CreateAnnotation(AnnotationClass annClass, TimeSpan time, CanvasLabel canvasLabel) { - var annotationControl = new AnnotationControl(annClass, AnnotationResizeStart) + var annotationControl = new AnnotationControl(annClass, time, AnnotationResizeStart) { - Width = info.Width, - Height = info.Height + Width = canvasLabel.Width, + Height = canvasLabel.Height }; annotationControl.MouseDown += AnnotationPositionStart; - SetLeft(annotationControl, info.X ); - SetTop(annotationControl, info.Y); + SetLeft(annotationControl, canvasLabel.X ); + SetTop(annotationControl, canvasLabel.Y); Children.Add(annotationControl); CurrentAnns.Add(annotationControl); _newAnnotationRect.Fill = new SolidColorBrush(annClass.Color); @@ -323,12 +321,17 @@ public class CanvasEditor : Canvas CurrentAnns.Remove(ann); } } - public void RemoveAllAnns() => RemoveAnnotations(CurrentAnns.ToList()); - public void RemoveSelectedAnns() => RemoveAnnotations(CurrentAnns.Where(x => x.IsSelected).ToList()); + public void RemoveAllAnns() => RemoveAnnotations(CurrentAnns); + public void RemoveSelectedAnns() => RemoveAnnotations(CurrentAnns.Where(x => x.IsSelected)); private void ClearSelections() { foreach (var ann in CurrentAnns) ann.IsSelected = false; } + + public void ClearExpiredAnnotations(TimeSpan time) + { + RemoveAnnotations(CurrentAnns.Where(x => time - x.Time > _viewThreshold)); + } } \ No newline at end of file diff --git a/Azaion.Annotator/DTO/AnnotationInfo.cs b/Azaion.Annotator/DTO/AnnotationInfo.cs deleted file mode 100644 index 765119a..0000000 --- a/Azaion.Annotator/DTO/AnnotationInfo.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System.Globalization; -using System.Windows; - -namespace Azaion.Annotator.DTO; - -public class AnnotationInfo -{ - public int ClassNumber { get; set; } - public double X { get; set; } - public double Y { get; set; } - public double Width { get; set; } - public double Height { get; set; } - - public AnnotationInfo() { } - public AnnotationInfo(int classNumber, double x, double y, double width, double height) - { - ClassNumber = classNumber; - X = x; - Y = y; - Width = width; - Height = height; - } - - public override string ToString() => $"{ClassNumber} {X:F5} {Y:F5} {Width:F5} {Height:F5}".Replace(',', '.'); - - public AnnotationInfo ToLabelCoordinates(Size canvasSize, Size videoSize) - { - var cw = canvasSize.Width; - var ch = canvasSize.Height; - var canvasAR = cw / ch; - var videoAR = videoSize.Width / videoSize.Height; - - var annInfo = new AnnotationInfo { ClassNumber = this.ClassNumber }; - double left, top; - if (videoAR > canvasAR) //100% width - { - left = X / cw; - annInfo.Width = Width / cw; - var realHeight = cw / videoAR; //real video height in pixels on canvas - var blackStripHeight = (ch - realHeight) / 2.0; //height of black strips at the top and bottom - top = (Y - blackStripHeight) / realHeight; - annInfo.Height = Height / realHeight; - } - else //100% height - { - top = Y / ch; - annInfo.Height = Height / ch; - var realWidth = ch * videoAR; //real video width in pixels on canvas - var blackStripWidth = (cw - realWidth) / 2.0; //height of black strips at the top and bottom - left = (X - blackStripWidth) / realWidth; - annInfo.Width = Width / realWidth; - } - - annInfo.X = left + annInfo.Width / 2.0; - annInfo.Y = top + annInfo.Height / 2.0; - - return annInfo; - } - - public AnnotationInfo ToCanvasCoordinates(Size canvasSize, Size videoSize) - { - var cw = canvasSize.Width; - var ch = canvasSize.Height; - var canvasAR = cw / ch; - var videoAR = videoSize.Width / videoSize.Height; - - var annInfo = new AnnotationInfo { ClassNumber = this.ClassNumber }; - - double left = X - Width / 2; - double top = Y - Height / 2; - - if (videoAR > canvasAR) //100% width - { - var realHeight = cw / videoAR; //real video height in pixels on canvas - var blackStripHeight = (ch - realHeight) / 2.0; //height of black strips at the top and bottom - - annInfo.X = left * cw; - annInfo.Y = top * realHeight + blackStripHeight; - annInfo.Width = Width * cw; - annInfo.Height = Height * realHeight; - } - else //100% height - { - var realWidth = ch * videoAR; //real video width in pixels on canvas - var blackStripWidth = (cw - realWidth) / 2.0; //height of black strips at the top and bottom - - annInfo.X = left * realWidth + blackStripWidth; - annInfo.Y = top * ch; - annInfo.Width = Width * realWidth; - annInfo.Height = Height * ch; - } - return annInfo; - } - - public static AnnotationInfo? Parse(string? s) - { - if (s == null || string.IsNullOrEmpty(s)) - return null; - - var strs = s.Replace(',','.').Split(' '); - if (strs.Length != 5) - return null; - - try - { - var res = new AnnotationInfo - { - ClassNumber = int.Parse(strs[0], CultureInfo.InvariantCulture), - X = double.Parse(strs[1], CultureInfo.InvariantCulture), - Y = double.Parse(strs[2], CultureInfo.InvariantCulture), - Width = double.Parse(strs[3], CultureInfo.InvariantCulture), - Height = double.Parse(strs[4], CultureInfo.InvariantCulture) - }; - return res; - } - catch (Exception) - { - return null; - } - } -} \ No newline at end of file diff --git a/Azaion.Annotator/DTO/AnnotationResult.cs b/Azaion.Annotator/DTO/AnnotationResult.cs new file mode 100644 index 0000000..e0fac56 --- /dev/null +++ b/Azaion.Annotator/DTO/AnnotationResult.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; + +namespace Azaion.Annotator.DTO; + +public class AnnotationResult +{ + [JsonProperty(PropertyName = "f")] + public string Image { get; set; } = null!; + [JsonProperty(PropertyName = "t")] + public TimeSpan Time { get; set; } + + [JsonProperty(PropertyName = "p")] + public double Percentage { get; set; } + + public double Lat { get; set; } + public double Lon { get; set; } + public List Labels { get; set; } = new(); + + + public AnnotationResult() { } + public AnnotationResult(TimeSpan time, string timeName, List labels) + { + Labels = labels; + Time = time; + Image = $"{timeName}.jpg"; + Percentage = 100; + } +} \ No newline at end of file diff --git a/Azaion.Annotator/DTO/Config.cs b/Azaion.Annotator/DTO/Config.cs index 5c834de..9aa092d 100644 --- a/Azaion.Annotator/DTO/Config.cs +++ b/Azaion.Annotator/DTO/Config.cs @@ -10,9 +10,13 @@ namespace Azaion.Annotator.DTO; public class Config { private const string CONFIG_PATH = "config.json"; + private const string DEFAULT_VIDEO_DIR = "video"; + private const string DEFAULT_LABELS_DIR = "labels"; private const string DEFAULT_IMAGES_DIR = "images"; + private const string DEFAULT_RESULTS_DIR = "results"; + private static readonly Size DefaultWindowSize = new(1280, 720); private static readonly Point DefaultWindowLocation = new(100, 100); @@ -20,7 +24,8 @@ public class Config public string VideosDirectory { get; set; } = DEFAULT_VIDEO_DIR; public string LabelsDirectory { get; set; } = DEFAULT_LABELS_DIR; public string ImagesDirectory { get; set; } = DEFAULT_IMAGES_DIR; - + public string ResultsDirectory { get; set; } = DEFAULT_RESULTS_DIR; + public List AnnotationClasses { get; set; } = []; public Size WindowSize { get; set; } public Point WindowLocation { get; set; } diff --git a/Azaion.Annotator/DTO/FormState.cs b/Azaion.Annotator/DTO/FormState.cs index c0cbf7f..b5dc947 100644 --- a/Azaion.Annotator/DTO/FormState.cs +++ b/Azaion.Annotator/DTO/FormState.cs @@ -1,4 +1,5 @@ -using System.IO; +using System.Globalization; +using System.IO; using System.Windows; namespace Azaion.Annotator.DTO; @@ -12,6 +13,26 @@ public class FormState public string VideoName => Path.GetFileNameWithoutExtension(CurrentFile).Replace(" ", ""); public TimeSpan CurrentVideoLength { get; set; } public int CurrentVolume { get; set; } = 100; - + public List AnnotationResults { get; set; } = []; + public string GetTimeName(TimeSpan ts) => $"{VideoName}_{ts:hmmssf}"; + + public TimeSpan? GetTime(string name) + { + var timeStr = name.Split("_").LastOrDefault(); + if (string.IsNullOrEmpty(timeStr)) + return null; + + try + { + //For some reason, TimeSpan.ParseExact doesn't work on every platform. + return new TimeSpan( + days: 0, + hours: int.Parse(timeStr[0..1]), + minutes: int.Parse(timeStr[1..3]), + seconds: int.Parse(timeStr[3..5]), + milliseconds: int.Parse(timeStr[5..6]) * 100); + } + catch (Exception e) { return null; } + } } diff --git a/Azaion.Annotator/DTO/Label.cs b/Azaion.Annotator/DTO/Label.cs new file mode 100644 index 0000000..fde951c --- /dev/null +++ b/Azaion.Annotator/DTO/Label.cs @@ -0,0 +1,153 @@ +using System.Globalization; +using System.Windows; +using Newtonsoft.Json; + +namespace Azaion.Annotator.DTO; + +public abstract class Label +{ + [JsonProperty(PropertyName = "cl")] + public int ClassNumber { get; set; } + + public Label(){} + public Label(int classNumber) { ClassNumber = classNumber; } +} + +public class CanvasLabel : Label +{ + public double X { get; set; } + public double Y { get; set; } + public double Width { get; set; } + public double Height { get; set; } + + public CanvasLabel() { } + public CanvasLabel(int classNumber, double x, double y, double width, double height) : base(classNumber) + { + X = x; + Y = y; + Width = width; + Height = height; + } + + public CanvasLabel(YoloLabel label, Size canvasSize, Size videoSize) + { + var cw = canvasSize.Width; + var ch = canvasSize.Height; + var canvasAr = cw / ch; + var videoAr = videoSize.Width / videoSize.Height; + + ClassNumber = label.ClassNumber; + + var left = X - Width / 2; + var top = Y - Height / 2; + + if (videoAr > canvasAr) //100% width + { + var realHeight = cw / videoAr; //real video height in pixels on canvas + var blackStripHeight = (ch - realHeight) / 2.0; //height of black strips at the top and bottom + + X = left * cw; + Y = top * realHeight + blackStripHeight; + Width = label.Width * cw; + Height = label.Height * realHeight; + } + else //100% height + { + var realWidth = ch * videoAr; //real video width in pixels on canvas + var blackStripWidth = (cw - realWidth) / 2.0; //height of black strips at the top and bottom + + X = left * realWidth + blackStripWidth; + Y = top * ch; + Width = label.Width * realWidth; + Height = label.Height * ch; + } + } +} + +public class YoloLabel : Label +{ + [JsonProperty(PropertyName = "x")] + public double CenterX { get; set; } + + [JsonProperty(PropertyName = "y")] + public double CenterY { get; set; } + + [JsonProperty(PropertyName = "w")] + public double Width { get; set; } + + [JsonProperty(PropertyName = "h")] + public double Height { get; set; } + + public YoloLabel() { } + public YoloLabel(int classNumber, double centerX, double centerY, double width, double height) : base(classNumber) + { + CenterX = centerX; + CenterY = centerY; + Width = width; + Height = height; + } + + public YoloLabel(CanvasLabel canvasLabel, Size canvasSize, Size videoSize) + { + + var cw = canvasSize.Width; + var ch = canvasSize.Height; + var canvasAr = cw / ch; + var videoAr = videoSize.Width / videoSize.Height; + + ClassNumber = canvasLabel.ClassNumber; + + double left, top; + if (videoAr > canvasAr) //100% width + { + left = canvasLabel.X / cw; + Width = canvasLabel.Width / cw; + var realHeight = cw / videoAr; //real video height in pixels on canvas + var blackStripHeight = (ch - realHeight) / 2.0; //height of black strips at the top and bottom + top = (canvasLabel.Y - blackStripHeight) / realHeight; + Height = canvasLabel.Height / realHeight; + } + else //100% height + { + top = canvasLabel.Y / ch; + Height = canvasLabel.Height / ch; + var realWidth = ch * videoAr; //real video width in pixels on canvas + var blackStripWidth = (cw - realWidth) / 2.0; //height of black strips at the top and bottom + left = (canvasLabel.X - blackStripWidth) / realWidth; + Width = canvasLabel.Width / realWidth; + } + + CenterX = left + canvasLabel.Width / 2.0; + CenterY = top + canvasLabel.Height / 2.0; + } + + public static YoloLabel? Parse(string s) + { + if (string.IsNullOrEmpty(s)) + return null; + + var strings = s.Replace(',','.').Split(' '); + if (strings.Length != 5) + return null; + + try + { + var res = new YoloLabel + { + ClassNumber = int.Parse(strings[0], CultureInfo.InvariantCulture), + CenterX = double.Parse(strings[1], CultureInfo.InvariantCulture), + CenterY = double.Parse(strings[2], CultureInfo.InvariantCulture), + Width = double.Parse(strings[3], CultureInfo.InvariantCulture), + Height = double.Parse(strings[4], CultureInfo.InvariantCulture) + }; + return res; + } + catch (Exception) + { + return null; + } + } + + public override string ToString() => $"{ClassNumber} {CenterX:F5} {CenterY:F5} {Width:F5} {Height:F5}".Replace(',', '.'); + +} \ No newline at end of file diff --git a/Azaion.Annotator/MainWindow.xaml b/Azaion.Annotator/MainWindow.xaml index 4f3097a..51f0e58 100644 --- a/Azaion.Annotator/MainWindow.xaml +++ b/Azaion.Annotator/MainWindow.xaml @@ -62,6 +62,7 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -373,6 +420,7 @@ diff --git a/Azaion.Annotator/MainWindow.xaml.cs b/Azaion.Annotator/MainWindow.xaml.cs index d7dc3bd..de0d69d 100644 --- a/Azaion.Annotator/MainWindow.xaml.cs +++ b/Azaion.Annotator/MainWindow.xaml.cs @@ -1,13 +1,12 @@ using System.Collections.ObjectModel; -using System.Drawing; using System.IO; -using System.Reflection; using System.Windows; using Azaion.Annotator.DTO; using Azaion.Annotator.Extensions; using LibVLCSharp.Shared; using MediatR; using Microsoft.WindowsAPICodePack.Dialogs; +using Newtonsoft.Json; using Point = System.Windows.Point; using Size = System.Windows.Size; @@ -22,12 +21,12 @@ public partial class MainWindow private readonly Config _config; private readonly HelpWindow _helpWindow; - private readonly TimeSpan _annotationTime = TimeSpan.FromSeconds(1); public ObservableCollection AnnotationClasses { get; set; } = new(); private bool _suspendLayout; - - public Dictionary> Annotations { get; set; } = new(); + + public Dictionary> Annotations { get; set; } = new(); + public Dictionary> AnnotationResults { get; set; } = new(); public MainWindow(LibVLC libVLC, MediaPlayer mediaPlayer, IMediator mediator, @@ -115,38 +114,17 @@ public partial class MainWindow Dispatcher.Invoke(() => VideoSlider.Value = _mediaPlayer.Position * VideoSlider.Maximum); Dispatcher.Invoke(() => StatusClock.Text = $"{TimeSpan.FromMilliseconds(_mediaPlayer.Time):mm\\:ss} / {_formState.CurrentVideoLength:mm\\:ss}"); - var curTime = _formState.GetTimeName(TimeSpan.FromMilliseconds(_mediaPlayer.Time)); - if (!Annotations.TryGetValue(curTime, out var annotationInfos)) + var time = TimeSpan.FromMilliseconds(_mediaPlayer.Time); + if (!Annotations.TryGetValue(time, out var annotations)) return; - var annotations = annotationInfos.Select(info => + foreach (var ann in annotations) { - var annClass = _config.AnnotationClasses[info.ClassNumber]; - var annInfo = info.ToCanvasCoordinates(Editor.RenderSize, _formState.CurrentVideoSize); - var annotation = Dispatcher.Invoke(() => Editor.CreateAnnotation(annClass, annInfo)); - return annotation; - }).ToList(); - - //remove annotations: either in 1 sec, either earlier if there is next annotation in a dictionary - var timeStr = curTime.Split("_").LastOrDefault(); - if (!int.TryParse(timeStr, out var time)) - return; - - var ts = TimeSpan.FromMilliseconds(time * 100); - var timeSpanRemove = Enumerable.Range(1, (int)_annotationTime.TotalMilliseconds / 100 - 1) - .Select(x => - { - var timeNext = TimeSpan.FromMilliseconds(x * 100); - var fName = _formState.GetTimeName(ts.Add(timeNext)); - return Annotations.ContainsKey(fName) ? timeNext : (TimeSpan?)null; - }).FirstOrDefault(x => x != null) ?? _annotationTime; - - _ = Task.Run(async () => - { - await Task.Delay(timeSpanRemove); - Dispatcher.Invoke(() => Editor.RemoveAnnotations(annotations)); - }); - + var annClass = _config.AnnotationClasses[ann.ClassNumber]; + var annInfo = new CanvasLabel(ann, Editor.RenderSize, _formState.CurrentVideoSize); + Dispatcher.Invoke(() => Editor.CreateAnnotation(annClass, time, annInfo)); + } + Dispatcher.Invoke(() => Editor.ClearExpiredAnnotations(time)); }; VideoSlider.ValueChanged += (value, newValue) => @@ -173,26 +151,61 @@ public partial class MainWindow public void LoadExistingAnnotations() { - var dirInfo = new DirectoryInfo(_config.LabelsDirectory); - if (!dirInfo.Exists) - return; + Annotations = LoadAnnotations(); + _formState.AnnotationResults = LoadAnnotationResults(); + } - var files = dirInfo.GetFiles($"{_formState.VideoName}_*"); - Annotations = files.ToDictionary(f => Path.GetFileNameWithoutExtension(f.Name), f => + private Dictionary> LoadAnnotations() + { + var labelDir = new DirectoryInfo(_config.LabelsDirectory); + if (!labelDir.Exists) + return new Dictionary>(); + + var labelFiles = labelDir.GetFiles($"{_formState.VideoName}_*"); + return labelFiles.Select(x => { - var str = File.ReadAllText(f.FullName); - return str.Split(Environment.NewLine).Select(AnnotationInfo.Parse) + var name = Path.GetFileNameWithoutExtension(x.Name); + return new + { + Name = x.FullName, + Time = _formState.GetTime(name) + }; + }).ToDictionary(f => f.Time!.Value, f => + { + var str = File.ReadAllText(f.Name); + return str.Split(Environment.NewLine).Select(YoloLabel.Parse) .Where(x => x != null) .ToList(); })!; } + private List LoadAnnotationResults() + { + var resultDir = new DirectoryInfo(_config!.ResultsDirectory); + if (!resultDir.Exists) + Directory.CreateDirectory(_config!.ResultsDirectory); + + var resultsFile = resultDir.GetFiles($"{_formState!.CurrentFile}*").FirstOrDefault(); + + List results; + if (resultsFile == null) + { + results = Annotations.Select(anns => new AnnotationResult(anns.Key, _formState.GetTimeName(anns.Key), anns.Value)) + .ToList(); + File.WriteAllText($"{_config.ResultsDirectory}/{_formState.VideoName}.json", JsonConvert.SerializeObject(results, Formatting.Indented)); + } + else + results = JsonConvert.DeserializeObject>(File.ReadAllText(File.ReadAllText(resultsFile.FullName)))!; + return results; + } + + private void ReloadFiles() { var dir = new DirectoryInfo(_config.VideosDirectory); if (!dir.Exists) return; - + var files = dir.GetFiles("mp4", "mov").Select(x => { _mediaPlayer.Media = new Media(_libVLC, x.FullName); diff --git a/Azaion.Annotator/PlayerControlHandler.cs b/Azaion.Annotator/PlayerControlHandler.cs index 5045800..9414fcd 100644 --- a/Azaion.Annotator/PlayerControlHandler.cs +++ b/Azaion.Annotator/PlayerControlHandler.cs @@ -4,6 +4,7 @@ using System.Windows.Input; using Azaion.Annotator.DTO; using LibVLCSharp.Shared; using MediatR; +using Newtonsoft.Json; namespace Azaion.Annotator; @@ -98,56 +99,64 @@ public class PlayerControlHandler(LibVLC libVLC, MediaPlayer mediaPlayer, MainWi private async Task ControlPlayback(PlaybackControlEnum controlEnum) { - var isCtrlPressed = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl); - var step = isCtrlPressed ? LARGE_STEP : STEP; - - switch (controlEnum) + try { - case PlaybackControlEnum.Play: - Play(); - break; - case PlaybackControlEnum.Pause: - mediaPlayer.Pause(); - if (!mediaPlayer.IsPlaying) - mainWindow.BlinkHelp(HelpTexts.HelpTextsDict[HelpTextEnum.AnnotationHelp]); - break; - case PlaybackControlEnum.Stop: - mediaPlayer.Stop(); - break; - case PlaybackControlEnum.PreviousFrame: - mediaPlayer.SetPause(true); - mediaPlayer.Time -= step; - mainWindow.VideoSlider.Value = mediaPlayer.Position * 100; - break; - case PlaybackControlEnum.NextFrame: - mediaPlayer.SetPause(true); - mediaPlayer.Time += step; - mainWindow.VideoSlider.Value = mediaPlayer.Position * 100; - break; - case PlaybackControlEnum.SaveAnnotations: - await SaveAnnotations(); - break; - case PlaybackControlEnum.RemoveSelectedAnns: - mainWindow.Editor.RemoveSelectedAnns(); - break; - case PlaybackControlEnum.RemoveAllAnns: - mainWindow.Editor.RemoveAllAnns(); - break; - case PlaybackControlEnum.TurnOnVolume: - mainWindow.TurnOnVolumeBtn.Visibility = Visibility.Collapsed; - mainWindow.TurnOffVolumeBtn.Visibility = Visibility.Visible; - mediaPlayer.Volume = formState.CurrentVolume; - break; - case PlaybackControlEnum.TurnOffVolume: - mainWindow.TurnOffVolumeBtn.Visibility = Visibility.Collapsed; - mainWindow.TurnOnVolumeBtn.Visibility = Visibility.Visible; - formState.CurrentVolume = mediaPlayer.Volume; - mediaPlayer.Volume = 0; - break; - case PlaybackControlEnum.None: - break; - default: - throw new ArgumentOutOfRangeException(nameof(controlEnum), controlEnum, null); + var isCtrlPressed = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl); + var step = isCtrlPressed ? LARGE_STEP : STEP; + + switch (controlEnum) + { + case PlaybackControlEnum.Play: + Play(); + break; + case PlaybackControlEnum.Pause: + mediaPlayer.Pause(); + if (!mediaPlayer.IsPlaying) + mainWindow.BlinkHelp(HelpTexts.HelpTextsDict[HelpTextEnum.AnnotationHelp]); + break; + case PlaybackControlEnum.Stop: + mediaPlayer.Stop(); + break; + case PlaybackControlEnum.PreviousFrame: + mediaPlayer.SetPause(true); + mediaPlayer.Time -= step; + mainWindow.VideoSlider.Value = mediaPlayer.Position * 100; + break; + case PlaybackControlEnum.NextFrame: + mediaPlayer.SetPause(true); + mediaPlayer.Time += step; + mainWindow.VideoSlider.Value = mediaPlayer.Position * 100; + break; + case PlaybackControlEnum.SaveAnnotations: + await SaveAnnotations(); + break; + case PlaybackControlEnum.RemoveSelectedAnns: + mainWindow.Editor.RemoveSelectedAnns(); + break; + case PlaybackControlEnum.RemoveAllAnns: + mainWindow.Editor.RemoveAllAnns(); + break; + case PlaybackControlEnum.TurnOnVolume: + mainWindow.TurnOnVolumeBtn.Visibility = Visibility.Collapsed; + mainWindow.TurnOffVolumeBtn.Visibility = Visibility.Visible; + mediaPlayer.Volume = formState.CurrentVolume; + break; + case PlaybackControlEnum.TurnOffVolume: + mainWindow.TurnOffVolumeBtn.Visibility = Visibility.Collapsed; + mainWindow.TurnOnVolumeBtn.Visibility = Visibility.Visible; + formState.CurrentVolume = mediaPlayer.Volume; + mediaPlayer.Volume = 0; + break; + case PlaybackControlEnum.None: + break; + default: + throw new ArgumentOutOfRangeException(nameof(controlEnum), controlEnum, null); + } + } + catch (Exception e) + { + Console.WriteLine(e); + throw; } } @@ -182,9 +191,10 @@ public class PlayerControlHandler(LibVLC libVLC, MediaPlayer mediaPlayer, MainWi if (string.IsNullOrEmpty(formState.CurrentFile)) return; - var fName = formState.GetTimeName(TimeSpan.FromMilliseconds(mediaPlayer.Time)); + var time = TimeSpan.FromMilliseconds(mediaPlayer.Time); + var fName = formState.GetTimeName(time); var currentAnns = mainWindow.Editor.CurrentAnns - .Select(x => x.Info.ToLabelCoordinates(mainWindow.Editor.RenderSize, formState.CurrentVideoSize)) + .Select(x => new YoloLabel(x.Info, mainWindow.Editor.RenderSize, formState.CurrentVideoSize)) .ToList(); var labels = string.Join(Environment.NewLine, currentAnns.Select(x => x.ToString())); @@ -192,14 +202,19 @@ public class PlayerControlHandler(LibVLC libVLC, MediaPlayer mediaPlayer, MainWi Directory.CreateDirectory(config.LabelsDirectory); if (!Directory.Exists(config.ImagesDirectory)) Directory.CreateDirectory(config.ImagesDirectory); - + if (!Directory.Exists(config.ResultsDirectory)) + Directory.CreateDirectory(config.ResultsDirectory); + await File.WriteAllTextAsync($"{config.LabelsDirectory}/{fName}.txt", labels); + formState.AnnotationResults.Add(new AnnotationResult(time, fName, currentAnns)); + await File.WriteAllTextAsync($"{config.ResultsDirectory}/{fName}.json", JsonConvert.SerializeObject(formState.AnnotationResults)); + var resultHeight = (uint)Math.Round(RESULT_WIDTH / formState.CurrentVideoSize.Width * formState.CurrentVideoSize.Height); mediaPlayer.TakeSnapshot(0, $"{config.ImagesDirectory}/{fName}.jpg", RESULT_WIDTH, resultHeight); - mainWindow.Annotations[fName] = currentAnns; + mainWindow.Annotations[time] = currentAnns; mainWindow.Editor.RemoveAllAnns(); mediaPlayer.Play(); } diff --git a/Azaion.Annotator/config.json b/Azaion.Annotator/config.json index 1fa8dd9..193c4be 100644 --- a/Azaion.Annotator/config.json +++ b/Azaion.Annotator/config.json @@ -1,7 +1,8 @@ { - "VideosDirectory": "D:\\dev\\azaion\\ai\\ai-data", - "LabelsDirectory": "D:\\dev\\azaion\\ai\\ai-data\\Azaion.Annotator\\labels", - "ImagesDirectory": "D:\\dev\\azaion\\ai\\ai-data\\Azaion.Annotator\\images", + "VideosDirectory": "E:\\Azaion3\\Videos", + "LabelsDirectory": "E:\\labels", + "ImagesDirectory": "E:\\images", + "ResultsDirectory": "E:\\results", "AnnotationClasses": [ { "Id": 0, diff --git a/New Text Document.txt b/New Text Document.txt new file mode 100644 index 0000000..475ab49 --- /dev/null +++ b/New Text Document.txt @@ -0,0 +1,5 @@ +гармата D20 D30 діацинт мста +макет +активність живої сили +вигорівша трава від пострілів +ящики diff --git a/анотації. довідка.txt b/анотації. довідка.txt new file mode 100644 index 0000000..e69de29