diff --git a/Azaion.Annotator/Azaion.Annotator.csproj b/Azaion.Annotator/Azaion.Annotator.csproj
index e590b0d..e657879 100644
--- a/Azaion.Annotator/Azaion.Annotator.csproj
+++ b/Azaion.Annotator/Azaion.Annotator.csproj
@@ -27,6 +27,7 @@
+
diff --git a/Azaion.Annotator/DTO/Label.cs b/Azaion.Annotator/DTO/Label.cs
index a78d054..b282fa0 100644
--- a/Azaion.Annotator/DTO/Label.cs
+++ b/Azaion.Annotator/DTO/Label.cs
@@ -148,9 +148,9 @@ public class YoloLabel : Label
}
}
- public static async Task> ReadFromFile(string filename, CancellationToken cancellationToken)
+ public static async Task> ReadFromFile(string filename)
{
- var str = await File.ReadAllTextAsync(filename, cancellationToken);
+ var str = await File.ReadAllTextAsync(filename);
return str.Split(Environment.NewLine)
.Select(Parse)
diff --git a/Azaion.Annotator/DTO/ThumbnailDto.cs b/Azaion.Annotator/DTO/ThumbnailDto.cs
new file mode 100644
index 0000000..80b2330
--- /dev/null
+++ b/Azaion.Annotator/DTO/ThumbnailDto.cs
@@ -0,0 +1,57 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Windows.Media.Imaging;
+
+namespace Azaion.Annotator.DTO;
+
+public class ThumbnailDto : INotifyPropertyChanged
+{
+ public string ThumbnailPath { get; set; }
+ public string ImagePath { get; set; }
+ public string LabelPath { get; set; }
+
+ private BitmapImage? _image;
+ public BitmapImage? Image
+ {
+ get
+ {
+ if (_image == null)
+ LoadImageAsync();
+ return _image;
+ }
+ set
+ {
+ _image = value;
+ OnPropertyChanged();
+ }
+ }
+
+ private async void LoadImageAsync()
+ {
+ await Task.Run(() =>
+ {
+ try
+ {
+ var bitmap = new BitmapImage();
+ bitmap.BeginInit();
+ bitmap.UriSource = new Uri(ThumbnailPath);
+ bitmap.CacheOption = BitmapCacheOption.OnLoad;
+ bitmap.DecodePixelWidth = 480;
+ bitmap.DecodePixelHeight = 270;
+ bitmap.EndInit();
+ bitmap.Freeze(); // Freeze to make it cross-thread accessible
+ Image = bitmap;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ }
+ });
+ }
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+ protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+}
\ No newline at end of file
diff --git a/Azaion.Annotator/DatasetExplorer.xaml b/Azaion.Annotator/DatasetExplorer.xaml
index 341fc0e..b28a222 100644
--- a/Azaion.Annotator/DatasetExplorer.xaml
+++ b/Azaion.Annotator/DatasetExplorer.xaml
@@ -3,9 +3,18 @@
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:vwp="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
xmlns:local="clr-namespace:Azaion.Annotator"
+ xmlns:dto="clr-namespace:Azaion.Annotator.DTO"
mc:Ignorable="d"
- Title="Браузер анотацій" Height="450" Width="800">
+ Title="Браузер анотацій" Height="900" Width="1200">
+
+
+
+
+
+
+
+
+
+
diff --git a/Azaion.Annotator/DatasetExplorer.xaml.cs b/Azaion.Annotator/DatasetExplorer.xaml.cs
index 615836e..0b00114 100644
--- a/Azaion.Annotator/DatasetExplorer.xaml.cs
+++ b/Azaion.Annotator/DatasetExplorer.xaml.cs
@@ -1,30 +1,55 @@
-using System.Windows;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Windows;
+using Azaion.Annotator.DTO;
namespace Azaion.Annotator;
-public partial class DatasetExplorer : Window
+public partial class DatasetExplorer
{
- private CancellationTokenSource _cancellationTokenSource;
+ private readonly Config _config;
- public DatasetExplorer(IGalleryManager galleryManager)
+ public ObservableCollection ThumbnailsDtos { get; set; } = new();
+
+ public DatasetExplorer(Config config)
{
- _cancellationTokenSource = new CancellationTokenSource();
+ _config = config;
InitializeComponent();
- Loaded += (sender, args) =>
- {
- _ = Task.Run(async () =>
- {
- while (!_cancellationTokenSource.Token.IsCancellationRequested)
- {
- await galleryManager.RefreshThumbnails(_cancellationTokenSource.Token);
- await Task.Delay(30000, _cancellationTokenSource.Token);
- }
- });
- };
+ DataContext = this;
+ Loaded += async (sender, args) => await LoadThumbnails();
- Closing += (sender, args) => _cancellationTokenSource.Cancel();
+ Closing += (sender, args) =>
+ {
+ args.Cancel = true;
+ Visibility = Visibility.Hidden;
+ };
}
+ private async Task LoadThumbnails()
+ {
+ if (!Directory.Exists(_config.ThumbnailsDirectory))
+ return;
+ var thumbnails = Directory.GetFiles(_config.ThumbnailsDirectory, "*.jpg");
+
+ 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)
+ {
+ imageName = $"{imageName}.{imageFormat}";
+ if (File.Exists(imageName))
+ break;
+ }
+
+ ThumbnailsDtos.Add(new ThumbnailDto
+ {
+ ThumbnailPath = thumbnail,
+ ImagePath = imageName,
+ LabelPath = Path.Combine(_config.LabelsDirectory, $"{name}.txt"),
+ });
+ }
+ }
}
\ No newline at end of file
diff --git a/Azaion.Annotator/GalleryManager.cs b/Azaion.Annotator/GalleryManager.cs
index 2cc5847..d42be1b 100644
--- a/Azaion.Annotator/GalleryManager.cs
+++ b/Azaion.Annotator/GalleryManager.cs
@@ -3,31 +3,25 @@ using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using Azaion.Annotator.DTO;
+using Microsoft.Extensions.Logging;
using Color = System.Drawing.Color;
using Size = System.Windows.Size;
namespace Azaion.Annotator;
-public class GalleryManager : IGalleryManager
+public class GalleryManager(Config config, ILogger logger) : IGalleryManager
{
- private readonly Config _config;
-
public int ThumbnailsCount { get; set; }
public int ImagesCount { get; set; }
- public GalleryManager(Config config)
+ public async Task RefreshThumbnails()
{
- _config = config;
- }
-
- public async Task RefreshThumbnails(CancellationToken cancellationToken)
- {
- var dir = new DirectoryInfo(_config.ThumbnailsDirectory);
+ var dir = new DirectoryInfo(config.ThumbnailsDirectory);
if (!dir.Exists)
- Directory.CreateDirectory(_config.ThumbnailsDirectory);
+ Directory.CreateDirectory(config.ThumbnailsDirectory);
var prefixLen = Config.ThumbnailPrefix.Length;
- var thumbnailsDir = new DirectoryInfo(_config.ThumbnailsDirectory);
+ var thumbnailsDir = new DirectoryInfo(config.ThumbnailsDirectory);
var thumbnails = thumbnailsDir.GetFiles()
.Select(x => Path.GetFileNameWithoutExtension(x.Name)[..^prefixLen])
@@ -36,7 +30,7 @@ public class GalleryManager : IGalleryManager
.ToHashSet();
ThumbnailsCount = thumbnails.Count;
- var files = new DirectoryInfo(_config.ImagesDirectory).GetFiles();
+ var files = new DirectoryInfo(config.ImagesDirectory).GetFiles();
ImagesCount = files.Length;
foreach (var img in files)
@@ -45,21 +39,28 @@ public class GalleryManager : IGalleryManager
if (thumbnails.Contains(imgName))
continue;
- var bitmap = await GenerateThumbnail(img, cancellationToken);
- var thumbnailName = Path.Combine(thumbnailsDir.FullName, $"{imgName}{Config.ThumbnailPrefix}.jpg");
- bitmap.Save(thumbnailName, ImageFormat.Jpeg);
+ try
+ {
+ var bitmap = await GenerateThumbnail(img);
+ var thumbnailName = Path.Combine(thumbnailsDir.FullName, $"{imgName}{Config.ThumbnailPrefix}.jpg");
+ bitmap.Save(thumbnailName, ImageFormat.Jpeg);
+ }
+ catch (Exception e)
+ {
+ logger.LogError(e, $"Failed to generate thumbnail for {img.Name}");
+ }
ThumbnailsCount++;
}
}
- private async Task GenerateThumbnail(FileInfo img, CancellationToken cancellationToken)
+ private async Task GenerateThumbnail(FileInfo img)
{
- var width = (int)_config.ThumbnailConfig.Size.Width;
- var height = (int)_config.ThumbnailConfig.Size.Height;
+ var width = (int)config.ThumbnailConfig.Size.Width;
+ var height = (int)config.ThumbnailConfig.Size.Height;
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);
@@ -71,39 +72,43 @@ public class GalleryManager : IGalleryManager
g.InterpolationMode = InterpolationMode.Default;
var size = new Size(originalImage.Width, originalImage.Height);
- var labels = (await YoloLabel.ReadFromFile(labelName, cancellationToken))
+ var labels = (await YoloLabel.ReadFromFile(labelName))
.Select(x => new CanvasLabel(x, size, size))
.ToList();
var thumbWhRatio = width / (float)height;
- var border = _config.ThumbnailConfig.Border;
+ var border = config.ThumbnailConfig.Border;
- var labelsMinX = labels.Any() ? labels.Min(x => x.X);
- var labelsMaxX = labels.Max(x => x.X + x.Width);
-
- var labelsMinY = labels.Min(x => x.Y);
- var labelsMaxY = labels.Max(x => x.Y + x.Height);
-
- var labelsHeight = labelsMaxY - labelsMinY + 2 * border;
- var labelsWidth = labelsMaxX - labelsMinX + 2 * border;
-
- var frameHeight = 0.0;
- var frameWidth = 0.0;
var frameX = 0.0;
var frameY = 0.0;
- if (labelsWidth / labelsHeight > thumbWhRatio)
+ var frameHeight = size.Height;
+ var frameWidth = size.Width;
+
+ if (labels.Any())
{
- frameWidth = labelsWidth;
- frameHeight = Math.Min(labelsWidth / thumbWhRatio, size.Height);
- frameX = Math.Max(0, labelsMinX - border);
- frameY = Math.Max(0, 0.5 * (labelsMinY + labelsMaxY - frameHeight) - border);
- }
- else
- {
- frameHeight = labelsHeight;
- frameWidth = Math.Min(labelsHeight * thumbWhRatio, size.Width);
- frameY = Math.Max(0, labelsMinY - border);
- frameX = Math.Max(0, 0.5 * (labelsMinX + labelsMaxX - frameWidth) - border);
+ var labelsMinX = labels.Min(x => x.X);
+ var labelsMaxX = labels.Max(x => x.X + x.Width);
+
+ var labelsMinY = labels.Min(x => x.Y);
+ var labelsMaxY = labels.Max(x => x.Y + x.Height);
+
+ var labelsHeight = labelsMaxY - labelsMinY + 2 * border;
+ var labelsWidth = labelsMaxX - labelsMinX + 2 * border;
+
+ if (labelsWidth / labelsHeight > thumbWhRatio)
+ {
+ frameWidth = labelsWidth;
+ frameHeight = Math.Min(labelsWidth / thumbWhRatio, size.Height);
+ frameX = Math.Max(0, labelsMinX - border);
+ frameY = Math.Max(0, 0.5 * (labelsMinY + labelsMaxY - frameHeight) - border);
+ }
+ else
+ {
+ frameHeight = labelsHeight;
+ frameWidth = Math.Min(labelsHeight * thumbWhRatio, size.Width);
+ frameY = Math.Max(0, labelsMinY - border);
+ frameX = Math.Max(0, 0.5 * (labelsMinX + labelsMaxX - frameWidth) - border);
+ }
}
var scale = frameHeight / height;
@@ -111,7 +116,7 @@ public class GalleryManager : IGalleryManager
foreach (var label in labels)
{
- var color = _config.AnnotationClassesDict[label.ClassNumber].Color;
+ var color = config.AnnotationClassesDict[label.ClassNumber].Color;
var brush = new SolidBrush(Color.FromArgb(color.A, color.R, color.G, color.B));
var rectangle = new RectangleF((float)((label.X - frameX) / scale), (float)((label.Y - frameY) / scale), (float)(label.Width / scale), (float)(label.Height / scale));
@@ -125,5 +130,5 @@ public interface IGalleryManager
{
int ThumbnailsCount { get; set; }
int ImagesCount { get; set; }
- Task RefreshThumbnails(CancellationToken cancellationToken);
+ Task RefreshThumbnails();
}
\ No newline at end of file
diff --git a/Azaion.Annotator/MainWindow.xaml.cs b/Azaion.Annotator/MainWindow.xaml.cs
index 4136851..b1116b7 100644
--- a/Azaion.Annotator/MainWindow.xaml.cs
+++ b/Azaion.Annotator/MainWindow.xaml.cs
@@ -27,6 +27,7 @@ public partial class MainWindow
private readonly IConfigRepository _configRepository;
private readonly HelpWindow _helpWindow;
private readonly ILogger _logger;
+ private readonly IGalleryManager _galleryManager;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
public ObservableCollection AnnotationClasses { get; set; } = new();
@@ -48,7 +49,8 @@ public partial class MainWindow
IConfigRepository configRepository,
HelpWindow helpWindow,
DatasetExplorer datasetExplorer,
- ILogger logger)
+ ILogger logger,
+ IGalleryManager galleryManager)
{
InitializeComponent();
_libVLC = libVLC;
@@ -60,6 +62,7 @@ public partial class MainWindow
_helpWindow = helpWindow;
_datasetExplorer = datasetExplorer;
_logger = logger;
+ _galleryManager = galleryManager;
VideoView.Loaded += VideoView_Loaded;
Closed += OnFormClosed;
@@ -70,6 +73,15 @@ public partial class MainWindow
Core.Initialize();
InitControls();
+ _ = Task.Run(async () =>
+ {
+ while (true)
+ {
+ await _galleryManager.RefreshThumbnails();
+ await Task.Delay(30000);
+ }
+ });
+
_suspendLayout = true;
Left = _config.WindowLocation.X;
@@ -222,7 +234,7 @@ public partial class MainWindow
var name = Path.GetFileNameWithoutExtension(file.Name);
var time = _formState.GetTime(name)!.Value;
- await AddAnnotation(time, await YoloLabel.ReadFromFile(file.FullName, cancellationToken));
+ await AddAnnotation(time, await YoloLabel.ReadFromFile(file.FullName));
}
}