diff --git a/Azaion.Annotator/Azaion.Annotator.csproj b/Azaion.Annotator/Azaion.Annotator.csproj
index e657879..5850804 100644
--- a/Azaion.Annotator/Azaion.Annotator.csproj
+++ b/Azaion.Annotator/Azaion.Annotator.csproj
@@ -37,7 +37,7 @@
PreserveNewest
- PreserveNewest
+ Always
diff --git a/Azaion.Annotator/Controls/AnnotationClasses.xaml b/Azaion.Annotator/Controls/AnnotationClasses.xaml
new file mode 100644
index 0000000..c070efa
--- /dev/null
+++ b/Azaion.Annotator/Controls/AnnotationClasses.xaml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Azaion.Annotator/Controls/AnnotationClasses.xaml.cs b/Azaion.Annotator/Controls/AnnotationClasses.xaml.cs
new file mode 100644
index 0000000..70b9dd4
--- /dev/null
+++ b/Azaion.Annotator/Controls/AnnotationClasses.xaml.cs
@@ -0,0 +1,11 @@
+using System.Windows.Controls;
+
+namespace Azaion.Annotator.Controls;
+
+public partial class AnnotationClasses : DataGrid
+{
+ public AnnotationClasses()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/Azaion.Annotator/Controls/CanvasEditor.cs b/Azaion.Annotator/Controls/CanvasEditor.cs
index c8e1ddc..8b5438a 100644
--- a/Azaion.Annotator/Controls/CanvasEditor.cs
+++ b/Azaion.Annotator/Controls/CanvasEditor.cs
@@ -13,7 +13,8 @@ namespace Azaion.Annotator.Controls;
public class CanvasEditor : Canvas
{
private Point _lastPos;
-
+ public SelectionState SelectionState { get; set; } = SelectionState.None;
+
private readonly Rectangle _newAnnotationRect;
private Point _newAnnotationStartPos;
@@ -30,6 +31,19 @@ public class CanvasEditor : Canvas
public FormState FormState { get; set; } = null!;
public IMediator Mediator { get; set; } = null!;
+ public static readonly DependencyProperty GetTimeFuncProp =
+ DependencyProperty.Register(
+ nameof(GetTimeFunc),
+ typeof(Func),
+ typeof(CanvasEditor),
+ new PropertyMetadata(null));
+
+ public Func GetTimeFunc
+ {
+ get => (Func)GetValue(GetTimeFuncProp);
+ set => SetValue(GetTimeFuncProp, value);
+ }
+
private AnnotationClass _currentAnnClass = null!;
public AnnotationClass CurrentAnnClass
{
@@ -84,11 +98,7 @@ public class CanvasEditor : Canvas
Stroke = new SolidColorBrush(Colors.Gray),
Fill = new SolidColorBrush(Color.FromArgb(128, 128, 128, 128)),
};
- Loaded += Init;
- }
- private void Init(object sender, RoutedEventArgs e)
- {
KeyDown += (_, args) =>
{
Console.WriteLine($"pressed {args.Key}");
@@ -98,15 +108,21 @@ public class CanvasEditor : Canvas
MouseUp += CanvasMouseUp;
SizeChanged += CanvasResized;
Cursor = Cursors.Cross;
- _horizontalLine.X1 = 0;
- _horizontalLine.X2 = ActualWidth;
- _verticalLine.Y1 = 0;
- _verticalLine.Y2 = ActualHeight;
-
+
Children.Add(_newAnnotationRect);
Children.Add(_horizontalLine);
Children.Add(_verticalLine);
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)
@@ -125,22 +141,22 @@ public class CanvasEditor : Canvas
if (e.LeftButton != MouseButtonState.Pressed)
return;
- if (FormState.SelectionState == SelectionState.NewAnnCreating)
+ if (SelectionState == SelectionState.NewAnnCreating)
NewAnnotationCreatingMove(sender, e);
- if (FormState.SelectionState == SelectionState.AnnResizing)
+ if (SelectionState == SelectionState.AnnResizing)
AnnotationResizeMove(sender, e);
- if (FormState.SelectionState == SelectionState.AnnMoving)
+ if (SelectionState == SelectionState.AnnMoving)
AnnotationPositionMove(sender, e);
}
private void CanvasMouseUp(object sender, MouseButtonEventArgs e)
{
- if (FormState.SelectionState == SelectionState.NewAnnCreating)
+ if (SelectionState == SelectionState.NewAnnCreating)
CreateAnnotation(e.GetPosition(this));
- FormState.SelectionState = SelectionState.None;
+ SelectionState = SelectionState.None;
e.Handled = true;
}
@@ -154,7 +170,7 @@ public class CanvasEditor : Canvas
private void AnnotationResizeStart(object sender, MouseEventArgs e)
{
- FormState.SelectionState = SelectionState.AnnResizing;
+ SelectionState = SelectionState.AnnResizing;
_lastPos = e.GetPosition(this);
_curRec = (Rectangle)sender;
_curAnn = (AnnotationControl)((Grid)_curRec.Parent).Parent;
@@ -163,7 +179,7 @@ public class CanvasEditor : Canvas
private void AnnotationResizeMove(object sender, MouseEventArgs e)
{
- if (FormState.SelectionState != SelectionState.AnnResizing)
+ if (SelectionState != SelectionState.AnnResizing)
return;
var currentPos = e.GetPosition(this);
@@ -224,13 +240,13 @@ public class CanvasEditor : Canvas
_curAnn.IsSelected = true;
- FormState.SelectionState = SelectionState.AnnMoving;
+ SelectionState = SelectionState.AnnMoving;
e.Handled = true;
}
private void AnnotationPositionMove(object sender, MouseEventArgs e)
{
- if (FormState.SelectionState != SelectionState.AnnMoving)
+ if (SelectionState != SelectionState.AnnMoving)
return;
var currentPos = e.GetPosition(this);
@@ -255,12 +271,12 @@ public class CanvasEditor : Canvas
SetTop(_newAnnotationRect, _newAnnotationStartPos.Y);
_newAnnotationRect.MouseMove += NewAnnotationCreatingMove;
- FormState.SelectionState = SelectionState.NewAnnCreating;
+ SelectionState = SelectionState.NewAnnCreating;
}
private void NewAnnotationCreatingMove(object sender, MouseEventArgs e)
{
- if (FormState.SelectionState != SelectionState.NewAnnCreating)
+ if (SelectionState != SelectionState.NewAnnCreating)
return;
var currentPos = e.GetPosition(this);
@@ -284,8 +300,7 @@ public class CanvasEditor : Canvas
if (width < MIN_SIZE || height < MIN_SIZE)
return;
- var mainWindow = (MainWindow)((Window)((Grid)Parent).Parent).Owner;
- var time = TimeSpan.FromMilliseconds(mainWindow.VideoView.MediaPlayer.Time);
+ var time = GetTimeFunc();
CreateAnnotation(CurrentAnnClass, time, new CanvasLabel
{
Width = width,
diff --git a/Azaion.Annotator/DTO/Config.cs b/Azaion.Annotator/DTO/Config.cs
index 4836f63..52e4d8c 100644
--- a/Azaion.Annotator/DTO/Config.cs
+++ b/Azaion.Annotator/DTO/Config.cs
@@ -1,6 +1,5 @@
using System.IO;
using System.Text;
-using System.Windows.Media;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Size = System.Windows.Size;
@@ -11,21 +10,22 @@ namespace Azaion.Annotator.DTO;
public class Config
{
public const string ThumbnailPrefix = "_thumb";
+ public const string ThumbnailsCacheFile = "thumbnails.cache";
public string VideosDirectory { get; set; }
public string LabelsDirectory { get; set; }
public string ImagesDirectory { get; set; }
public string ResultsDirectory { get; set; }
public string ThumbnailsDirectory { get; set; }
+ public string UnknownImages { get; set; }
public List AnnotationClasses { get; set; } = [];
private Dictionary? _annotationClassesDict;
public Dictionary AnnotationClassesDict => _annotationClassesDict ??= AnnotationClasses.ToDictionary(x => x.Id);
- public Size WindowSize { get; set; }
- public Point WindowLocation { get; set; }
- public bool FullScreen { get; set; }
+ public WindowConfig MainWindowConfig { get; set; }
+ public WindowConfig DatasetExplorerConfig { get; set; }
public double LeftPanelWidth { get; set; }
public double RightPanelWidth { get; set; }
@@ -38,6 +38,13 @@ public class Config
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 Size Size { get; set; }
@@ -60,6 +67,7 @@ public class FileConfigRepository(ILogger logger) : IConfi
private const string DEFAULT_IMAGES_DIR = "images";
private const string DEFAULT_RESULTS_DIR = "results";
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 Point DefaultWindowLocation = new(100, 100);
@@ -82,9 +90,18 @@ public class FileConfigRepository(ILogger logger) : IConfi
ImagesDirectory = Path.Combine(exePath, DEFAULT_IMAGES_DIR),
ResultsDirectory = Path.Combine(exePath, DEFAULT_RESULTS_DIR),
ThumbnailsDirectory = Path.Combine(exePath, DEFAULT_THUMBNAILS_DIR),
+ UnknownImages = Path.Combine(exePath, DEFAULT_UNKNOWN_IMG_DIR),
- WindowLocation = DefaultWindowLocation,
- WindowSize = DefaultWindowSize,
+ MainWindowConfig = new WindowConfig
+ {
+ WindowSize = DefaultWindowSize,
+ WindowLocation = DefaultWindowLocation
+ },
+ DatasetExplorerConfig = new WindowConfig
+ {
+ WindowSize = DefaultWindowSize,
+ WindowLocation = DefaultWindowLocation
+ },
ShowHelpOnStart = true,
VideoFormats = DefaultVideoFormats,
diff --git a/Azaion.Annotator/DTO/FormState.cs b/Azaion.Annotator/DTO/FormState.cs
index c92d0e5..a52d5be 100644
--- a/Azaion.Annotator/DTO/FormState.cs
+++ b/Azaion.Annotator/DTO/FormState.cs
@@ -6,8 +6,6 @@ namespace Azaion.Annotator.DTO;
public class FormState
{
- public SelectionState SelectionState { get; set; } = SelectionState.None;
-
public MediaFileInfo? CurrentMedia { get; set; }
public string VideoName => string.IsNullOrEmpty(CurrentMedia?.Name)
? ""
diff --git a/Azaion.Annotator/DTO/Label.cs b/Azaion.Annotator/DTO/Label.cs
index b282fa0..fb49a13 100644
--- a/Azaion.Annotator/DTO/Label.cs
+++ b/Azaion.Annotator/DTO/Label.cs
@@ -7,11 +7,16 @@ namespace Azaion.Annotator.DTO;
public abstract class Label
{
- [JsonProperty(PropertyName = "cl")]
- public int ClassNumber { get; set; }
-
- public Label(){}
- public Label(int classNumber) { ClassNumber = classNumber; }
+ [JsonProperty(PropertyName = "cl")] public int ClassNumber { get; set; }
+
+ public Label()
+ {
+ }
+
+ public Label(int classNumber)
+ {
+ ClassNumber = classNumber;
+ }
}
public class CanvasLabel : Label
@@ -20,8 +25,11 @@ public class CanvasLabel : Label
public double Y { get; set; }
public double Width { 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)
{
X = x;
@@ -36,17 +44,17 @@ public class CanvasLabel : Label
var ch = canvasSize.Height;
var canvasAr = cw / ch;
var videoAr = videoSize.Width / videoSize.Height;
-
+
ClassNumber = label.ClassNumber;
-
+
var left = label.CenterX - label.Width / 2;
var top = label.CenterY - label.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;
@@ -67,19 +75,18 @@ public class CanvasLabel : Label
public class YoloLabel : Label
{
- [JsonProperty(PropertyName = "x")]
- public double CenterX { get; set; }
+ [JsonProperty(PropertyName = "x")] public double CenterX { get; set; }
- [JsonProperty(PropertyName = "y")]
- public double CenterY { get; set; }
+ [JsonProperty(PropertyName = "y")] public double CenterY { get; set; }
- [JsonProperty(PropertyName = "w")]
- public double Width { get; set; }
+ [JsonProperty(PropertyName = "w")] public double Width { get; set; }
+
+ [JsonProperty(PropertyName = "h")] public double Height { get; set; }
+
+ public YoloLabel()
+ {
+ }
- [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;
@@ -96,7 +103,7 @@ public class YoloLabel : Label
var videoAr = videoSize.Width / videoSize.Height;
ClassNumber = canvasLabel.ClassNumber;
-
+
double left, top;
if (videoAr > canvasAr) //100% width
{
@@ -125,8 +132,8 @@ public class YoloLabel : Label
{
if (string.IsNullOrEmpty(s))
return null;
-
- var strings = s.Replace(',','.').Split(' ');
+
+ var strings = s.Replace(',', '.').Split(' ');
if (strings.Length != 5)
return null;
@@ -158,6 +165,11 @@ public class YoloLabel : Label
.ToList()!;
}
+ public static async Task WriteToFile(IEnumerable 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(',', '.');
-
}
\ No newline at end of file
diff --git a/Azaion.Annotator/DatasetExplorer.xaml b/Azaion.Annotator/DatasetExplorer.xaml
index b28a222..8843055 100644
--- a/Azaion.Annotator/DatasetExplorer.xaml
+++ b/Azaion.Annotator/DatasetExplorer.xaml
@@ -6,6 +6,7 @@
xmlns:vwp="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
xmlns:local="clr-namespace:Azaion.Annotator"
xmlns:dto="clr-namespace:Azaion.Annotator.DTO"
+ xmlns:controls="clr-namespace:Azaion.Annotator.Controls"
mc:Ignorable="d"
Title="Браузер анотацій" Height="900" Width="1200">
@@ -28,16 +29,82 @@
-
-
-
+
+
+
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ Background="Black">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+ Loading:
+
+
+
+
+
+
+
+
+
+
diff --git a/Azaion.Annotator/DatasetExplorer.xaml.cs b/Azaion.Annotator/DatasetExplorer.xaml.cs
index 0b00114..bcc7671 100644
--- a/Azaion.Annotator/DatasetExplorer.xaml.cs
+++ b/Azaion.Annotator/DatasetExplorer.xaml.cs
@@ -1,55 +1,237 @@
using System.Collections.ObjectModel;
using System.IO;
using System.Windows;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
using Azaion.Annotator.DTO;
+using Azaion.Annotator.Extensions;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using MessageBox = System.Windows.MessageBox;
namespace Azaion.Annotator;
public partial class DatasetExplorer
{
private readonly Config _config;
+ private readonly ILogger _logger;
public ObservableCollection ThumbnailsDtos { get; set; } = new();
+ private ObservableCollection 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> LabelsCache { get; set; } = new();
+
+ public string CurrentImage { get; set; }
+
+ public DatasetExplorer(Config config, ILogger logger, IConfigRepository configRepository, FormState formState)
{
_config = config;
+ _logger = logger;
+ _configRepository = configRepository;
+ _formState = formState;
+ _thumbnailsCacheFile = Path.Combine(config.ThumbnailsDirectory, Config.ThumbnailsCacheFile);
+ if (File.Exists(_thumbnailsCacheFile))
+ {
+ var cache = JsonConvert.DeserializeObject>>(File.ReadAllText(_thumbnailsCacheFile));
+ LabelsCache = cache ?? new Dictionary>();
+ }
InitializeComponent();
DataContext = this;
- Loaded += async (sender, args) => await LoadThumbnails();
+ Loaded += (_, _) =>
+ {
+ AllAnnotationClasses = new ObservableCollection(
+ new List { 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) =>
{
args.Cancel = true;
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))
return;
+ ThumbnailsDtos.Clear();
var thumbnails = Directory.GetFiles(_config.ThumbnailsDirectory, "*.jpg");
+ var thumbNum = 0;
foreach (var thumbnail in thumbnails)
{
- var name = Path.GetFileNameWithoutExtension(thumbnail)[..^Config.ThumbnailPrefix.Length];
- var imageName = Path.Combine(_config.ImagesDirectory, name);
- foreach (var imageFormat in _config.ImageFormats)
+ 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 imageName = Path.Combine(_config.ImagesDirectory, name);
+ foreach (var imageFormat in _config.ImageFormats)
+ {
+ imageName = $"{imageName}.{imageFormat}";
+ if (File.Exists(imageName))
+ break;
+ }
+
+ var labelPath = Path.Combine(_config.LabelsDirectory, $"{name}.txt");
+
+ if (!LabelsCache.TryGetValue(name, out var classes))
+ {
+ if (!File.Exists(labelPath))
{
- imageName = $"{imageName}.{imageFormat}";
- if (File.Exists(imageName))
- break;
+ _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
{
ThumbnailPath = thumbnail,
ImagePath = imageName,
- LabelPath = Path.Combine(_config.LabelsDirectory, $"{name}.txt"),
+ LabelPath = labelPath
});
}
+
}
}
\ No newline at end of file
diff --git a/Azaion.Annotator/Extensions/ColorExtensions.cs b/Azaion.Annotator/Extensions/ColorExtensions.cs
index 44057d4..e96aa92 100644
--- a/Azaion.Annotator/Extensions/ColorExtensions.cs
+++ b/Azaion.Annotator/Extensions/ColorExtensions.cs
@@ -7,7 +7,9 @@ public static class ColorExtensions
public static Color ToColor(this int id)
{
var index = id % ColorValues.Length;
- var hex = $"#40{ColorValues[index]}";
+ var hex = index == -1
+ ? "#40DDDDDD"
+ : $"#40{ColorValues[index]}";
var color =(Color)ColorConverter.ConvertFromString(hex);
return color;
}
diff --git a/Azaion.Annotator/Extensions/WindowExtensions.cs b/Azaion.Annotator/Extensions/WindowExtensions.cs
new file mode 100644
index 0000000..48ec952
--- /dev/null
+++ b/Azaion.Annotator/Extensions/WindowExtensions.cs
@@ -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
+ };
+}
\ No newline at end of file
diff --git a/Azaion.Annotator/GalleryManager.cs b/Azaion.Annotator/GalleryManager.cs
index d42be1b..3470a94 100644
--- a/Azaion.Annotator/GalleryManager.cs
+++ b/Azaion.Annotator/GalleryManager.cs
@@ -42,8 +42,11 @@ public class GalleryManager(Config config, ILogger logger) : IGa
try
{
var bitmap = await GenerateThumbnail(img);
- var thumbnailName = Path.Combine(thumbnailsDir.FullName, $"{imgName}{Config.ThumbnailPrefix}.jpg");
- bitmap.Save(thumbnailName, ImageFormat.Jpeg);
+ if (bitmap != null)
+ {
+ var thumbnailName = Path.Combine(thumbnailsDir.FullName, $"{imgName}{Config.ThumbnailPrefix}.jpg");
+ bitmap.Save(thumbnailName, ImageFormat.Jpeg);
+ }
}
catch (Exception e)
{
@@ -54,7 +57,7 @@ public class GalleryManager(Config config, ILogger logger) : IGa
}
}
- private async Task GenerateThumbnail(FileInfo img)
+ private async Task GenerateThumbnail(FileInfo img)
{
var width = (int)config.ThumbnailConfig.Size.Width;
var height = (int)config.ThumbnailConfig.Size.Height;
@@ -62,7 +65,7 @@ public class GalleryManager(Config config, ILogger logger) : IGa
var imgName = Path.GetFileNameWithoutExtension(img.Name);
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);
@@ -72,6 +75,12 @@ public class GalleryManager(Config config, ILogger logger) : IGa
g.InterpolationMode = InterpolationMode.Default;
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))
.Select(x => new CanvasLabel(x, size, size))
.ToList();
diff --git a/Azaion.Annotator/MainWindow.xaml b/Azaion.Annotator/MainWindow.xaml
index 4fd13df..10b1eca 100644
--- a/Azaion.Annotator/MainWindow.xaml
+++ b/Azaion.Annotator/MainWindow.xaml
@@ -160,51 +160,11 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
AnnotationClasses { get; set; } = new();
+ private ObservableCollection AnnotationClasses { get; set; } = new();
private bool _suspendLayout;
private readonly TimeSpan _thresholdBefore = TimeSpan.FromMilliseconds(100);
@@ -66,6 +66,15 @@ public partial class MainWindow
VideoView.Loaded += VideoView_Loaded;
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)
@@ -84,16 +93,22 @@ public partial class MainWindow
_suspendLayout = true;
- Left = _config.WindowLocation.X;
- Top = _config.WindowLocation.Y;
+ Left = _config.MainWindowConfig.WindowLocation.X;
+ Top = _config.MainWindowConfig.WindowLocation.Y;
+ Width = _config.MainWindowConfig.WindowSize.Width;
+ Height = _config.MainWindowConfig.WindowSize.Height;
- Width = _config.WindowSize.Width;
- Height = _config.WindowSize.Height;
+ _datasetExplorer.Left = _config.MainWindowConfig.WindowLocation.X;
+ _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.LastOrDefault()!.Width = new GridLength(_config.RightPanelWidth);
- if (_config.FullScreen)
+ if (_config.MainWindowConfig.FullScreen)
WindowState = WindowState.Maximized;
_suspendLayout = false;
@@ -190,9 +205,8 @@ public partial class MainWindow
_config.LeftPanelWidth = MainGrid.ColumnDefinitions.FirstOrDefault()!.Width.Value;
_config.RightPanelWidth = MainGrid.ColumnDefinitions.LastOrDefault()!.Width.Value;
- _config.WindowSize = new Size(Width, Height);
- _config.WindowLocation = new Point(Left, Top);
- _config.FullScreen = WindowState == WindowState.Maximized;
+
+ _config.MainWindowConfig = this.GetConfig();
await ThrottleExt.Throttle(() =>
{
_configRepository.Save(_config);
@@ -303,7 +317,6 @@ public partial class MainWindow
_mediaPlayer.Stop();
_mediaPlayer.Dispose();
_libVLC.Dispose();
- _config.AnnotationClasses = AnnotationClasses.ToList();
_configRepository.Save(_config);
Application.Current.Shutdown();
}
diff --git a/Azaion.Annotator/PlayerControlHandler.cs b/Azaion.Annotator/PlayerControlHandler.cs
index fbfc77c..251578d 100644
--- a/Azaion.Annotator/PlayerControlHandler.cs
+++ b/Azaion.Annotator/PlayerControlHandler.cs
@@ -69,8 +69,6 @@ public class PlayerControlHandler :
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 keyNumber = (int?)null;
@@ -79,7 +77,7 @@ public class PlayerControlHandler :
if ((int)key >= (int)Key.NumPad1 && (int)key <= (int)Key.NumPad9)
keyNumber = key - Key.NumPad1;
if (keyNumber.HasValue)
- SelectClass(_mainWindow.AnnotationClasses[keyNumber.Value]);
+ SelectClass((AnnotationClass)_mainWindow.LvClasses.Items[keyNumber.Value]);
if (_keysControlEnumDict.TryGetValue(key, out var value))
await ControlPlayback(value);
@@ -234,16 +232,9 @@ public class PlayerControlHandler :
var currentAnns = _mainWindow.Editor.CurrentAnns
.Select(x => new YoloLabel(x.Info, _mainWindow.Editor.RenderSize, _formState.CurrentVideoSize))
.ToList();
- var labels = string.Join(Environment.NewLine, currentAnns.Select(x => x.ToString()));
- 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);
+ await YoloLabel.WriteToFile(currentAnns, Path.Combine(_config.LabelsDirectory, $"{fName}.txt"));
- await File.WriteAllTextAsync(Path.Combine(_config.LabelsDirectory, $"{fName}.txt"), labels);
var resultHeight = (uint)Math.Round(RESULT_WIDTH / _formState.CurrentVideoSize.Width * _formState.CurrentVideoSize.Height);
await _mainWindow.AddAnnotation(time, currentAnns);
diff --git a/Azaion.Annotator/config.json b/Azaion.Annotator/config.json
index 8858c13..a4109d6 100644
--- a/Azaion.Annotator/config.json
+++ b/Azaion.Annotator/config.json
@@ -3,6 +3,7 @@
"LabelsDirectory": "E:\\labels",
"ImagesDirectory": "E:\\images",
"ResultsDirectory": "E:\\results",
+ "UnknownImages": "E:\\unknown",
"ThumbnailsDirectory": "E:\\thumbnails",
"AnnotationClasses": [
{ "Id": 0, "Name": "Броньована техніка", "Color": "#40FF0000" },
@@ -16,13 +17,20 @@
{ "Id": 8, "Name": "Танк з захистом", "Color": "#40008000" },
{ "Id": 9, "Name": "Дим", "Color": "#40000080" }
],
- "WindowSize": "1920,1080",
- "WindowLocation": "200,121",
+ "MainWindowConfig": {
+ "WindowSize": "1920,1080",
+ "WindowLocation": "50,50",
+ "FullScreen": true
+ },
+ "DatasetExplorerConfig": {
+ "WindowSize": "1920,1080",
+ "WindowLocation": "50,50",
+ "FullScreen": true
+ },
"ThumbnailConfig": {
"Size": "480,270",
"Border": 10
},
- "FullScreen": true,
"LeftPanelWidth": 300,
"RightPanelWidth": 300,
"ShowHelpOnStart": false,