using System.Collections.ObjectModel; using System.IO; using System.Windows; using System.Windows.Input; using System.Windows.Media; 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(); private int _tempSelectedClassIdx = 0; private readonly string _thumbnailsCacheFile; private readonly IConfigRepository _configRepository; private readonly FormState _formState; private static Dictionary> LabelsCache { get; set; } = new(); public bool ThumbnailLoading { get; set; } public ThumbnailDto? CurrentThumbnail { get; set; } public DatasetExplorer( Config config, ILogger logger, IConfigRepository configRepository, FormState formState, IGalleryManager galleryManager) { _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 (_, _) => { AllAnnotationClasses = new ObservableCollection( new List { new(-1, "All") } .Concat(_config.AnnotationClasses)); LvClasses.ItemsSource = AllAnnotationClasses; LvClasses.MouseUp += async (_, _) => { var selectedClass = (AnnotationClass)LvClasses.SelectedItem; ExplorerEditor.CurrentAnnClass = selectedClass; config.LastSelectedExplorerClass = selectedClass.Id; if (Switcher.SelectedIndex == 0) await ReloadThumbnails(); else foreach (var ann in ExplorerEditor.CurrentAnns.Where(x => x.IsSelected)) ann.AnnotationClass = selectedClass; }; LvClasses.SelectionChanged += (_, _) => { if (Switcher.SelectedIndex != 1) return; var selectedClass = (AnnotationClass)LvClasses.SelectedItem; if (selectedClass == null) return; ExplorerEditor.CurrentAnnClass = selectedClass; foreach (var ann in ExplorerEditor.CurrentAnns.Where(x => x.IsSelected)) ann.AnnotationClass = selectedClass; }; LvClasses.SelectedIndex = config.LastSelectedExplorerClass ?? 0; ExplorerEditor.CurrentAnnClass = (AnnotationClass)LvClasses.SelectedItem; await ReloadThumbnails(); SizeChanged += async (_, _) => await SaveUserSettings(); LocationChanged += async (_, _) => await SaveUserSettings(); StateChanged += async (_, _) => await SaveUserSettings(); RefreshThumbBar.Value = galleryManager.ThumbnailsPercentage; }; Closing += (sender, args) => { args.Cancel = true; Visibility = Visibility.Hidden; }; ThumbnailsView.KeyDown += async (sender, args) => { switch (args.Key) { case Key.Delete: DeleteAnnotations(); break; case Key.Enter: await EditAnnotation(); break; } }; ThumbnailsView.MouseDoubleClick += async (_, _) => await EditAnnotation(); ThumbnailsView.SelectionChanged += (_, _) => { StatusText.Text = $"Обрано: {ThumbnailsView.SelectedItems.Count} | {ThumbnailsView.SelectedIndex} / {ThumbnailsDtos.Count}"; }; Activated += (_, _) => { _formState.ActiveWindow = WindowsEnum.DatasetExplorer; }; Switcher.SelectionChanged += (sender, args) => { if (Switcher.SelectedIndex == 1) { //Editor _tempSelectedClassIdx = LvClasses.SelectedIndex; LvClasses.ItemsSource = _config.AnnotationClasses; } else { //Explorer LvClasses.ItemsSource = AllAnnotationClasses; LvClasses.SelectedIndex = _tempSelectedClassIdx; ExplorerEditor.Background = null; } }; ExplorerEditor.GetTimeFunc = () => _formState.GetTime(CurrentThumbnail!.ImagePath); galleryManager.ThumbnailsUpdate += thumbnailsPercentage => { Dispatcher.Invoke(() => RefreshThumbBar.Value = thumbnailsPercentage); }; } private async Task EditAnnotation() { try { ThumbnailLoading = true; if (ThumbnailsView.SelectedItem == null) return; var dto = (ThumbnailsView.SelectedItem as ThumbnailDto)!; CurrentThumbnail = dto; ExplorerEditor.Background = new ImageBrush { ImageSource = await dto.ImagePath.OpenImage() }; Switcher.SelectedIndex = 1; LvClasses.SelectedIndex = 1; var time = _formState.GetTime(dto.ImagePath); ExplorerEditor.RemoveAllAnns(); foreach (var ann in await YoloLabel.ReadFromFile(dto.LabelPath)) { var annClass = _config.AnnotationClasses[ann.ClassNumber]; var annInfo = new CanvasLabel(ann, ExplorerEditor.RenderSize, ExplorerEditor.RenderSize); ExplorerEditor.CreateAnnotation(annClass, time, annInfo); } ThumbnailLoading = false; } catch (Exception e) { _logger.LogError(e, e.Message); throw; } finally { ThumbnailLoading = false; } } private async Task SaveUserSettings() { _config.DatasetExplorerConfig = this.GetConfig(); await ThrottleExt.Throttle(() => { _configRepository.Save(_config); return Task.CompletedTask; }, TimeSpan.FromSeconds(5)); } private void DeleteAnnotations() { var tempSelected = ThumbnailsView.SelectedIndex; 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); } ThumbnailsView.SelectedIndex = Math.Min(ThumbnailsDtos.Count, tempSelected); } private async Task ReloadThumbnails() { LoadingAnnsCaption.Visibility = Visibility.Visible; LoadingAnnsBar.Visibility = Visibility.Visible; if (!Directory.Exists(_config.ThumbnailsDirectory)) return; ThumbnailsDtos.Clear(); var thumbnails = Directory.GetFiles(_config.ThumbnailsDirectory, "*.jpg"); var thumbNum = 0; foreach (var thumbnail in thumbnails) { await AddThumbnail(thumbnail); if (thumbNum % 1000 == 0) { await File.WriteAllTextAsync(_thumbnailsCacheFile, JsonConvert.SerializeObject(LabelsCache)); LoadingAnnsBar.Value = thumbNum * 100.0 / thumbnails.Length; } thumbNum++; } LoadingAnnsCaption.Visibility = Visibility.Collapsed; LoadingAnnsBar.Visibility = Visibility.Collapsed; await File.WriteAllTextAsync(_thumbnailsCacheFile, JsonConvert.SerializeObject(LabelsCache)); } private async Task AddThumbnail(string thumbnail) { try { var name = Path.GetFileNameWithoutExtension(thumbnail)[..^Config.ThumbnailPrefix.Length]; var imageName = Path.Combine(_config.ImagesDirectory, name); foreach (var f in _config.ImageFormats) { var curName = $"{imageName}.{f}"; if (File.Exists(curName)) { imageName = curName; break; } } var labelPath = Path.Combine(_config.LabelsDirectory, $"{name}.txt"); if (!LabelsCache.TryGetValue(name, out var classes)) { if (!File.Exists(labelPath)) { var imageExists = File.Exists(imageName); if (!imageExists) { _logger.LogError($"No label {labelPath} found ! Image {imageName} not found, removing thumbnail {thumbnail}"); File.Delete(thumbnail); } else { _logger.LogError($"No label {labelPath} found! But Image {imageName} exists! Image moved to {_config.UnknownImages} directory!"); File.Move(imageName, Path.Combine(_config.UnknownImages, imageName)); } 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 = labelPath }); } } catch (Exception e) { _logger.LogError(e, e.Message); } } }