add editor, fix some bugs

WIP
This commit is contained in:
Alex Bezdieniezhnykh
2024-09-10 17:10:54 +03:00
parent b4bedb7520
commit 52371ace3a
16 changed files with 498 additions and 148 deletions
+192 -10
View File
@@ -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<DatasetExplorer> _logger;
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;
_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();
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) =>
{
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
});
}
}
}