Add annotationResult and put them into json

This commit is contained in:
Oleksandr Bezdieniezhnykh
2024-07-16 14:06:05 +03:00
parent ea8d0e686a
commit 71006a2462
16 changed files with 445 additions and 252 deletions
+4 -4
View File
@@ -5,16 +5,16 @@ namespace Azaion.Annotator.Test;
public class DictTest
{
public Dictionary<string, List<AnnotationInfo>> Annotations = new();
public Dictionary<string, List<YoloLabel>> 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)
];
}
}
+16 -2
View File
@@ -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<MainWindow>();
services.AddSingleton<HelpWindow>();
@@ -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;
}
+6
View File
@@ -15,7 +15,13 @@
<PackageReference Include="LibVLCSharp.WPF" Version="3.8.2" />
<PackageReference Include="MediatR" Version="12.2.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="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="WindowsAPICodePack" Version="7.0.4" />
</ItemGroup>
@@ -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<object, MouseButtonEventArgs> resizeStart)
public AnnotationControl(AnnotationClass annotationClass, TimeSpan time, Action<object, MouseButtonEventArgs> 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);
}
+23 -20
View File
@@ -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));
}
}
-121
View File
@@ -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;
}
}
}
+28
View File
@@ -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;
}
}
+6 -1
View File
@@ -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<AnnotationClass> AnnotationClasses { get; set; } = [];
public Size WindowSize { get; set; }
public Point WindowLocation { get; set; }
+23 -2
View File
@@ -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<AnnotationResult> 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; }
}
}
+153
View File
@@ -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(',', '.');
}
+49 -1
View File
@@ -62,6 +62,7 @@
<ColumnDefinition Width="250" />
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Menu Grid.Row="0"
@@ -179,10 +180,56 @@
x:Name="VideoView">
<controls:CanvasEditor x:Name="Editor" Background="#01000000" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
</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"
Grid.Column="0"
Grid.Row="4"
Grid.ColumnSpan="3"
Grid.ColumnSpan="4"
Background="#252525"
Foreground="LightBlue">
@@ -373,6 +420,7 @@
<StatusBar
Grid.Row="5"
Grid.Column="2"
Grid.ColumnSpan="2"
Background="#252525"
Foreground="White"
>
+55 -42
View File
@@ -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<AnnotationClass> AnnotationClasses { get; set; } = new();
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,
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<TimeSpan, List<YoloLabel>> LoadAnnotations()
{
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);
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<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()
{
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);
+68 -53
View File
@@ -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();
}
+4 -3
View File
@@ -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,
+5
View File
@@ -0,0 +1,5 @@
гармата D20 D30 діацинт мста
макет
активність живої сили
вигорівша трава від пострілів
ящики