mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 22:56:29 +00:00
add editor, fix some bugs
WIP
This commit is contained in:
@@ -37,7 +37,7 @@
|
|||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Resource>
|
</Resource>
|
||||||
<None Update="config.json">
|
<None Update="config.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<DataGrid x:Class="Azaion.Annotator.Controls.AnnotationClasses"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:Azaion.Annotator.Controls"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="300" d:DesignWidth="300"
|
||||||
|
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="50"
|
||||||
|
Header="Клавіша"
|
||||||
|
CanUserSort="False">
|
||||||
|
<DataGridTemplateColumn.HeaderStyle>
|
||||||
|
<Style TargetType="DataGridColumnHeader">
|
||||||
|
<Setter Property="Background" Value="#252525"></Setter>
|
||||||
|
</Style>
|
||||||
|
</DataGridTemplateColumn.HeaderStyle>
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Border Background="{Binding Path=ColorBrush}">
|
||||||
|
<TextBlock Text="{Binding Path=ClassNumber}"></TextBlock>
|
||||||
|
</Border>
|
||||||
|
</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>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace Azaion.Annotator.Controls;
|
||||||
|
|
||||||
|
public partial class AnnotationClasses : DataGrid
|
||||||
|
{
|
||||||
|
public AnnotationClasses()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ namespace Azaion.Annotator.Controls;
|
|||||||
public class CanvasEditor : Canvas
|
public class CanvasEditor : Canvas
|
||||||
{
|
{
|
||||||
private Point _lastPos;
|
private Point _lastPos;
|
||||||
|
public SelectionState SelectionState { get; set; } = SelectionState.None;
|
||||||
|
|
||||||
private readonly Rectangle _newAnnotationRect;
|
private readonly Rectangle _newAnnotationRect;
|
||||||
private Point _newAnnotationStartPos;
|
private Point _newAnnotationStartPos;
|
||||||
@@ -30,6 +31,19 @@ public class CanvasEditor : Canvas
|
|||||||
public FormState FormState { get; set; } = null!;
|
public FormState FormState { get; set; } = null!;
|
||||||
public IMediator Mediator { get; set; } = null!;
|
public IMediator Mediator { get; set; } = null!;
|
||||||
|
|
||||||
|
public static readonly DependencyProperty GetTimeFuncProp =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(GetTimeFunc),
|
||||||
|
typeof(Func<TimeSpan>),
|
||||||
|
typeof(CanvasEditor),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
public Func<TimeSpan> GetTimeFunc
|
||||||
|
{
|
||||||
|
get => (Func<TimeSpan>)GetValue(GetTimeFuncProp);
|
||||||
|
set => SetValue(GetTimeFuncProp, value);
|
||||||
|
}
|
||||||
|
|
||||||
private AnnotationClass _currentAnnClass = null!;
|
private AnnotationClass _currentAnnClass = null!;
|
||||||
public AnnotationClass CurrentAnnClass
|
public AnnotationClass CurrentAnnClass
|
||||||
{
|
{
|
||||||
@@ -84,11 +98,7 @@ public class CanvasEditor : Canvas
|
|||||||
Stroke = new SolidColorBrush(Colors.Gray),
|
Stroke = new SolidColorBrush(Colors.Gray),
|
||||||
Fill = new SolidColorBrush(Color.FromArgb(128, 128, 128, 128)),
|
Fill = new SolidColorBrush(Color.FromArgb(128, 128, 128, 128)),
|
||||||
};
|
};
|
||||||
Loaded += Init;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Init(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
KeyDown += (_, args) =>
|
KeyDown += (_, args) =>
|
||||||
{
|
{
|
||||||
Console.WriteLine($"pressed {args.Key}");
|
Console.WriteLine($"pressed {args.Key}");
|
||||||
@@ -98,15 +108,21 @@ public class CanvasEditor : Canvas
|
|||||||
MouseUp += CanvasMouseUp;
|
MouseUp += CanvasMouseUp;
|
||||||
SizeChanged += CanvasResized;
|
SizeChanged += CanvasResized;
|
||||||
Cursor = Cursors.Cross;
|
Cursor = Cursors.Cross;
|
||||||
_horizontalLine.X1 = 0;
|
|
||||||
_horizontalLine.X2 = ActualWidth;
|
|
||||||
_verticalLine.Y1 = 0;
|
|
||||||
_verticalLine.Y2 = ActualHeight;
|
|
||||||
|
|
||||||
Children.Add(_newAnnotationRect);
|
Children.Add(_newAnnotationRect);
|
||||||
Children.Add(_horizontalLine);
|
Children.Add(_horizontalLine);
|
||||||
Children.Add(_verticalLine);
|
Children.Add(_verticalLine);
|
||||||
Children.Add(_classNameHint);
|
Children.Add(_classNameHint);
|
||||||
|
|
||||||
|
Loaded += Init;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Init(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_horizontalLine.X1 = 0;
|
||||||
|
_horizontalLine.X2 = ActualWidth;
|
||||||
|
_verticalLine.Y1 = 0;
|
||||||
|
_verticalLine.Y2 = ActualHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CanvasMouseDown(object sender, MouseButtonEventArgs e)
|
private void CanvasMouseDown(object sender, MouseButtonEventArgs e)
|
||||||
@@ -125,22 +141,22 @@ public class CanvasEditor : Canvas
|
|||||||
|
|
||||||
if (e.LeftButton != MouseButtonState.Pressed)
|
if (e.LeftButton != MouseButtonState.Pressed)
|
||||||
return;
|
return;
|
||||||
if (FormState.SelectionState == SelectionState.NewAnnCreating)
|
if (SelectionState == SelectionState.NewAnnCreating)
|
||||||
NewAnnotationCreatingMove(sender, e);
|
NewAnnotationCreatingMove(sender, e);
|
||||||
|
|
||||||
if (FormState.SelectionState == SelectionState.AnnResizing)
|
if (SelectionState == SelectionState.AnnResizing)
|
||||||
AnnotationResizeMove(sender, e);
|
AnnotationResizeMove(sender, e);
|
||||||
|
|
||||||
if (FormState.SelectionState == SelectionState.AnnMoving)
|
if (SelectionState == SelectionState.AnnMoving)
|
||||||
AnnotationPositionMove(sender, e);
|
AnnotationPositionMove(sender, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CanvasMouseUp(object sender, MouseButtonEventArgs e)
|
private void CanvasMouseUp(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
if (FormState.SelectionState == SelectionState.NewAnnCreating)
|
if (SelectionState == SelectionState.NewAnnCreating)
|
||||||
CreateAnnotation(e.GetPosition(this));
|
CreateAnnotation(e.GetPosition(this));
|
||||||
|
|
||||||
FormState.SelectionState = SelectionState.None;
|
SelectionState = SelectionState.None;
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +170,7 @@ public class CanvasEditor : Canvas
|
|||||||
|
|
||||||
private void AnnotationResizeStart(object sender, MouseEventArgs e)
|
private void AnnotationResizeStart(object sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
FormState.SelectionState = SelectionState.AnnResizing;
|
SelectionState = SelectionState.AnnResizing;
|
||||||
_lastPos = e.GetPosition(this);
|
_lastPos = e.GetPosition(this);
|
||||||
_curRec = (Rectangle)sender;
|
_curRec = (Rectangle)sender;
|
||||||
_curAnn = (AnnotationControl)((Grid)_curRec.Parent).Parent;
|
_curAnn = (AnnotationControl)((Grid)_curRec.Parent).Parent;
|
||||||
@@ -163,7 +179,7 @@ public class CanvasEditor : Canvas
|
|||||||
|
|
||||||
private void AnnotationResizeMove(object sender, MouseEventArgs e)
|
private void AnnotationResizeMove(object sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
if (FormState.SelectionState != SelectionState.AnnResizing)
|
if (SelectionState != SelectionState.AnnResizing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var currentPos = e.GetPosition(this);
|
var currentPos = e.GetPosition(this);
|
||||||
@@ -224,13 +240,13 @@ public class CanvasEditor : Canvas
|
|||||||
|
|
||||||
_curAnn.IsSelected = true;
|
_curAnn.IsSelected = true;
|
||||||
|
|
||||||
FormState.SelectionState = SelectionState.AnnMoving;
|
SelectionState = SelectionState.AnnMoving;
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AnnotationPositionMove(object sender, MouseEventArgs e)
|
private void AnnotationPositionMove(object sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
if (FormState.SelectionState != SelectionState.AnnMoving)
|
if (SelectionState != SelectionState.AnnMoving)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var currentPos = e.GetPosition(this);
|
var currentPos = e.GetPosition(this);
|
||||||
@@ -255,12 +271,12 @@ public class CanvasEditor : Canvas
|
|||||||
SetTop(_newAnnotationRect, _newAnnotationStartPos.Y);
|
SetTop(_newAnnotationRect, _newAnnotationStartPos.Y);
|
||||||
_newAnnotationRect.MouseMove += NewAnnotationCreatingMove;
|
_newAnnotationRect.MouseMove += NewAnnotationCreatingMove;
|
||||||
|
|
||||||
FormState.SelectionState = SelectionState.NewAnnCreating;
|
SelectionState = SelectionState.NewAnnCreating;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NewAnnotationCreatingMove(object sender, MouseEventArgs e)
|
private void NewAnnotationCreatingMove(object sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
if (FormState.SelectionState != SelectionState.NewAnnCreating)
|
if (SelectionState != SelectionState.NewAnnCreating)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var currentPos = e.GetPosition(this);
|
var currentPos = e.GetPosition(this);
|
||||||
@@ -284,8 +300,7 @@ public class CanvasEditor : Canvas
|
|||||||
if (width < MIN_SIZE || height < MIN_SIZE)
|
if (width < MIN_SIZE || height < MIN_SIZE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var mainWindow = (MainWindow)((Window)((Grid)Parent).Parent).Owner;
|
var time = GetTimeFunc();
|
||||||
var time = TimeSpan.FromMilliseconds(mainWindow.VideoView.MediaPlayer.Time);
|
|
||||||
CreateAnnotation(CurrentAnnClass, time, new CanvasLabel
|
CreateAnnotation(CurrentAnnClass, time, new CanvasLabel
|
||||||
{
|
{
|
||||||
Width = width,
|
Width = width,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Windows.Media;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Size = System.Windows.Size;
|
using Size = System.Windows.Size;
|
||||||
@@ -11,21 +10,22 @@ namespace Azaion.Annotator.DTO;
|
|||||||
public class Config
|
public class Config
|
||||||
{
|
{
|
||||||
public const string ThumbnailPrefix = "_thumb";
|
public const string ThumbnailPrefix = "_thumb";
|
||||||
|
public const string ThumbnailsCacheFile = "thumbnails.cache";
|
||||||
|
|
||||||
public string VideosDirectory { get; set; }
|
public string VideosDirectory { get; set; }
|
||||||
public string LabelsDirectory { get; set; }
|
public string LabelsDirectory { get; set; }
|
||||||
public string ImagesDirectory { get; set; }
|
public string ImagesDirectory { get; set; }
|
||||||
public string ResultsDirectory { get; set; }
|
public string ResultsDirectory { get; set; }
|
||||||
public string ThumbnailsDirectory { get; set; }
|
public string ThumbnailsDirectory { get; set; }
|
||||||
|
public string UnknownImages { get; set; }
|
||||||
|
|
||||||
public List<AnnotationClass> AnnotationClasses { get; set; } = [];
|
public List<AnnotationClass> AnnotationClasses { get; set; } = [];
|
||||||
|
|
||||||
private Dictionary<int, AnnotationClass>? _annotationClassesDict;
|
private Dictionary<int, AnnotationClass>? _annotationClassesDict;
|
||||||
public Dictionary<int, AnnotationClass> AnnotationClassesDict => _annotationClassesDict ??= AnnotationClasses.ToDictionary(x => x.Id);
|
public Dictionary<int, AnnotationClass> AnnotationClassesDict => _annotationClassesDict ??= AnnotationClasses.ToDictionary(x => x.Id);
|
||||||
|
|
||||||
public Size WindowSize { get; set; }
|
public WindowConfig MainWindowConfig { get; set; }
|
||||||
public Point WindowLocation { get; set; }
|
public WindowConfig DatasetExplorerConfig { get; set; }
|
||||||
public bool FullScreen { get; set; }
|
|
||||||
|
|
||||||
public double LeftPanelWidth { get; set; }
|
public double LeftPanelWidth { get; set; }
|
||||||
public double RightPanelWidth { get; set; }
|
public double RightPanelWidth { get; set; }
|
||||||
@@ -38,6 +38,13 @@ public class Config
|
|||||||
public ThumbnailConfig ThumbnailConfig { get; set; }
|
public ThumbnailConfig ThumbnailConfig { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class WindowConfig
|
||||||
|
{
|
||||||
|
public Size WindowSize { get; set; }
|
||||||
|
public Point WindowLocation { get; set; }
|
||||||
|
public bool FullScreen { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class ThumbnailConfig
|
public class ThumbnailConfig
|
||||||
{
|
{
|
||||||
public Size Size { get; set; }
|
public Size Size { get; set; }
|
||||||
@@ -60,6 +67,7 @@ public class FileConfigRepository(ILogger<FileConfigRepository> logger) : IConfi
|
|||||||
private const string DEFAULT_IMAGES_DIR = "images";
|
private const string DEFAULT_IMAGES_DIR = "images";
|
||||||
private const string DEFAULT_RESULTS_DIR = "results";
|
private const string DEFAULT_RESULTS_DIR = "results";
|
||||||
private const string DEFAULT_THUMBNAILS_DIR = "thumbnails";
|
private const string DEFAULT_THUMBNAILS_DIR = "thumbnails";
|
||||||
|
private const string DEFAULT_UNKNOWN_IMG_DIR = "unknown";
|
||||||
|
|
||||||
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);
|
||||||
@@ -82,9 +90,18 @@ public class FileConfigRepository(ILogger<FileConfigRepository> logger) : IConfi
|
|||||||
ImagesDirectory = Path.Combine(exePath, DEFAULT_IMAGES_DIR),
|
ImagesDirectory = Path.Combine(exePath, DEFAULT_IMAGES_DIR),
|
||||||
ResultsDirectory = Path.Combine(exePath, DEFAULT_RESULTS_DIR),
|
ResultsDirectory = Path.Combine(exePath, DEFAULT_RESULTS_DIR),
|
||||||
ThumbnailsDirectory = Path.Combine(exePath, DEFAULT_THUMBNAILS_DIR),
|
ThumbnailsDirectory = Path.Combine(exePath, DEFAULT_THUMBNAILS_DIR),
|
||||||
|
UnknownImages = Path.Combine(exePath, DEFAULT_UNKNOWN_IMG_DIR),
|
||||||
|
|
||||||
WindowLocation = DefaultWindowLocation,
|
MainWindowConfig = new WindowConfig
|
||||||
|
{
|
||||||
WindowSize = DefaultWindowSize,
|
WindowSize = DefaultWindowSize,
|
||||||
|
WindowLocation = DefaultWindowLocation
|
||||||
|
},
|
||||||
|
DatasetExplorerConfig = new WindowConfig
|
||||||
|
{
|
||||||
|
WindowSize = DefaultWindowSize,
|
||||||
|
WindowLocation = DefaultWindowLocation
|
||||||
|
},
|
||||||
ShowHelpOnStart = true,
|
ShowHelpOnStart = true,
|
||||||
|
|
||||||
VideoFormats = DefaultVideoFormats,
|
VideoFormats = DefaultVideoFormats,
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ namespace Azaion.Annotator.DTO;
|
|||||||
|
|
||||||
public class FormState
|
public class FormState
|
||||||
{
|
{
|
||||||
public SelectionState SelectionState { get; set; } = SelectionState.None;
|
|
||||||
|
|
||||||
public MediaFileInfo? CurrentMedia { get; set; }
|
public MediaFileInfo? CurrentMedia { get; set; }
|
||||||
public string VideoName => string.IsNullOrEmpty(CurrentMedia?.Name)
|
public string VideoName => string.IsNullOrEmpty(CurrentMedia?.Name)
|
||||||
? ""
|
? ""
|
||||||
|
|||||||
@@ -7,11 +7,16 @@ namespace Azaion.Annotator.DTO;
|
|||||||
|
|
||||||
public abstract class Label
|
public abstract class Label
|
||||||
{
|
{
|
||||||
[JsonProperty(PropertyName = "cl")]
|
[JsonProperty(PropertyName = "cl")] public int ClassNumber { get; set; }
|
||||||
public int ClassNumber { get; set; }
|
|
||||||
|
|
||||||
public Label(){}
|
public Label()
|
||||||
public Label(int classNumber) { ClassNumber = classNumber; }
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Label(int classNumber)
|
||||||
|
{
|
||||||
|
ClassNumber = classNumber;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CanvasLabel : Label
|
public class CanvasLabel : Label
|
||||||
@@ -21,7 +26,10 @@ public class CanvasLabel : Label
|
|||||||
public double Width { get; set; }
|
public double Width { get; set; }
|
||||||
public double Height { get; set; }
|
public double Height { get; set; }
|
||||||
|
|
||||||
public CanvasLabel() { }
|
public CanvasLabel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public CanvasLabel(int classNumber, double x, double y, double width, double height) : base(classNumber)
|
public CanvasLabel(int classNumber, double x, double y, double width, double height) : base(classNumber)
|
||||||
{
|
{
|
||||||
X = x;
|
X = x;
|
||||||
@@ -67,19 +75,18 @@ public class CanvasLabel : Label
|
|||||||
|
|
||||||
public class YoloLabel : Label
|
public class YoloLabel : Label
|
||||||
{
|
{
|
||||||
[JsonProperty(PropertyName = "x")]
|
[JsonProperty(PropertyName = "x")] public double CenterX { get; set; }
|
||||||
public double CenterX { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "y")]
|
[JsonProperty(PropertyName = "y")] public double CenterY { get; set; }
|
||||||
public double CenterY { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "w")]
|
[JsonProperty(PropertyName = "w")] public double Width { get; set; }
|
||||||
public double Width { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty(PropertyName = "h")]
|
[JsonProperty(PropertyName = "h")] public double Height { get; set; }
|
||||||
public double Height { get; set; }
|
|
||||||
|
public YoloLabel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public YoloLabel() { }
|
|
||||||
public YoloLabel(int classNumber, double centerX, double centerY, double width, double height) : base(classNumber)
|
public YoloLabel(int classNumber, double centerX, double centerY, double width, double height) : base(classNumber)
|
||||||
{
|
{
|
||||||
CenterX = centerX;
|
CenterX = centerX;
|
||||||
@@ -158,6 +165,11 @@ public class YoloLabel : Label
|
|||||||
.ToList()!;
|
.ToList()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"{ClassNumber} {CenterX:F5} {CenterY:F5} {Width:F5} {Height:F5}".Replace(',', '.');
|
public static async Task WriteToFile(IEnumerable<YoloLabel> labels, string filename)
|
||||||
|
{
|
||||||
|
var labelsStr = string.Join(Environment.NewLine, labels.Select(x => x.ToString()));
|
||||||
|
await File.WriteAllTextAsync(filename, labelsStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"{ClassNumber} {CenterX:F5} {CenterY:F5} {Width:F5} {Height:F5}".Replace(',', '.');
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
xmlns:vwp="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
|
xmlns:vwp="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
|
||||||
xmlns:local="clr-namespace:Azaion.Annotator"
|
xmlns:local="clr-namespace:Azaion.Annotator"
|
||||||
xmlns:dto="clr-namespace:Azaion.Annotator.DTO"
|
xmlns:dto="clr-namespace:Azaion.Annotator.DTO"
|
||||||
|
xmlns:controls="clr-namespace:Azaion.Annotator.Controls"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="Браузер анотацій" Height="900" Width="1200">
|
Title="Браузер анотацій" Height="900" Width="1200">
|
||||||
|
|
||||||
@@ -28,16 +29,82 @@
|
|||||||
<ColumnDefinition Width="150" />
|
<ColumnDefinition Width="150" />
|
||||||
<ColumnDefinition Width="4"/>
|
<ColumnDefinition Width="4"/>
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="4"/>
|
|
||||||
<ColumnDefinition Width="200" />
|
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<vwp:GridView
|
<controls:AnnotationClasses
|
||||||
|
x:Name="LvClasses"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.Row="0">
|
||||||
|
</controls:AnnotationClasses>
|
||||||
|
|
||||||
|
<TabControl
|
||||||
|
Name="Switcher"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Background="Black">
|
||||||
|
<TabItem Header="Браузер">
|
||||||
|
<vwp:GridView
|
||||||
|
Name="ThumbnailsView"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Background="Black"
|
||||||
Margin="2,5,2,2"
|
Margin="2,5,2,2"
|
||||||
ItemsSource="{Binding ThumbnailsDtos, Mode=OneWay}"
|
ItemsSource="{Binding ThumbnailsDtos, Mode=OneWay}"
|
||||||
ItemTemplate="{StaticResource ThumbnailTemplate}">
|
ItemTemplate="{StaticResource ThumbnailTemplate}"
|
||||||
|
>
|
||||||
</vwp:GridView>
|
</vwp:GridView>
|
||||||
|
</TabItem>
|
||||||
|
<TabItem Header="Перегляд">
|
||||||
|
<controls:CanvasEditor x:Name="ExplorerEditor"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
HorizontalAlignment="Stretch" >
|
||||||
|
</controls:CanvasEditor>
|
||||||
|
</TabItem>
|
||||||
|
</TabControl>
|
||||||
|
<StatusBar
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="3"
|
||||||
|
Background="#252525"
|
||||||
|
Foreground="White"
|
||||||
|
>
|
||||||
|
<StatusBar.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
</Grid>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</StatusBar.ItemsPanel>
|
||||||
|
<StatusBarItem Grid.Column="0" Background="Black">
|
||||||
|
<TextBlock>Loading:</TextBlock>
|
||||||
|
</StatusBarItem>
|
||||||
|
<StatusBarItem Grid.Column="1" Background="Black">
|
||||||
|
<controls:UpdatableProgressBar x:Name="Volume"
|
||||||
|
Width="150"
|
||||||
|
Height="15"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Background="#252525"
|
||||||
|
BorderBrush="#252525"
|
||||||
|
Foreground="LightBlue"
|
||||||
|
Maximum="100"
|
||||||
|
Minimum="0">
|
||||||
|
</controls:UpdatableProgressBar>
|
||||||
|
</StatusBarItem>
|
||||||
|
<Separator Grid.Column="2"/>
|
||||||
|
<StatusBarItem Grid.Column="3" Background="Black">
|
||||||
|
<TextBlock Text="{Binding AnnotationCount}" />
|
||||||
|
</StatusBarItem>
|
||||||
|
</StatusBar>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -1,39 +1,203 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
using Azaion.Annotator.DTO;
|
using Azaion.Annotator.DTO;
|
||||||
|
using Azaion.Annotator.Extensions;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using MessageBox = System.Windows.MessageBox;
|
||||||
|
|
||||||
namespace Azaion.Annotator;
|
namespace Azaion.Annotator;
|
||||||
|
|
||||||
public partial class DatasetExplorer
|
public partial class DatasetExplorer
|
||||||
{
|
{
|
||||||
private readonly Config _config;
|
private readonly Config _config;
|
||||||
|
private readonly ILogger<DatasetExplorer> _logger;
|
||||||
|
|
||||||
public ObservableCollection<ThumbnailDto> ThumbnailsDtos { get; set; } = new();
|
public ObservableCollection<ThumbnailDto> ThumbnailsDtos { get; set; } = new();
|
||||||
|
private ObservableCollection<AnnotationClass> AllAnnotationClasses { get; set; } = new();
|
||||||
|
|
||||||
public DatasetExplorer(Config config)
|
private int _tempSelectedClassIdx = 0;
|
||||||
|
private readonly string _thumbnailsCacheFile;
|
||||||
|
private IConfigRepository _configRepository;
|
||||||
|
private readonly FormState _formState;
|
||||||
|
private static Dictionary<string, List<int>> LabelsCache { get; set; } = new();
|
||||||
|
|
||||||
|
public string CurrentImage { get; set; }
|
||||||
|
|
||||||
|
public DatasetExplorer(Config config, ILogger<DatasetExplorer> logger, IConfigRepository configRepository, FormState formState)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_logger = logger;
|
||||||
|
_configRepository = configRepository;
|
||||||
|
_formState = formState;
|
||||||
|
_thumbnailsCacheFile = Path.Combine(config.ThumbnailsDirectory, Config.ThumbnailsCacheFile);
|
||||||
|
if (File.Exists(_thumbnailsCacheFile))
|
||||||
|
{
|
||||||
|
var cache = JsonConvert.DeserializeObject<Dictionary<string, List<int>>>(File.ReadAllText(_thumbnailsCacheFile));
|
||||||
|
LabelsCache = cache ?? new Dictionary<string, List<int>>();
|
||||||
|
}
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
Loaded += async (sender, args) => await LoadThumbnails();
|
Loaded += (_, _) =>
|
||||||
|
{
|
||||||
|
AllAnnotationClasses = new ObservableCollection<AnnotationClass>(
|
||||||
|
new List<AnnotationClass> { new(-1, "All") }
|
||||||
|
.Concat(_config.AnnotationClasses));
|
||||||
|
LvClasses.ItemsSource = AllAnnotationClasses;
|
||||||
|
|
||||||
|
LvClasses.SelectionChanged += async (_, _) =>
|
||||||
|
{
|
||||||
|
var selectedClass = (AnnotationClass)LvClasses.SelectedItem;
|
||||||
|
await SelectClass(selectedClass);
|
||||||
|
};
|
||||||
|
LvClasses.SelectedIndex = 0;
|
||||||
|
|
||||||
|
SizeChanged += async (_, _) => await SaveUserSettings();
|
||||||
|
LocationChanged += async (_, _) => await SaveUserSettings();
|
||||||
|
StateChanged += async (_, _) => await SaveUserSettings();
|
||||||
|
};
|
||||||
|
|
||||||
Closing += (sender, args) =>
|
Closing += (sender, args) =>
|
||||||
{
|
{
|
||||||
args.Cancel = true;
|
args.Cancel = true;
|
||||||
Visibility = Visibility.Hidden;
|
Visibility = Visibility.Hidden;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ThumbnailsView.KeyDown += (sender, args) =>
|
||||||
|
{
|
||||||
|
switch (args.Key)
|
||||||
|
{
|
||||||
|
case Key.Delete:
|
||||||
|
DeleteAnnotations();
|
||||||
|
break;
|
||||||
|
case Key.Enter:
|
||||||
|
EditAnnotation();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ThumbnailsView.MouseDoubleClick += async (_, _) => await EditAnnotation();
|
||||||
|
|
||||||
|
ExplorerEditor.KeyDown += (_, args) =>
|
||||||
|
{
|
||||||
|
var key = args.Key;
|
||||||
|
var keyNumber = (int?)null;
|
||||||
|
|
||||||
|
if ((int)key >= (int)Key.D1 && (int)key <= (int)Key.D9)
|
||||||
|
keyNumber = key - Key.D1;
|
||||||
|
if ((int)key >= (int)Key.NumPad1 && (int)key <= (int)Key.NumPad9)
|
||||||
|
keyNumber = key - Key.NumPad1;
|
||||||
|
if (!keyNumber.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LvClasses.SelectedIndex = keyNumber.Value;
|
||||||
|
};
|
||||||
|
|
||||||
|
ExplorerEditor.GetTimeFunc = () => _formState.GetTime(CurrentImage!)!.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadThumbnails()
|
private async Task EditAnnotation()
|
||||||
|
{
|
||||||
|
if (ThumbnailsView.SelectedItem == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var dto = (ThumbnailsView.SelectedItem as ThumbnailDto)!;
|
||||||
|
ExplorerEditor.Background = new ImageBrush
|
||||||
|
{
|
||||||
|
ImageSource = new BitmapImage(new Uri(dto.ImagePath))
|
||||||
|
};
|
||||||
|
CurrentImage = dto.ImagePath;
|
||||||
|
Switcher.SelectedIndex = 1;
|
||||||
|
LvClasses.SelectedIndex = 1;
|
||||||
|
|
||||||
|
var time = _formState.GetTime(CurrentImage)!.Value;
|
||||||
|
foreach (var ann in await YoloLabel.ReadFromFile(dto.LabelPath))
|
||||||
|
{
|
||||||
|
var annClass = _config.AnnotationClasses[ann.ClassNumber];
|
||||||
|
var annInfo = new CanvasLabel(ann, ExplorerEditor.RenderSize, ExplorerEditor.RenderSize);
|
||||||
|
Dispatcher.Invoke(() => ExplorerEditor.CreateAnnotation(annClass, time, annInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
Switcher.SelectionChanged += (_, args) =>
|
||||||
|
{
|
||||||
|
//From Explorer to Editor
|
||||||
|
if (Switcher.SelectedIndex == 1)
|
||||||
|
{
|
||||||
|
_tempSelectedClassIdx = LvClasses.SelectedIndex;
|
||||||
|
LvClasses.ItemsSource = _config.AnnotationClasses;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LvClasses.ItemsSource = AllAnnotationClasses;
|
||||||
|
LvClasses.SelectedIndex = _tempSelectedClassIdx;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SelectClass(AnnotationClass annClass)
|
||||||
|
{
|
||||||
|
ExplorerEditor.CurrentAnnClass = annClass;
|
||||||
|
|
||||||
|
if (Switcher.SelectedIndex == 0)
|
||||||
|
await ReloadThumbnails();
|
||||||
|
else
|
||||||
|
foreach (var ann in ExplorerEditor.CurrentAnns.Where(x => x.IsSelected))
|
||||||
|
ann.AnnotationClass = annClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveUserSettings()
|
||||||
|
{
|
||||||
|
_config.DatasetExplorerConfig = this.GetConfig();
|
||||||
|
await ThrottleExt.Throttle(() =>
|
||||||
|
{
|
||||||
|
_configRepository.Save(_config);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}, TimeSpan.FromSeconds(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteAnnotations()
|
||||||
|
{
|
||||||
|
var result = MessageBox.Show("Чи дійсно видалити аннотації?","Підтвердження видалення", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||||
|
if (result != MessageBoxResult.Yes)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var selected = ThumbnailsView.SelectedItems.Count;
|
||||||
|
for (var i = 0; i < selected; i++)
|
||||||
|
{
|
||||||
|
var dto = (ThumbnailsView.SelectedItems[0] as ThumbnailDto)!;
|
||||||
|
File.Delete(dto.ImagePath);
|
||||||
|
File.Delete(dto.LabelPath);
|
||||||
|
File.Delete(dto.ThumbnailPath);
|
||||||
|
ThumbnailsDtos.Remove(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReloadThumbnails()
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(_config.ThumbnailsDirectory))
|
if (!Directory.Exists(_config.ThumbnailsDirectory))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
ThumbnailsDtos.Clear();
|
||||||
var thumbnails = Directory.GetFiles(_config.ThumbnailsDirectory, "*.jpg");
|
var thumbnails = Directory.GetFiles(_config.ThumbnailsDirectory, "*.jpg");
|
||||||
|
|
||||||
|
var thumbNum = 0;
|
||||||
foreach (var thumbnail in thumbnails)
|
foreach (var thumbnail in thumbnails)
|
||||||
|
{
|
||||||
|
await AddThumbnail(thumbnail);
|
||||||
|
|
||||||
|
if (thumbNum % 1000 == 0)
|
||||||
|
await File.WriteAllTextAsync(_thumbnailsCacheFile, JsonConvert.SerializeObject(LabelsCache));
|
||||||
|
thumbNum++;
|
||||||
|
}
|
||||||
|
await File.WriteAllTextAsync(_thumbnailsCacheFile, JsonConvert.SerializeObject(LabelsCache));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddThumbnail(string thumbnail)
|
||||||
{
|
{
|
||||||
var name = Path.GetFileNameWithoutExtension(thumbnail)[..^Config.ThumbnailPrefix.Length];
|
var name = Path.GetFileNameWithoutExtension(thumbnail)[..^Config.ThumbnailPrefix.Length];
|
||||||
var imageName = Path.Combine(_config.ImagesDirectory, name);
|
var imageName = Path.Combine(_config.ImagesDirectory, name);
|
||||||
@@ -44,12 +208,30 @@ public partial class DatasetExplorer
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var labelPath = Path.Combine(_config.LabelsDirectory, $"{name}.txt");
|
||||||
|
|
||||||
|
if (!LabelsCache.TryGetValue(name, out var classes))
|
||||||
|
{
|
||||||
|
if (!File.Exists(labelPath))
|
||||||
|
{
|
||||||
|
_logger.LogError($"No label {labelPath} found ! Image {(!File.Exists(imageName) ? "not exists!" : "exists.")}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var labels = await YoloLabel.ReadFromFile(labelPath);
|
||||||
|
classes = labels.Select(x => x.ClassNumber).Distinct().ToList();
|
||||||
|
LabelsCache.Add(name, classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (classes.Contains(ExplorerEditor.CurrentAnnClass.Id) || ExplorerEditor.CurrentAnnClass.Id == -1)
|
||||||
|
{
|
||||||
ThumbnailsDtos.Add(new ThumbnailDto
|
ThumbnailsDtos.Add(new ThumbnailDto
|
||||||
{
|
{
|
||||||
ThumbnailPath = thumbnail,
|
ThumbnailPath = thumbnail,
|
||||||
ImagePath = imageName,
|
ImagePath = imageName,
|
||||||
LabelPath = Path.Combine(_config.LabelsDirectory, $"{name}.txt"),
|
LabelPath = labelPath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,9 @@ public static class ColorExtensions
|
|||||||
public static Color ToColor(this int id)
|
public static Color ToColor(this int id)
|
||||||
{
|
{
|
||||||
var index = id % ColorValues.Length;
|
var index = id % ColorValues.Length;
|
||||||
var hex = $"#40{ColorValues[index]}";
|
var hex = index == -1
|
||||||
|
? "#40DDDDDD"
|
||||||
|
: $"#40{ColorValues[index]}";
|
||||||
var color =(Color)ColorConverter.ConvertFromString(hex);
|
var color =(Color)ColorConverter.ConvertFromString(hex);
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using Azaion.Annotator.DTO;
|
||||||
|
|
||||||
|
namespace Azaion.Annotator.Extensions;
|
||||||
|
|
||||||
|
public static class WindowExtensions
|
||||||
|
{
|
||||||
|
public static WindowConfig GetConfig(this Window window) =>
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
WindowSize = new Size(window.Width, window.Height),
|
||||||
|
WindowLocation = new Point(window.Left, window.Top),
|
||||||
|
FullScreen = window.WindowState == WindowState.Maximized
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -42,9 +42,12 @@ public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGa
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var bitmap = await GenerateThumbnail(img);
|
var bitmap = await GenerateThumbnail(img);
|
||||||
|
if (bitmap != null)
|
||||||
|
{
|
||||||
var thumbnailName = Path.Combine(thumbnailsDir.FullName, $"{imgName}{Config.ThumbnailPrefix}.jpg");
|
var thumbnailName = Path.Combine(thumbnailsDir.FullName, $"{imgName}{Config.ThumbnailPrefix}.jpg");
|
||||||
bitmap.Save(thumbnailName, ImageFormat.Jpeg);
|
bitmap.Save(thumbnailName, ImageFormat.Jpeg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
logger.LogError(e, $"Failed to generate thumbnail for {img.Name}");
|
logger.LogError(e, $"Failed to generate thumbnail for {img.Name}");
|
||||||
@@ -54,7 +57,7 @@ public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Bitmap> GenerateThumbnail(FileInfo img)
|
private async Task<Bitmap?> GenerateThumbnail(FileInfo img)
|
||||||
{
|
{
|
||||||
var width = (int)config.ThumbnailConfig.Size.Width;
|
var width = (int)config.ThumbnailConfig.Size.Width;
|
||||||
var height = (int)config.ThumbnailConfig.Size.Height;
|
var height = (int)config.ThumbnailConfig.Size.Height;
|
||||||
@@ -62,7 +65,7 @@ public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGa
|
|||||||
var imgName = Path.GetFileNameWithoutExtension(img.Name);
|
var imgName = Path.GetFileNameWithoutExtension(img.Name);
|
||||||
var labelName = Path.Combine(config.LabelsDirectory, $"{imgName}.txt");
|
var labelName = Path.Combine(config.LabelsDirectory, $"{imgName}.txt");
|
||||||
|
|
||||||
var originalImage = Image.FromFile(img.FullName);
|
var originalImage = Image.FromStream(new MemoryStream(await File.ReadAllBytesAsync(img.FullName)));
|
||||||
|
|
||||||
var bitmap = new Bitmap(width, height);
|
var bitmap = new Bitmap(width, height);
|
||||||
|
|
||||||
@@ -72,6 +75,12 @@ public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGa
|
|||||||
g.InterpolationMode = InterpolationMode.Default;
|
g.InterpolationMode = InterpolationMode.Default;
|
||||||
|
|
||||||
var size = new Size(originalImage.Width, originalImage.Height);
|
var size = new Size(originalImage.Width, originalImage.Height);
|
||||||
|
if (!File.Exists(labelName))
|
||||||
|
{
|
||||||
|
File.Move(img.FullName, Path.Combine(config.UnknownImages, Path.GetFileName(img.Name)));
|
||||||
|
logger.LogInformation($"No labels found for image {img.Name}! Moved image to the {config.UnknownImages} folder.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
var labels = (await YoloLabel.ReadFromFile(labelName))
|
var labels = (await YoloLabel.ReadFromFile(labelName))
|
||||||
.Select(x => new CanvasLabel(x, size, size))
|
.Select(x => new CanvasLabel(x, size, size))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|||||||
@@ -160,51 +160,11 @@
|
|||||||
</GridView>
|
</GridView>
|
||||||
</ListView.View>
|
</ListView.View>
|
||||||
</ListView>
|
</ListView>
|
||||||
<DataGrid x:Name="LvClasses"
|
<controls:AnnotationClasses
|
||||||
|
x:Name="LvClasses"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="4"
|
Grid.Row="4">
|
||||||
Background="Black"
|
</controls:AnnotationClasses>
|
||||||
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="50"
|
|
||||||
Header="Клавіша"
|
|
||||||
CanUserSort="False">
|
|
||||||
<DataGridTemplateColumn.HeaderStyle>
|
|
||||||
<Style TargetType="DataGridColumnHeader">
|
|
||||||
<Setter Property="Background" Value="#252525"></Setter>
|
|
||||||
</Style>
|
|
||||||
</DataGridTemplateColumn.HeaderStyle>
|
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<Border Background="{Binding Path=ColorBrush}">
|
|
||||||
<TextBlock Text="{Binding Path=ClassNumber}"></TextBlock>
|
|
||||||
</Border>
|
|
||||||
</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>
|
|
||||||
<GridSplitter
|
<GridSplitter
|
||||||
Background="DarkGray"
|
Background="DarkGray"
|
||||||
ResizeDirection="Columns"
|
ResizeDirection="Columns"
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public partial class MainWindow
|
|||||||
private readonly IGalleryManager _galleryManager;
|
private readonly IGalleryManager _galleryManager;
|
||||||
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
public ObservableCollection<AnnotationClass> AnnotationClasses { get; set; } = new();
|
private ObservableCollection<AnnotationClass> AnnotationClasses { get; set; } = new();
|
||||||
private bool _suspendLayout;
|
private bool _suspendLayout;
|
||||||
|
|
||||||
private readonly TimeSpan _thresholdBefore = TimeSpan.FromMilliseconds(100);
|
private readonly TimeSpan _thresholdBefore = TimeSpan.FromMilliseconds(100);
|
||||||
@@ -66,6 +66,15 @@ public partial class MainWindow
|
|||||||
|
|
||||||
VideoView.Loaded += VideoView_Loaded;
|
VideoView.Loaded += VideoView_Loaded;
|
||||||
Closed += OnFormClosed;
|
Closed += OnFormClosed;
|
||||||
|
|
||||||
|
if (!Directory.Exists(_config.LabelsDirectory))
|
||||||
|
Directory.CreateDirectory(_config.LabelsDirectory);
|
||||||
|
if (!Directory.Exists(_config.ImagesDirectory))
|
||||||
|
Directory.CreateDirectory(_config.ImagesDirectory);
|
||||||
|
if (!Directory.Exists(_config.ResultsDirectory))
|
||||||
|
Directory.CreateDirectory(_config.ResultsDirectory);
|
||||||
|
|
||||||
|
Editor.GetTimeFunc = () => TimeSpan.FromMilliseconds(_mediaPlayer.Time);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void VideoView_Loaded(object sender, RoutedEventArgs e)
|
private void VideoView_Loaded(object sender, RoutedEventArgs e)
|
||||||
@@ -84,16 +93,22 @@ public partial class MainWindow
|
|||||||
|
|
||||||
_suspendLayout = true;
|
_suspendLayout = true;
|
||||||
|
|
||||||
Left = _config.WindowLocation.X;
|
Left = _config.MainWindowConfig.WindowLocation.X;
|
||||||
Top = _config.WindowLocation.Y;
|
Top = _config.MainWindowConfig.WindowLocation.Y;
|
||||||
|
Width = _config.MainWindowConfig.WindowSize.Width;
|
||||||
|
Height = _config.MainWindowConfig.WindowSize.Height;
|
||||||
|
|
||||||
Width = _config.WindowSize.Width;
|
_datasetExplorer.Left = _config.MainWindowConfig.WindowLocation.X;
|
||||||
Height = _config.WindowSize.Height;
|
_datasetExplorer.Top = _config.DatasetExplorerConfig.WindowLocation.Y;
|
||||||
|
_datasetExplorer.Width = _config.DatasetExplorerConfig.WindowSize.Width;
|
||||||
|
_datasetExplorer.Height = _config.DatasetExplorerConfig.WindowSize.Height;
|
||||||
|
if (_config.DatasetExplorerConfig.FullScreen)
|
||||||
|
_datasetExplorer.WindowState = WindowState.Maximized;
|
||||||
|
|
||||||
MainGrid.ColumnDefinitions.FirstOrDefault()!.Width = new GridLength(_config.LeftPanelWidth);
|
MainGrid.ColumnDefinitions.FirstOrDefault()!.Width = new GridLength(_config.LeftPanelWidth);
|
||||||
MainGrid.ColumnDefinitions.LastOrDefault()!.Width = new GridLength(_config.RightPanelWidth);
|
MainGrid.ColumnDefinitions.LastOrDefault()!.Width = new GridLength(_config.RightPanelWidth);
|
||||||
|
|
||||||
if (_config.FullScreen)
|
if (_config.MainWindowConfig.FullScreen)
|
||||||
WindowState = WindowState.Maximized;
|
WindowState = WindowState.Maximized;
|
||||||
|
|
||||||
_suspendLayout = false;
|
_suspendLayout = false;
|
||||||
@@ -190,9 +205,8 @@ public partial class MainWindow
|
|||||||
|
|
||||||
_config.LeftPanelWidth = MainGrid.ColumnDefinitions.FirstOrDefault()!.Width.Value;
|
_config.LeftPanelWidth = MainGrid.ColumnDefinitions.FirstOrDefault()!.Width.Value;
|
||||||
_config.RightPanelWidth = MainGrid.ColumnDefinitions.LastOrDefault()!.Width.Value;
|
_config.RightPanelWidth = MainGrid.ColumnDefinitions.LastOrDefault()!.Width.Value;
|
||||||
_config.WindowSize = new Size(Width, Height);
|
|
||||||
_config.WindowLocation = new Point(Left, Top);
|
_config.MainWindowConfig = this.GetConfig();
|
||||||
_config.FullScreen = WindowState == WindowState.Maximized;
|
|
||||||
await ThrottleExt.Throttle(() =>
|
await ThrottleExt.Throttle(() =>
|
||||||
{
|
{
|
||||||
_configRepository.Save(_config);
|
_configRepository.Save(_config);
|
||||||
@@ -303,7 +317,6 @@ public partial class MainWindow
|
|||||||
_mediaPlayer.Stop();
|
_mediaPlayer.Stop();
|
||||||
_mediaPlayer.Dispose();
|
_mediaPlayer.Dispose();
|
||||||
_libVLC.Dispose();
|
_libVLC.Dispose();
|
||||||
_config.AnnotationClasses = AnnotationClasses.ToList();
|
|
||||||
_configRepository.Save(_config);
|
_configRepository.Save(_config);
|
||||||
Application.Current.Shutdown();
|
Application.Current.Shutdown();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,8 +69,6 @@ public class PlayerControlHandler :
|
|||||||
|
|
||||||
public async Task Handle(KeyEvent notification, CancellationToken cancellationToken)
|
public async Task Handle(KeyEvent notification, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"Catch {notification.Args.Key} by {notification.Sender.GetType().Name}");
|
|
||||||
|
|
||||||
var key = notification.Args.Key;
|
var key = notification.Args.Key;
|
||||||
var keyNumber = (int?)null;
|
var keyNumber = (int?)null;
|
||||||
|
|
||||||
@@ -79,7 +77,7 @@ public class PlayerControlHandler :
|
|||||||
if ((int)key >= (int)Key.NumPad1 && (int)key <= (int)Key.NumPad9)
|
if ((int)key >= (int)Key.NumPad1 && (int)key <= (int)Key.NumPad9)
|
||||||
keyNumber = key - Key.NumPad1;
|
keyNumber = key - Key.NumPad1;
|
||||||
if (keyNumber.HasValue)
|
if (keyNumber.HasValue)
|
||||||
SelectClass(_mainWindow.AnnotationClasses[keyNumber.Value]);
|
SelectClass((AnnotationClass)_mainWindow.LvClasses.Items[keyNumber.Value]);
|
||||||
|
|
||||||
if (_keysControlEnumDict.TryGetValue(key, out var value))
|
if (_keysControlEnumDict.TryGetValue(key, out var value))
|
||||||
await ControlPlayback(value);
|
await ControlPlayback(value);
|
||||||
@@ -234,16 +232,9 @@ public class PlayerControlHandler :
|
|||||||
var currentAnns = _mainWindow.Editor.CurrentAnns
|
var currentAnns = _mainWindow.Editor.CurrentAnns
|
||||||
.Select(x => new YoloLabel(x.Info, _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()));
|
|
||||||
|
|
||||||
if (!Directory.Exists(_config.LabelsDirectory))
|
await YoloLabel.WriteToFile(currentAnns, Path.Combine(_config.LabelsDirectory, $"{fName}.txt"));
|
||||||
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(Path.Combine(_config.LabelsDirectory, $"{fName}.txt"), labels);
|
|
||||||
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);
|
||||||
|
|
||||||
await _mainWindow.AddAnnotation(time, currentAnns);
|
await _mainWindow.AddAnnotation(time, currentAnns);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"LabelsDirectory": "E:\\labels",
|
"LabelsDirectory": "E:\\labels",
|
||||||
"ImagesDirectory": "E:\\images",
|
"ImagesDirectory": "E:\\images",
|
||||||
"ResultsDirectory": "E:\\results",
|
"ResultsDirectory": "E:\\results",
|
||||||
|
"UnknownImages": "E:\\unknown",
|
||||||
"ThumbnailsDirectory": "E:\\thumbnails",
|
"ThumbnailsDirectory": "E:\\thumbnails",
|
||||||
"AnnotationClasses": [
|
"AnnotationClasses": [
|
||||||
{ "Id": 0, "Name": "Броньована техніка", "Color": "#40FF0000" },
|
{ "Id": 0, "Name": "Броньована техніка", "Color": "#40FF0000" },
|
||||||
@@ -16,13 +17,20 @@
|
|||||||
{ "Id": 8, "Name": "Танк з захистом", "Color": "#40008000" },
|
{ "Id": 8, "Name": "Танк з захистом", "Color": "#40008000" },
|
||||||
{ "Id": 9, "Name": "Дим", "Color": "#40000080" }
|
{ "Id": 9, "Name": "Дим", "Color": "#40000080" }
|
||||||
],
|
],
|
||||||
|
"MainWindowConfig": {
|
||||||
"WindowSize": "1920,1080",
|
"WindowSize": "1920,1080",
|
||||||
"WindowLocation": "200,121",
|
"WindowLocation": "50,50",
|
||||||
|
"FullScreen": true
|
||||||
|
},
|
||||||
|
"DatasetExplorerConfig": {
|
||||||
|
"WindowSize": "1920,1080",
|
||||||
|
"WindowLocation": "50,50",
|
||||||
|
"FullScreen": true
|
||||||
|
},
|
||||||
"ThumbnailConfig": {
|
"ThumbnailConfig": {
|
||||||
"Size": "480,270",
|
"Size": "480,270",
|
||||||
"Border": 10
|
"Border": 10
|
||||||
},
|
},
|
||||||
"FullScreen": true,
|
|
||||||
"LeftPanelWidth": 300,
|
"LeftPanelWidth": 300,
|
||||||
"RightPanelWidth": 300,
|
"RightPanelWidth": 300,
|
||||||
"ShowHelpOnStart": false,
|
"ShowHelpOnStart": false,
|
||||||
|
|||||||
Reference in New Issue
Block a user