mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 09:56:31 +00:00
Add annotationResult and put them into json
This commit is contained in:
@@ -5,16 +5,16 @@ namespace Azaion.Annotator.Test;
|
|||||||
|
|
||||||
public class DictTest
|
public class DictTest
|
||||||
{
|
{
|
||||||
public Dictionary<string, List<AnnotationInfo>> Annotations = new();
|
public Dictionary<string, List<YoloLabel>> Annotations = new();
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void DictAddTest()
|
public void DictAddTest()
|
||||||
{
|
{
|
||||||
Annotations["sd"] = [new AnnotationInfo(1, 2, 2, 2, 2)];
|
Annotations["sd"] = [new YoloLabel(1, 2, 2, 2, 2)];
|
||||||
Annotations["sd"] =
|
Annotations["sd"] =
|
||||||
[
|
[
|
||||||
new AnnotationInfo(1, 2, 2, 2, 2),
|
new YoloLabel(1, 2, 2, 2, 2),
|
||||||
new AnnotationInfo(0, 1, 3, 2, 1)
|
new YoloLabel(0, 1, 3, 2, 1)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,19 +4,33 @@ using System.Windows;
|
|||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using Azaion.Annotator.DTO;
|
using Azaion.Annotator.DTO;
|
||||||
|
using Azaion.Annotator.Extensions;
|
||||||
using LibVLCSharp.Shared;
|
using LibVLCSharp.Shared;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
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;
|
namespace Azaion.Annotator;
|
||||||
|
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
private readonly ServiceProvider _serviceProvider;
|
private readonly ServiceProvider _serviceProvider;
|
||||||
private IMediator _mediator;
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
public App()
|
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();
|
var services = new ServiceCollection();
|
||||||
services.AddSingleton<MainWindow>();
|
services.AddSingleton<MainWindow>();
|
||||||
services.AddSingleton<HelpWindow>();
|
services.AddSingleton<HelpWindow>();
|
||||||
@@ -37,7 +51,7 @@ public partial class App : Application
|
|||||||
|
|
||||||
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
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;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,13 @@
|
|||||||
<PackageReference Include="LibVLCSharp.WPF" Version="3.8.2" />
|
<PackageReference Include="LibVLCSharp.WPF" Version="3.8.2" />
|
||||||
<PackageReference Include="MediatR" Version="12.2.0" />
|
<PackageReference Include="MediatR" Version="12.2.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="Serilog" Version="4.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.1" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||||
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.20" />
|
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.20" />
|
||||||
<PackageReference Include="WindowsAPICodePack" Version="7.0.4" />
|
<PackageReference Include="WindowsAPICodePack" Version="7.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ public class AnnotationControl : Border
|
|||||||
|
|
||||||
private readonly Grid _grid;
|
private readonly Grid _grid;
|
||||||
private readonly TextBlock _classNameLabel;
|
private readonly TextBlock _classNameLabel;
|
||||||
|
public TimeSpan Time { get; set; }
|
||||||
|
|
||||||
private AnnotationClass _annotationClass = null!;
|
private AnnotationClass _annotationClass = null!;
|
||||||
public AnnotationClass AnnotationClass
|
public AnnotationClass AnnotationClass
|
||||||
{
|
{
|
||||||
@@ -40,8 +41,9 @@ public class AnnotationControl : Border
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public AnnotationControl(AnnotationClass annotationClass, Action<object, MouseButtonEventArgs> resizeStart)
|
public AnnotationControl(AnnotationClass annotationClass, TimeSpan time, Action<object, MouseButtonEventArgs> resizeStart)
|
||||||
{
|
{
|
||||||
|
Time = time;
|
||||||
_resizeStart = resizeStart;
|
_resizeStart = resizeStart;
|
||||||
_classNameLabel = new TextBlock
|
_classNameLabel = new TextBlock
|
||||||
{
|
{
|
||||||
@@ -104,5 +106,5 @@ public class AnnotationControl : Border
|
|||||||
return rect;
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Documents;
|
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
@@ -26,7 +25,8 @@ public class CanvasEditor : Canvas
|
|||||||
private AnnotationControl _curAnn = null!;
|
private AnnotationControl _curAnn = null!;
|
||||||
|
|
||||||
private const int MIN_SIZE = 20;
|
private const int MIN_SIZE = 20;
|
||||||
|
private readonly TimeSpan _viewThreshold = TimeSpan.FromMilliseconds(400);
|
||||||
|
|
||||||
public FormState FormState { get; set; } = null!;
|
public FormState FormState { get; set; } = null!;
|
||||||
public IMediator Mediator { 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);
|
var height = Math.Abs(endPos.Y - _newAnnotationStartPos.Y);
|
||||||
if (width < MIN_SIZE || height < MIN_SIZE)
|
if (width < MIN_SIZE || height < MIN_SIZE)
|
||||||
return;
|
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,
|
Width = width,
|
||||||
Height = height
|
Height = height,
|
||||||
};
|
X = Math.Min(endPos.X, _newAnnotationStartPos.X),
|
||||||
annotationControl.MouseDown += AnnotationPositionStart;
|
Y = Math.Min(endPos.Y, _newAnnotationStartPos.Y)
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
Width = canvasLabel.Width,
|
||||||
Height = info.Height
|
Height = canvasLabel.Height
|
||||||
};
|
};
|
||||||
annotationControl.MouseDown += AnnotationPositionStart;
|
annotationControl.MouseDown += AnnotationPositionStart;
|
||||||
SetLeft(annotationControl, info.X );
|
SetLeft(annotationControl, canvasLabel.X );
|
||||||
SetTop(annotationControl, info.Y);
|
SetTop(annotationControl, canvasLabel.Y);
|
||||||
Children.Add(annotationControl);
|
Children.Add(annotationControl);
|
||||||
CurrentAnns.Add(annotationControl);
|
CurrentAnns.Add(annotationControl);
|
||||||
_newAnnotationRect.Fill = new SolidColorBrush(annClass.Color);
|
_newAnnotationRect.Fill = new SolidColorBrush(annClass.Color);
|
||||||
@@ -323,12 +321,17 @@ public class CanvasEditor : Canvas
|
|||||||
CurrentAnns.Remove(ann);
|
CurrentAnns.Remove(ann);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void RemoveAllAnns() => RemoveAnnotations(CurrentAnns.ToList());
|
public void RemoveAllAnns() => RemoveAnnotations(CurrentAnns);
|
||||||
public void RemoveSelectedAnns() => RemoveAnnotations(CurrentAnns.Where(x => x.IsSelected).ToList());
|
public void RemoveSelectedAnns() => RemoveAnnotations(CurrentAnns.Where(x => x.IsSelected));
|
||||||
|
|
||||||
private void ClearSelections()
|
private void ClearSelections()
|
||||||
{
|
{
|
||||||
foreach (var ann in CurrentAnns)
|
foreach (var ann in CurrentAnns)
|
||||||
ann.IsSelected = false;
|
ann.IsSelected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ClearExpiredAnnotations(TimeSpan time)
|
||||||
|
{
|
||||||
|
RemoveAnnotations(CurrentAnns.Where(x => time - x.Time > _viewThreshold));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<YoloLabel> Labels { get; set; } = new();
|
||||||
|
|
||||||
|
|
||||||
|
public AnnotationResult() { }
|
||||||
|
public AnnotationResult(TimeSpan time, string timeName, List<YoloLabel> labels)
|
||||||
|
{
|
||||||
|
Labels = labels;
|
||||||
|
Time = time;
|
||||||
|
Image = $"{timeName}.jpg";
|
||||||
|
Percentage = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,9 +10,13 @@ namespace Azaion.Annotator.DTO;
|
|||||||
public class Config
|
public class Config
|
||||||
{
|
{
|
||||||
private const string CONFIG_PATH = "config.json";
|
private const string CONFIG_PATH = "config.json";
|
||||||
|
|
||||||
private const string DEFAULT_VIDEO_DIR = "video";
|
private const string DEFAULT_VIDEO_DIR = "video";
|
||||||
|
|
||||||
private const string DEFAULT_LABELS_DIR = "labels";
|
private const string DEFAULT_LABELS_DIR = "labels";
|
||||||
private const string DEFAULT_IMAGES_DIR = "images";
|
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 Size DefaultWindowSize = new(1280, 720);
|
||||||
private static readonly Point DefaultWindowLocation = new(100, 100);
|
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 VideosDirectory { get; set; } = DEFAULT_VIDEO_DIR;
|
||||||
public string LabelsDirectory { get; set; } = DEFAULT_LABELS_DIR;
|
public string LabelsDirectory { get; set; } = DEFAULT_LABELS_DIR;
|
||||||
public string ImagesDirectory { get; set; } = DEFAULT_IMAGES_DIR;
|
public string ImagesDirectory { get; set; } = DEFAULT_IMAGES_DIR;
|
||||||
|
public string ResultsDirectory { get; set; } = DEFAULT_RESULTS_DIR;
|
||||||
|
|
||||||
public List<AnnotationClass> AnnotationClasses { get; set; } = [];
|
public List<AnnotationClass> AnnotationClasses { get; set; } = [];
|
||||||
public Size WindowSize { get; set; }
|
public Size WindowSize { get; set; }
|
||||||
public Point WindowLocation { get; set; }
|
public Point WindowLocation { get; set; }
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.IO;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
||||||
namespace Azaion.Annotator.DTO;
|
namespace Azaion.Annotator.DTO;
|
||||||
@@ -12,6 +13,26 @@ public class FormState
|
|||||||
public string VideoName => Path.GetFileNameWithoutExtension(CurrentFile).Replace(" ", "");
|
public string VideoName => Path.GetFileNameWithoutExtension(CurrentFile).Replace(" ", "");
|
||||||
public TimeSpan CurrentVideoLength { get; set; }
|
public TimeSpan CurrentVideoLength { get; set; }
|
||||||
public int CurrentVolume { get; set; } = 100;
|
public int CurrentVolume { get; set; } = 100;
|
||||||
|
public List<AnnotationResult> AnnotationResults { get; set; } = [];
|
||||||
|
|
||||||
public string GetTimeName(TimeSpan ts) => $"{VideoName}_{ts:hmmssf}";
|
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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(',', '.');
|
||||||
|
|
||||||
|
}
|
||||||
@@ -62,6 +62,7 @@
|
|||||||
<ColumnDefinition Width="250" />
|
<ColumnDefinition Width="250" />
|
||||||
<ColumnDefinition Width="4"/>
|
<ColumnDefinition Width="4"/>
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="200" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<Menu Grid.Row="0"
|
<Menu Grid.Row="0"
|
||||||
@@ -179,10 +180,56 @@
|
|||||||
x:Name="VideoView">
|
x:Name="VideoView">
|
||||||
<controls:CanvasEditor x:Name="Editor" Background="#01000000" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
|
<controls:CanvasEditor x:Name="Editor" Background="#01000000" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
|
||||||
</wpf:VideoView>
|
</wpf:VideoView>
|
||||||
|
|
||||||
|
<DataGrid x:Name="DgAnnotations"
|
||||||
|
Grid.Column="3"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.RowSpan="3"
|
||||||
|
Background="Black"
|
||||||
|
RowBackground="#252525"
|
||||||
|
Foreground="White"
|
||||||
|
RowHeaderWidth="0"
|
||||||
|
Padding="2 0 0 0"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
SelectionMode="Single"
|
||||||
|
CellStyle="{DynamicResource DataGridCellStyle1}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
CanUserResizeRows="False"
|
||||||
|
CanUserResizeColumns="False">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTemplateColumn
|
||||||
|
Width="120"
|
||||||
|
Header="Кадр"
|
||||||
|
CanUserSort="False">
|
||||||
|
<DataGridTemplateColumn.HeaderStyle>
|
||||||
|
<Style TargetType="DataGridColumnHeader">
|
||||||
|
<Setter Property="Background" Value="#252525"></Setter>
|
||||||
|
</Style>
|
||||||
|
</DataGridTemplateColumn.HeaderStyle>
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding Path=ClassNumber}"></TextBlock>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
<DataGridTextColumn
|
||||||
|
Width="*"
|
||||||
|
Header="Клас"
|
||||||
|
Binding="{Binding Path=Name}"
|
||||||
|
CanUserSort="False">
|
||||||
|
<DataGridTextColumn.HeaderStyle>
|
||||||
|
<Style TargetType="DataGridColumnHeader">
|
||||||
|
<Setter Property="Background" Value="#252525"></Setter>
|
||||||
|
</Style>
|
||||||
|
</DataGridTextColumn.HeaderStyle>
|
||||||
|
</DataGridTextColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
|
||||||
<controls:UpdatableProgressBar x:Name="VideoSlider"
|
<controls:UpdatableProgressBar x:Name="VideoSlider"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
Grid.ColumnSpan="3"
|
Grid.ColumnSpan="4"
|
||||||
Background="#252525"
|
Background="#252525"
|
||||||
Foreground="LightBlue">
|
Foreground="LightBlue">
|
||||||
|
|
||||||
@@ -373,6 +420,7 @@
|
|||||||
<StatusBar
|
<StatusBar
|
||||||
Grid.Row="5"
|
Grid.Row="5"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
Background="#252525"
|
Background="#252525"
|
||||||
Foreground="White"
|
Foreground="White"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using Azaion.Annotator.DTO;
|
using Azaion.Annotator.DTO;
|
||||||
using Azaion.Annotator.Extensions;
|
using Azaion.Annotator.Extensions;
|
||||||
using LibVLCSharp.Shared;
|
using LibVLCSharp.Shared;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.WindowsAPICodePack.Dialogs;
|
using Microsoft.WindowsAPICodePack.Dialogs;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Point = System.Windows.Point;
|
using Point = System.Windows.Point;
|
||||||
using Size = System.Windows.Size;
|
using Size = System.Windows.Size;
|
||||||
|
|
||||||
@@ -22,12 +21,12 @@ public partial class MainWindow
|
|||||||
|
|
||||||
private readonly Config _config;
|
private readonly Config _config;
|
||||||
private readonly HelpWindow _helpWindow;
|
private readonly HelpWindow _helpWindow;
|
||||||
private readonly TimeSpan _annotationTime = TimeSpan.FromSeconds(1);
|
|
||||||
|
|
||||||
public ObservableCollection<AnnotationClass> AnnotationClasses { get; set; } = new();
|
public ObservableCollection<AnnotationClass> AnnotationClasses { get; set; } = new();
|
||||||
private bool _suspendLayout;
|
private bool _suspendLayout;
|
||||||
|
|
||||||
public Dictionary<string, List<AnnotationInfo>> Annotations { get; set; } = new();
|
public Dictionary<TimeSpan, List<YoloLabel>> Annotations { get; set; } = new();
|
||||||
|
public Dictionary<TimeSpan, List<AnnotationResult>> AnnotationResults { get; set; } = new();
|
||||||
|
|
||||||
public MainWindow(LibVLC libVLC, MediaPlayer mediaPlayer,
|
public MainWindow(LibVLC libVLC, MediaPlayer mediaPlayer,
|
||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
@@ -115,38 +114,17 @@ public partial class MainWindow
|
|||||||
Dispatcher.Invoke(() => VideoSlider.Value = _mediaPlayer.Position * VideoSlider.Maximum);
|
Dispatcher.Invoke(() => VideoSlider.Value = _mediaPlayer.Position * VideoSlider.Maximum);
|
||||||
Dispatcher.Invoke(() => StatusClock.Text = $"{TimeSpan.FromMilliseconds(_mediaPlayer.Time):mm\\:ss} / {_formState.CurrentVideoLength:mm\\:ss}");
|
Dispatcher.Invoke(() => StatusClock.Text = $"{TimeSpan.FromMilliseconds(_mediaPlayer.Time):mm\\:ss} / {_formState.CurrentVideoLength:mm\\:ss}");
|
||||||
|
|
||||||
var curTime = _formState.GetTimeName(TimeSpan.FromMilliseconds(_mediaPlayer.Time));
|
var time = TimeSpan.FromMilliseconds(_mediaPlayer.Time);
|
||||||
if (!Annotations.TryGetValue(curTime, out var annotationInfos))
|
if (!Annotations.TryGetValue(time, out var annotations))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var annotations = annotationInfos.Select(info =>
|
foreach (var ann in annotations)
|
||||||
{
|
{
|
||||||
var annClass = _config.AnnotationClasses[info.ClassNumber];
|
var annClass = _config.AnnotationClasses[ann.ClassNumber];
|
||||||
var annInfo = info.ToCanvasCoordinates(Editor.RenderSize, _formState.CurrentVideoSize);
|
var annInfo = new CanvasLabel(ann, Editor.RenderSize, _formState.CurrentVideoSize);
|
||||||
var annotation = Dispatcher.Invoke(() => Editor.CreateAnnotation(annClass, annInfo));
|
Dispatcher.Invoke(() => Editor.CreateAnnotation(annClass, time, annInfo));
|
||||||
return annotation;
|
}
|
||||||
}).ToList();
|
Dispatcher.Invoke(() => Editor.ClearExpiredAnnotations(time));
|
||||||
|
|
||||||
//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));
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
VideoSlider.ValueChanged += (value, newValue) =>
|
VideoSlider.ValueChanged += (value, newValue) =>
|
||||||
@@ -173,26 +151,61 @@ public partial class MainWindow
|
|||||||
|
|
||||||
public void LoadExistingAnnotations()
|
public void LoadExistingAnnotations()
|
||||||
{
|
{
|
||||||
var dirInfo = new DirectoryInfo(_config.LabelsDirectory);
|
Annotations = LoadAnnotations();
|
||||||
if (!dirInfo.Exists)
|
_formState.AnnotationResults = LoadAnnotationResults();
|
||||||
return;
|
}
|
||||||
|
|
||||||
var files = dirInfo.GetFiles($"{_formState.VideoName}_*");
|
private Dictionary<TimeSpan, List<YoloLabel>> LoadAnnotations()
|
||||||
Annotations = files.ToDictionary(f => Path.GetFileNameWithoutExtension(f.Name), f =>
|
{
|
||||||
|
var labelDir = new DirectoryInfo(_config.LabelsDirectory);
|
||||||
|
if (!labelDir.Exists)
|
||||||
|
return new Dictionary<TimeSpan, List<YoloLabel>>();
|
||||||
|
|
||||||
|
var labelFiles = labelDir.GetFiles($"{_formState.VideoName}_*");
|
||||||
|
return labelFiles.Select(x =>
|
||||||
{
|
{
|
||||||
var str = File.ReadAllText(f.FullName);
|
var name = Path.GetFileNameWithoutExtension(x.Name);
|
||||||
return str.Split(Environment.NewLine).Select(AnnotationInfo.Parse)
|
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)
|
.Where(x => x != null)
|
||||||
.ToList();
|
.ToList();
|
||||||
})!;
|
})!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<AnnotationResult> LoadAnnotationResults()
|
||||||
|
{
|
||||||
|
var resultDir = new DirectoryInfo(_config!.ResultsDirectory);
|
||||||
|
if (!resultDir.Exists)
|
||||||
|
Directory.CreateDirectory(_config!.ResultsDirectory);
|
||||||
|
|
||||||
|
var resultsFile = resultDir.GetFiles($"{_formState!.CurrentFile}*").FirstOrDefault();
|
||||||
|
|
||||||
|
List<AnnotationResult> 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<List<AnnotationResult>>(File.ReadAllText(File.ReadAllText(resultsFile.FullName)))!;
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void ReloadFiles()
|
private void ReloadFiles()
|
||||||
{
|
{
|
||||||
var dir = new DirectoryInfo(_config.VideosDirectory);
|
var dir = new DirectoryInfo(_config.VideosDirectory);
|
||||||
if (!dir.Exists)
|
if (!dir.Exists)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var files = dir.GetFiles("mp4", "mov").Select(x =>
|
var files = dir.GetFiles("mp4", "mov").Select(x =>
|
||||||
{
|
{
|
||||||
_mediaPlayer.Media = new Media(_libVLC, x.FullName);
|
_mediaPlayer.Media = new Media(_libVLC, x.FullName);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Windows.Input;
|
|||||||
using Azaion.Annotator.DTO;
|
using Azaion.Annotator.DTO;
|
||||||
using LibVLCSharp.Shared;
|
using LibVLCSharp.Shared;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Azaion.Annotator;
|
namespace Azaion.Annotator;
|
||||||
|
|
||||||
@@ -98,56 +99,64 @@ public class PlayerControlHandler(LibVLC libVLC, MediaPlayer mediaPlayer, MainWi
|
|||||||
|
|
||||||
private async Task ControlPlayback(PlaybackControlEnum controlEnum)
|
private async Task ControlPlayback(PlaybackControlEnum controlEnum)
|
||||||
{
|
{
|
||||||
var isCtrlPressed = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
|
try
|
||||||
var step = isCtrlPressed ? LARGE_STEP : STEP;
|
|
||||||
|
|
||||||
switch (controlEnum)
|
|
||||||
{
|
{
|
||||||
case PlaybackControlEnum.Play:
|
var isCtrlPressed = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
|
||||||
Play();
|
var step = isCtrlPressed ? LARGE_STEP : STEP;
|
||||||
break;
|
|
||||||
case PlaybackControlEnum.Pause:
|
switch (controlEnum)
|
||||||
mediaPlayer.Pause();
|
{
|
||||||
if (!mediaPlayer.IsPlaying)
|
case PlaybackControlEnum.Play:
|
||||||
mainWindow.BlinkHelp(HelpTexts.HelpTextsDict[HelpTextEnum.AnnotationHelp]);
|
Play();
|
||||||
break;
|
break;
|
||||||
case PlaybackControlEnum.Stop:
|
case PlaybackControlEnum.Pause:
|
||||||
mediaPlayer.Stop();
|
mediaPlayer.Pause();
|
||||||
break;
|
if (!mediaPlayer.IsPlaying)
|
||||||
case PlaybackControlEnum.PreviousFrame:
|
mainWindow.BlinkHelp(HelpTexts.HelpTextsDict[HelpTextEnum.AnnotationHelp]);
|
||||||
mediaPlayer.SetPause(true);
|
break;
|
||||||
mediaPlayer.Time -= step;
|
case PlaybackControlEnum.Stop:
|
||||||
mainWindow.VideoSlider.Value = mediaPlayer.Position * 100;
|
mediaPlayer.Stop();
|
||||||
break;
|
break;
|
||||||
case PlaybackControlEnum.NextFrame:
|
case PlaybackControlEnum.PreviousFrame:
|
||||||
mediaPlayer.SetPause(true);
|
mediaPlayer.SetPause(true);
|
||||||
mediaPlayer.Time += step;
|
mediaPlayer.Time -= step;
|
||||||
mainWindow.VideoSlider.Value = mediaPlayer.Position * 100;
|
mainWindow.VideoSlider.Value = mediaPlayer.Position * 100;
|
||||||
break;
|
break;
|
||||||
case PlaybackControlEnum.SaveAnnotations:
|
case PlaybackControlEnum.NextFrame:
|
||||||
await SaveAnnotations();
|
mediaPlayer.SetPause(true);
|
||||||
break;
|
mediaPlayer.Time += step;
|
||||||
case PlaybackControlEnum.RemoveSelectedAnns:
|
mainWindow.VideoSlider.Value = mediaPlayer.Position * 100;
|
||||||
mainWindow.Editor.RemoveSelectedAnns();
|
break;
|
||||||
break;
|
case PlaybackControlEnum.SaveAnnotations:
|
||||||
case PlaybackControlEnum.RemoveAllAnns:
|
await SaveAnnotations();
|
||||||
mainWindow.Editor.RemoveAllAnns();
|
break;
|
||||||
break;
|
case PlaybackControlEnum.RemoveSelectedAnns:
|
||||||
case PlaybackControlEnum.TurnOnVolume:
|
mainWindow.Editor.RemoveSelectedAnns();
|
||||||
mainWindow.TurnOnVolumeBtn.Visibility = Visibility.Collapsed;
|
break;
|
||||||
mainWindow.TurnOffVolumeBtn.Visibility = Visibility.Visible;
|
case PlaybackControlEnum.RemoveAllAnns:
|
||||||
mediaPlayer.Volume = formState.CurrentVolume;
|
mainWindow.Editor.RemoveAllAnns();
|
||||||
break;
|
break;
|
||||||
case PlaybackControlEnum.TurnOffVolume:
|
case PlaybackControlEnum.TurnOnVolume:
|
||||||
mainWindow.TurnOffVolumeBtn.Visibility = Visibility.Collapsed;
|
mainWindow.TurnOnVolumeBtn.Visibility = Visibility.Collapsed;
|
||||||
mainWindow.TurnOnVolumeBtn.Visibility = Visibility.Visible;
|
mainWindow.TurnOffVolumeBtn.Visibility = Visibility.Visible;
|
||||||
formState.CurrentVolume = mediaPlayer.Volume;
|
mediaPlayer.Volume = formState.CurrentVolume;
|
||||||
mediaPlayer.Volume = 0;
|
break;
|
||||||
break;
|
case PlaybackControlEnum.TurnOffVolume:
|
||||||
case PlaybackControlEnum.None:
|
mainWindow.TurnOffVolumeBtn.Visibility = Visibility.Collapsed;
|
||||||
break;
|
mainWindow.TurnOnVolumeBtn.Visibility = Visibility.Visible;
|
||||||
default:
|
formState.CurrentVolume = mediaPlayer.Volume;
|
||||||
throw new ArgumentOutOfRangeException(nameof(controlEnum), controlEnum, null);
|
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))
|
if (string.IsNullOrEmpty(formState.CurrentFile))
|
||||||
return;
|
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
|
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();
|
.ToList();
|
||||||
var labels = string.Join(Environment.NewLine, currentAnns.Select(x => x.ToString()));
|
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);
|
Directory.CreateDirectory(config.LabelsDirectory);
|
||||||
if (!Directory.Exists(config.ImagesDirectory))
|
if (!Directory.Exists(config.ImagesDirectory))
|
||||||
Directory.CreateDirectory(config.ImagesDirectory);
|
Directory.CreateDirectory(config.ImagesDirectory);
|
||||||
|
if (!Directory.Exists(config.ResultsDirectory))
|
||||||
|
Directory.CreateDirectory(config.ResultsDirectory);
|
||||||
|
|
||||||
await File.WriteAllTextAsync($"{config.LabelsDirectory}/{fName}.txt", labels);
|
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);
|
var resultHeight = (uint)Math.Round(RESULT_WIDTH / formState.CurrentVideoSize.Width * formState.CurrentVideoSize.Height);
|
||||||
|
|
||||||
mediaPlayer.TakeSnapshot(0, $"{config.ImagesDirectory}/{fName}.jpg", RESULT_WIDTH, resultHeight);
|
mediaPlayer.TakeSnapshot(0, $"{config.ImagesDirectory}/{fName}.jpg", RESULT_WIDTH, resultHeight);
|
||||||
|
|
||||||
mainWindow.Annotations[fName] = currentAnns;
|
mainWindow.Annotations[time] = currentAnns;
|
||||||
mainWindow.Editor.RemoveAllAnns();
|
mainWindow.Editor.RemoveAllAnns();
|
||||||
mediaPlayer.Play();
|
mediaPlayer.Play();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"VideosDirectory": "D:\\dev\\azaion\\ai\\ai-data",
|
"VideosDirectory": "E:\\Azaion3\\Videos",
|
||||||
"LabelsDirectory": "D:\\dev\\azaion\\ai\\ai-data\\Azaion.Annotator\\labels",
|
"LabelsDirectory": "E:\\labels",
|
||||||
"ImagesDirectory": "D:\\dev\\azaion\\ai\\ai-data\\Azaion.Annotator\\images",
|
"ImagesDirectory": "E:\\images",
|
||||||
|
"ResultsDirectory": "E:\\results",
|
||||||
"AnnotationClasses": [
|
"AnnotationClasses": [
|
||||||
{
|
{
|
||||||
"Id": 0,
|
"Id": 0,
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
гармата D20 D30 діацинт мста
|
||||||
|
макет
|
||||||
|
активність живої сили
|
||||||
|
вигорівша трава від пострілів
|
||||||
|
ящики
|
||||||
Reference in New Issue
Block a user