mirror of
https://github.com/azaion/annotations.git
synced 2026-04-23 00:46:31 +00:00
add results pane
differentiate videos which already has annotations
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||||
<PackageReference Include="xunit" Version="2.8.0" />
|
<PackageReference Include="xunit" Version="2.8.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
using IntervalTree;
|
||||||
|
|
||||||
|
namespace Azaion.Annotator.Test;
|
||||||
|
|
||||||
|
public class IntervalTreeTest
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(IntervalTreeTestQueryTestData))]
|
||||||
|
public void IntervalTreeTestQuery(int second, string[] expected)
|
||||||
|
{
|
||||||
|
var mainTree = new IntervalTree<TimeSpan, string>
|
||||||
|
{
|
||||||
|
{ TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(3), "res01" },
|
||||||
|
{ TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(4), "res02" },
|
||||||
|
{ TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(7), "res04" },
|
||||||
|
{ TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(8), "res05" },
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = mainTree.Query(TimeSpan.FromSeconds(second)).ToArray();
|
||||||
|
result.Should().Equal(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> IntervalTreeTestQueryTestData()
|
||||||
|
{
|
||||||
|
yield return [1, new[] {"res01"}];
|
||||||
|
yield return [5, new[] {"res04", "res05"}];
|
||||||
|
yield return [9, new string[] {}];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
<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="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="RangeTree" Version="3.0.1" />
|
||||||
<PackageReference Include="Serilog" Version="4.0.0" />
|
<PackageReference Include="Serilog" Version="4.0.0" />
|
||||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||||
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.1" />
|
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.1" />
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ public class CanvasEditor : Canvas
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public void RemoveAnnotations(IEnumerable<AnnotationControl> listToRemove)
|
private void RemoveAnnotations(IEnumerable<AnnotationControl> listToRemove)
|
||||||
{
|
{
|
||||||
foreach (var ann in listToRemove)
|
foreach (var ann in listToRemove)
|
||||||
{
|
{
|
||||||
@@ -321,8 +321,15 @@ public class CanvasEditor : Canvas
|
|||||||
CurrentAnns.Remove(ann);
|
CurrentAnns.Remove(ann);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void RemoveAllAnns() => RemoveAnnotations(CurrentAnns);
|
|
||||||
public void RemoveSelectedAnns() => RemoveAnnotations(CurrentAnns.Where(x => x.IsSelected));
|
public void RemoveAllAnns()
|
||||||
|
{
|
||||||
|
foreach (var ann in CurrentAnns)
|
||||||
|
Children.Remove(ann);
|
||||||
|
CurrentAnns.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveSelectedAnns() => RemoveAnnotations(CurrentAnns.Where(x => x.IsSelected).ToList());
|
||||||
|
|
||||||
private void ClearSelections()
|
private void ClearSelections()
|
||||||
{
|
{
|
||||||
@@ -332,6 +339,7 @@ public class CanvasEditor : Canvas
|
|||||||
|
|
||||||
public void ClearExpiredAnnotations(TimeSpan time)
|
public void ClearExpiredAnnotations(TimeSpan time)
|
||||||
{
|
{
|
||||||
RemoveAnnotations(CurrentAnns.Where(x => time - x.Time > _viewThreshold));
|
var expiredAnns = CurrentAnns.Where(x => Math.Abs((time - x.Time).TotalMilliseconds) > _viewThreshold.TotalMilliseconds).ToList();
|
||||||
|
RemoveAnnotations(expiredAnns);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
using Newtonsoft.Json;
|
using System.Windows.Media;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Azaion.Annotator.DTO;
|
namespace Azaion.Annotator.DTO;
|
||||||
|
|
||||||
public class AnnotationResult
|
public class AnnotationResult
|
||||||
{
|
{
|
||||||
|
private readonly Config _config = null!;
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "f")]
|
[JsonProperty(PropertyName = "f")]
|
||||||
public string Image { get; set; } = null!;
|
public string Image { get; set; } = null!;
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "t")]
|
[JsonProperty(PropertyName = "t")]
|
||||||
public TimeSpan Time { get; set; }
|
public TimeSpan Time { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public string TimeStr => $"{Time:h\\:mm\\:ss}";
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "p")]
|
[JsonProperty(PropertyName = "p")]
|
||||||
public double Percentage { get; set; }
|
public double Percentage { get; set; }
|
||||||
|
|
||||||
@@ -20,10 +20,53 @@ public class AnnotationResult
|
|||||||
public double Lon { get; set; }
|
public double Lon { get; set; }
|
||||||
public List<YoloLabel> Labels { get; set; } = new();
|
public List<YoloLabel> Labels { get; set; } = new();
|
||||||
|
|
||||||
|
#region For Display in the grid
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
//For XAML Form
|
||||||
|
public string TimeStr => $"{Time:h\\:mm\\:ss}";
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
//For XAML Form
|
||||||
|
public string ClassName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Labels.Count == 0)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
var groups = Labels.Select(x => x.ClassNumber).GroupBy(x => x).ToList();
|
||||||
|
return groups.Count > 1
|
||||||
|
? string.Join(",", groups.Select(x => x.Key + 1))
|
||||||
|
: _config.AnnotationClassesDict[groups.FirstOrDefault().Key].Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
//For XAML Form
|
||||||
|
public Color ClassColor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var defaultColor = (Color)ColorConverter.ConvertFromString("#404040");
|
||||||
|
if (Labels.Count == 0)
|
||||||
|
return defaultColor;
|
||||||
|
|
||||||
|
var groups = Labels.Select(x => x.ClassNumber).GroupBy(x => x).ToList();
|
||||||
|
|
||||||
|
return groups.Count > 1
|
||||||
|
? defaultColor
|
||||||
|
: _config.AnnotationClassesDict[groups.FirstOrDefault().Key].Color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
public AnnotationResult() { }
|
public AnnotationResult() { }
|
||||||
public AnnotationResult(TimeSpan time, string timeName, List<YoloLabel> labels)
|
public AnnotationResult(TimeSpan time, string timeName, List<YoloLabel> labels, Config config)
|
||||||
{
|
{
|
||||||
|
_config = config;
|
||||||
Labels = labels;
|
Labels = labels;
|
||||||
Time = time;
|
Time = time;
|
||||||
Image = $"{timeName}.jpg";
|
Image = $"{timeName}.jpg";
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ public class Config
|
|||||||
public string ResultsDirectory { get; set; } = DEFAULT_RESULTS_DIR;
|
public string ResultsDirectory { get; set; } = DEFAULT_RESULTS_DIR;
|
||||||
|
|
||||||
public List<AnnotationClass> AnnotationClasses { get; set; } = [];
|
public List<AnnotationClass> AnnotationClasses { get; set; } = [];
|
||||||
|
|
||||||
|
private Dictionary<int, AnnotationClass>? _annotationClassesDict;
|
||||||
|
public Dictionary<int, AnnotationClass> AnnotationClassesDict => _annotationClassesDict ??= AnnotationClasses.ToDictionary(x => x.Id);
|
||||||
|
|
||||||
public Size WindowSize { get; set; }
|
public Size WindowSize { get; set; }
|
||||||
public Point WindowLocation { get; set; }
|
public Point WindowLocation { get; set; }
|
||||||
public bool ShowHelpOnStart { get; set; }
|
public bool ShowHelpOnStart { get; set; }
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Globalization;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ 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 ObservableCollection<AnnotationResult> AnnotationResults { get; set; } = [];
|
||||||
|
|
||||||
public string GetTimeName(TimeSpan ts) => $"{VideoName}_{ts:hmmssf}";
|
public string GetTimeName(TimeSpan ts) => $"{VideoName}_{ts:hmmssf}";
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ public class CanvasLabel : Label
|
|||||||
|
|
||||||
ClassNumber = label.ClassNumber;
|
ClassNumber = label.ClassNumber;
|
||||||
|
|
||||||
var left = X - Width / 2;
|
var left = label.CenterX - label.Width / 2;
|
||||||
var top = Y - Height / 2;
|
var top = label.CenterY - label.Height / 2;
|
||||||
|
|
||||||
if (videoAr > canvasAr) //100% width
|
if (videoAr > canvasAr) //100% width
|
||||||
{
|
{
|
||||||
@@ -89,7 +89,6 @@ public class YoloLabel : Label
|
|||||||
|
|
||||||
public YoloLabel(CanvasLabel canvasLabel, Size canvasSize, Size videoSize)
|
public YoloLabel(CanvasLabel canvasLabel, Size canvasSize, Size videoSize)
|
||||||
{
|
{
|
||||||
|
|
||||||
var cw = canvasSize.Width;
|
var cw = canvasSize.Width;
|
||||||
var ch = canvasSize.Height;
|
var ch = canvasSize.Height;
|
||||||
var canvasAr = cw / ch;
|
var canvasAr = cw / ch;
|
||||||
@@ -117,8 +116,8 @@ public class YoloLabel : Label
|
|||||||
Width = canvasLabel.Width / realWidth;
|
Width = canvasLabel.Width / realWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
CenterX = left + canvasLabel.Width / 2.0;
|
CenterX = left + Width / 2.0;
|
||||||
CenterY = top + canvasLabel.Height / 2.0;
|
CenterY = top + Height / 2.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static YoloLabel? Parse(string s)
|
public static YoloLabel? Parse(string s)
|
||||||
|
|||||||
@@ -207,7 +207,7 @@
|
|||||||
CanUserResizeColumns="False">
|
CanUserResizeColumns="False">
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<DataGridTextColumn
|
<DataGridTextColumn
|
||||||
Width="120"
|
Width="60"
|
||||||
Header="Кадр"
|
Header="Кадр"
|
||||||
CanUserSort="False"
|
CanUserSort="False"
|
||||||
Binding="{Binding Path=TimeStr}">
|
Binding="{Binding Path=TimeStr}">
|
||||||
@@ -220,15 +220,29 @@
|
|||||||
<DataGridTextColumn
|
<DataGridTextColumn
|
||||||
Width="*"
|
Width="*"
|
||||||
Header="Клас"
|
Header="Клас"
|
||||||
Binding="{Binding Path=Name}"
|
Binding="{Binding Path=ClassName}"
|
||||||
CanUserSort="False">
|
CanUserSort="False">
|
||||||
<DataGridTextColumn.HeaderStyle>
|
<DataGridTextColumn.HeaderStyle>
|
||||||
<Style TargetType="DataGridColumnHeader">
|
<Style TargetType="DataGridColumnHeader">
|
||||||
<Setter Property="Background" Value="#252525"></Setter>
|
<Setter Property="Background" Value="#252525"></Setter>
|
||||||
</Style>
|
</Style>
|
||||||
</DataGridTextColumn.HeaderStyle>
|
</DataGridTextColumn.HeaderStyle>
|
||||||
|
<DataGridTextColumn.CellStyle>
|
||||||
|
<Style TargetType="DataGridCell">
|
||||||
|
<Setter Property="Background">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{Binding Path=ClassColor}"></SolidColorBrush>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</DataGridTextColumn.CellStyle>
|
||||||
</DataGridTextColumn>
|
</DataGridTextColumn>
|
||||||
</DataGrid.Columns>
|
</DataGrid.Columns>
|
||||||
|
<DataGrid.ItemContainerStyle>
|
||||||
|
<Style TargetType="DataGridRow">
|
||||||
|
<EventSetter Event="MouseDoubleClick" Handler="DgAnnotationsRowClick"></EventSetter>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.ItemContainerStyle>
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
|
|
||||||
<controls:UpdatableProgressBar x:Name="VideoSlider"
|
<controls:UpdatableProgressBar x:Name="VideoSlider"
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
using Azaion.Annotator.DTO;
|
using Azaion.Annotator.DTO;
|
||||||
using Azaion.Annotator.Extensions;
|
using Azaion.Annotator.Extensions;
|
||||||
using LibVLCSharp.Shared;
|
using LibVLCSharp.Shared;
|
||||||
@@ -9,6 +11,7 @@ using Microsoft.WindowsAPICodePack.Dialogs;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Point = System.Windows.Point;
|
using Point = System.Windows.Point;
|
||||||
using Size = System.Windows.Size;
|
using Size = System.Windows.Size;
|
||||||
|
using IntervalTree;
|
||||||
|
|
||||||
namespace Azaion.Annotator;
|
namespace Azaion.Annotator;
|
||||||
|
|
||||||
@@ -25,8 +28,11 @@ public partial class MainWindow
|
|||||||
public ObservableCollection<AnnotationClass> AnnotationClasses { get; set; } = new();
|
public ObservableCollection<AnnotationClass> AnnotationClasses { get; set; } = new();
|
||||||
private bool _suspendLayout;
|
private bool _suspendLayout;
|
||||||
|
|
||||||
public Dictionary<TimeSpan, List<YoloLabel>> Annotations { get; set; } = new();
|
private readonly TimeSpan _thresholdBefore = TimeSpan.FromMilliseconds(100);
|
||||||
public Dictionary<TimeSpan, List<AnnotationResult>> AnnotationResults { get; set; } = new();
|
private readonly TimeSpan _thresholdAfter = TimeSpan.FromMilliseconds(300);
|
||||||
|
|
||||||
|
public Dictionary<TimeSpan, List<YoloLabel>> AnnotationsDict { get; set; } = new();
|
||||||
|
private IntervalTree<TimeSpan, List<YoloLabel>> Annotations { get; set; } = new();
|
||||||
|
|
||||||
public MainWindow(LibVLC libVLC, MediaPlayer mediaPlayer,
|
public MainWindow(LibVLC libVLC, MediaPlayer mediaPlayer,
|
||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
@@ -111,20 +117,7 @@ public partial class MainWindow
|
|||||||
|
|
||||||
_mediaPlayer.PositionChanged += (o, args) =>
|
_mediaPlayer.PositionChanged += (o, args) =>
|
||||||
{
|
{
|
||||||
Dispatcher.Invoke(() => VideoSlider.Value = _mediaPlayer.Position * VideoSlider.Maximum);
|
ShowTimeAnnotations(TimeSpan.FromMilliseconds(_mediaPlayer.Time));
|
||||||
Dispatcher.Invoke(() => StatusClock.Text = $"{TimeSpan.FromMilliseconds(_mediaPlayer.Time):mm\\:ss} / {_formState.CurrentVideoLength:mm\\:ss}");
|
|
||||||
|
|
||||||
var time = TimeSpan.FromMilliseconds(_mediaPlayer.Time);
|
|
||||||
if (!Annotations.TryGetValue(time, out var annotations))
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var ann in 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) =>
|
VideoSlider.ValueChanged += (value, newValue) =>
|
||||||
@@ -147,59 +140,58 @@ public partial class MainWindow
|
|||||||
|
|
||||||
Editor.FormState = _formState;
|
Editor.FormState = _formState;
|
||||||
Editor.Mediator = _mediator;
|
Editor.Mediator = _mediator;
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadExistingAnnotations()
|
|
||||||
{
|
|
||||||
Annotations = LoadAnnotations();
|
|
||||||
_formState.AnnotationResults = LoadAnnotationResults();
|
|
||||||
DgAnnotations.ItemsSource = _formState.AnnotationResults;
|
DgAnnotations.ItemsSource = _formState.AnnotationResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<TimeSpan, List<YoloLabel>> LoadAnnotations()
|
private void ShowTimeAnnotations(TimeSpan time)
|
||||||
{
|
{
|
||||||
|
Dispatcher.Invoke(() => VideoSlider.Value = _mediaPlayer.Position * VideoSlider.Maximum);
|
||||||
|
Dispatcher.Invoke(() => StatusClock.Text = $"{TimeSpan.FromMilliseconds(_mediaPlayer.Time):mm\\:ss} / {_formState.CurrentVideoLength:mm\\:ss}");
|
||||||
|
|
||||||
|
Dispatcher.Invoke(() => Editor.ClearExpiredAnnotations(time));
|
||||||
|
|
||||||
|
var annotations = Annotations.Query(time).SelectMany(x => x).ToList();
|
||||||
|
foreach (var ann in annotations)
|
||||||
|
{
|
||||||
|
var annClass = _config.AnnotationClasses[ann.ClassNumber];
|
||||||
|
var annInfo = new CanvasLabel(ann, Editor.RenderSize, _formState.CurrentVideoSize);
|
||||||
|
Dispatcher.Invoke(() => Editor.CreateAnnotation(annClass, time, annInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadAnnotations()
|
||||||
|
{
|
||||||
|
_formState.AnnotationResults.Clear();
|
||||||
|
AnnotationsDict.Clear();
|
||||||
|
Annotations.Clear();
|
||||||
|
|
||||||
var labelDir = new DirectoryInfo(_config.LabelsDirectory);
|
var labelDir = new DirectoryInfo(_config.LabelsDirectory);
|
||||||
if (!labelDir.Exists)
|
if (!labelDir.Exists)
|
||||||
return new Dictionary<TimeSpan, List<YoloLabel>>();
|
return;
|
||||||
|
|
||||||
var labelFiles = labelDir.GetFiles($"{_formState.VideoName}_*");
|
var labelFiles = labelDir.GetFiles($"{_formState.VideoName}_*");
|
||||||
return labelFiles.Select(x =>
|
foreach (var file in labelFiles)
|
||||||
{
|
{
|
||||||
var name = Path.GetFileNameWithoutExtension(x.Name);
|
var name = Path.GetFileNameWithoutExtension(file.Name);
|
||||||
return new
|
var time = _formState.GetTime(name)!.Value;
|
||||||
{
|
|
||||||
Name = x.FullName,
|
var str = File.ReadAllText(file.FullName);
|
||||||
Time = _formState.GetTime(name)
|
var annotations = str.Split(Environment.NewLine).Select(YoloLabel.Parse)
|
||||||
};
|
.Where(ann => ann != null)
|
||||||
}).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();
|
.ToList();
|
||||||
})!;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<AnnotationResult> LoadAnnotationResults()
|
AddAnnotation(time, annotations!);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task AddAnnotation(TimeSpan time, List<YoloLabel> annotations)
|
||||||
|
{
|
||||||
|
var fName = _formState.GetTimeName(time);
|
||||||
|
AnnotationsDict.Add(time, annotations!);
|
||||||
|
Annotations.Add(time.Subtract(_thresholdBefore), time.Add(_thresholdAfter), annotations);
|
||||||
|
_formState.AnnotationResults.Add(new AnnotationResult(time, fName, annotations, _config));
|
||||||
|
await File.WriteAllTextAsync($"{_config.ResultsDirectory}/{fName}.json", JsonConvert.SerializeObject(_formState.AnnotationResults));
|
||||||
|
}
|
||||||
|
|
||||||
private void ReloadFiles()
|
private void ReloadFiles()
|
||||||
{
|
{
|
||||||
@@ -288,4 +280,17 @@ public partial class MainWindow
|
|||||||
_helpWindow.Show();
|
_helpWindow.Show();
|
||||||
_helpWindow.Activate();
|
_helpWindow.Activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DgAnnotationsRowClick(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
DgAnnotations.MouseDoubleClick += (sender, args) =>
|
||||||
|
{
|
||||||
|
Editor.RemoveAllAnns();
|
||||||
|
var dgRow = ItemsControl.ContainerFromElement((DataGrid)sender, (args.OriginalSource as DependencyObject)!) as DataGridRow;
|
||||||
|
var res = (AnnotationResult)dgRow!.Item;
|
||||||
|
_mediaPlayer.SetPause(true);
|
||||||
|
_mediaPlayer.Time = (long)res.Time.TotalMilliseconds;// + 250;
|
||||||
|
ShowTimeAnnotations(res.Time);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ 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;
|
||||||
|
|
||||||
@@ -179,7 +178,7 @@ public class PlayerControlHandler(LibVLC libVLC, MediaPlayer mediaPlayer, MainWi
|
|||||||
var fileInfo = (VideoFileInfo)mainWindow.LvFiles.SelectedItem;
|
var fileInfo = (VideoFileInfo)mainWindow.LvFiles.SelectedItem;
|
||||||
|
|
||||||
formState.CurrentFile = fileInfo.Name;
|
formState.CurrentFile = fileInfo.Name;
|
||||||
mainWindow.LoadExistingAnnotations();
|
mainWindow.ReloadAnnotations();
|
||||||
|
|
||||||
mediaPlayer.Stop();
|
mediaPlayer.Stop();
|
||||||
mediaPlayer.Play(new Media(libVLC, fileInfo.Path));
|
mediaPlayer.Play(new Media(libVLC, fileInfo.Path));
|
||||||
@@ -206,15 +205,10 @@ public class PlayerControlHandler(LibVLC libVLC, MediaPlayer mediaPlayer, MainWi
|
|||||||
Directory.CreateDirectory(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[time] = currentAnns;
|
await mainWindow.AddAnnotation(time, currentAnns);
|
||||||
mainWindow.Editor.RemoveAllAnns();
|
mainWindow.Editor.RemoveAllAnns();
|
||||||
mediaPlayer.Play();
|
mediaPlayer.Play();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user