using System.Collections.ObjectModel; using System.Windows; using System.Windows.Input; using System.Windows.Media; using Azaion.Common.Database; using Azaion.Common.DTO; using Azaion.Common.DTO.Config; using Azaion.Common.Events; using Azaion.Common.Services; using LinqToDB; using MediatR; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using ScottPlot; using Color = ScottPlot.Color; namespace Azaion.Dataset; public partial class DatasetExplorer { private readonly ILogger _logger; private readonly AnnotationConfig _annotationConfig; private readonly DirectoriesConfig _directoriesConfig; private Dictionary> _annotationsDict; private readonly CancellationTokenSource _cts = new(); public ObservableCollection AllDetectionClasses { get; set; } = new(); public ObservableCollection SelectedAnnotations { get; set; } = new(); public readonly Dictionary SelectedAnnotationDict = new(); private int _tempSelectedClassIdx = 0; private readonly IGalleryService _galleryService; private readonly IDbFactory _dbFactory; private readonly IMediator _mediator; public bool ThumbnailLoading { get; set; } public AnnotationThumbnail? CurrentAnnotation { get; set; } public DatasetExplorer( IOptions directoriesConfig, IOptions annotationConfig, ILogger logger, IGalleryService galleryService, FormState formState, IDbFactory dbFactory, IMediator mediator) { _directoriesConfig = directoriesConfig.Value; _annotationConfig = annotationConfig.Value; _logger = logger; _galleryService = galleryService; _dbFactory = dbFactory; _mediator = mediator; InitializeComponent(); Loaded += OnLoaded; Activated += (_, _) => formState.ActiveWindow = WindowEnum.DatasetExplorer; ThumbnailsView.KeyDown += async (sender, args) => { switch (args.Key) { case Key.Delete: await DeleteAnnotations(); break; case Key.Enter: await EditAnnotation(); break; } }; ThumbnailsView.MouseDoubleClick += async (_, _) => await EditAnnotation(); ThumbnailsView.SelectionChanged += (_, _) => { StatusText.Text = $"Обрано: {ThumbnailsView.SelectedItems.Count} | {ThumbnailsView.SelectedIndex} / {SelectedAnnotations.Count}"; ValidateBtn.Visibility = ThumbnailsView.SelectedItems.Cast().Any(x => x.IsSeed) ? Visibility.Visible : Visibility.Hidden; }; ExplorerEditor.GetTimeFunc = () => CurrentAnnotation!.Annotation.Time; _galleryService.ThumbnailsUpdate += thumbnailsPercentage => { Dispatcher.Invoke(() => RefreshThumbBar.Value = thumbnailsPercentage); }; Closing += (_, _) => _cts.Cancel(); } private async void OnLoaded(object sender, RoutedEventArgs e) { AllDetectionClasses = new ObservableCollection( new List { new() {Id = -1, Name = "All", ShortName = "All"}} .Concat(_annotationConfig.AnnotationClasses)); LvClasses.ItemsSource = AllDetectionClasses; LvClasses.MouseUp += async (_, _) => { var selectedClass = (DetectionClass)LvClasses.SelectedItem; ExplorerEditor.CurrentAnnClass = selectedClass; _annotationConfig.LastSelectedExplorerClass = selectedClass.Id; if (Switcher.SelectedIndex == 0) await ReloadThumbnails(); else foreach (var ann in ExplorerEditor.CurrentDetections.Where(x => x.IsSelected)) ann.DetectionClass = selectedClass; }; LvClasses.SelectionChanged += (_, _) => { if (Switcher.SelectedIndex != 1) return; var selectedClass = (DetectionClass)LvClasses.SelectedItem; if (selectedClass == null) return; ExplorerEditor.CurrentAnnClass = selectedClass; foreach (var ann in ExplorerEditor.CurrentDetections.Where(x => x.IsSelected)) ann.DetectionClass = selectedClass; }; LvClasses.SelectedIndex = _annotationConfig.LastSelectedExplorerClass ?? 0; ExplorerEditor.CurrentAnnClass = (DetectionClass)LvClasses.SelectedItem; await _dbFactory.Run(async db => { var allAnnotations = await db.Annotations .LoadWith(x => x.Detections) .OrderBy(x => x.AnnotationStatus) .ThenByDescending(x => x.CreatedDate) .ToListAsync(); _annotationsDict = AllDetectionClasses.ToDictionary(x => x.Id, _ => new List()); foreach (var annotation in allAnnotations) AddAnnotationToDict(annotation); }); await ReloadThumbnails(); await LoadClassDistribution(); DataContext = this; } public void AddAnnotationToDict(Annotation annotation) { foreach (var c in annotation.Classes) _annotationsDict[c].Add(annotation); _annotationsDict[-1].Add(annotation); } private async Task LoadClassDistribution() { var data = _annotationsDict .Where(x => x.Key != -1) .Select(gr => new { gr.Key, _annotationConfig.DetectionClassesDict[gr.Key].Name, _annotationConfig.DetectionClassesDict[gr.Key].Color, ClassCount = gr.Value.Count }) .ToList(); var foregroundColor = Color.FromColor(System.Drawing.Color.Black); var bars = data.Select(x => new Bar { Orientation = Orientation.Horizontal, Position = -1.5 * x.Key + 1, Label = x.ClassCount > 200 ? x.ClassCount.ToString() : "", FillColor = new Color(x.Color.R, x.Color.G, x.Color.B, x.Color.A), Value = x.ClassCount, CenterLabel = true, LabelOffset = 10 }).ToList(); ClassDistribution.Plot.Add.Bars(bars); foreach (var x in data) { var label = ClassDistribution.Plot.Add.Text(x.Name, 50, -1.5 * x.Key + 1.1); label.LabelFontColor = foregroundColor; label.LabelFontSize = 18; } ClassDistribution.Plot.Axes.AutoScale(); ClassDistribution.Plot.HideAxesAndGrid(); ClassDistribution.Plot.FigureBackground.Color = new("#888888"); ClassDistribution.Refresh(); } private async void RefreshThumbnailsBtnClick(object sender, RoutedEventArgs e) { RefreshThumbnailsButtonItem.Visibility = Visibility.Hidden; RefreshProgressBarItem.Visibility = Visibility.Visible; var result = MessageBox.Show($"Видалити всі іконки і згенерувати нову базу іконок в {_directoriesConfig.ThumbnailsDirectory}?", "Підтвердження оновлення іконок", MessageBoxButton.YesNo, MessageBoxImage.Question); if (result != MessageBoxResult.Yes) return; await _galleryService.ClearThumbnails(); await _galleryService.RefreshThumbnails(); RefreshProgressBarItem.Visibility = Visibility.Hidden; RefreshThumbnailsButtonItem.Visibility = Visibility.Visible; } private async Task EditAnnotation() { try { ThumbnailLoading = true; if (ThumbnailsView.SelectedItem == null) return; CurrentAnnotation = (ThumbnailsView.SelectedItem as AnnotationThumbnail)!; var ann = CurrentAnnotation.Annotation; ExplorerEditor.Background = new ImageBrush { ImageSource = await ann.ImagePath.OpenImage() }; SwitchTab(toEditor: true); var time = ann.Time; ExplorerEditor.RemoveAllAnns(); foreach (var deetection in ann.Detections) { var annClass = _annotationConfig.DetectionClassesDict[deetection.ClassNumber]; var canvasLabel = new CanvasLabel(deetection, ExplorerEditor.RenderSize, ExplorerEditor.RenderSize); ExplorerEditor.CreateDetectionControl(annClass, time, canvasLabel); } ThumbnailLoading = false; } catch (Exception e) { _logger.LogError(e, e.Message); throw; } finally { ThumbnailLoading = false; } } public void SwitchTab(bool toEditor) { if (toEditor) { AnnotationsTab.Visibility = Visibility.Collapsed; EditorTab.Visibility = Visibility.Visible; _tempSelectedClassIdx = LvClasses.SelectedIndex; LvClasses.ItemsSource = _annotationConfig.AnnotationClasses; Switcher.SelectedIndex = 1; LvClasses.SelectedIndex = Math.Max(0, _tempSelectedClassIdx - 1); } else { AnnotationsTab.Visibility = Visibility.Visible; EditorTab.Visibility = Visibility.Collapsed; LvClasses.ItemsSource = AllDetectionClasses; LvClasses.SelectedIndex = _tempSelectedClassIdx; Switcher.SelectedIndex = 0; } } private async Task DeleteAnnotations() { var tempSelected = ThumbnailsView.SelectedIndex; var result = MessageBox.Show("Чи дійсно видалити аннотації?","Підтвердження видалення", MessageBoxButton.YesNo, MessageBoxImage.Question); if (result != MessageBoxResult.Yes) return; var annotations = ThumbnailsView.SelectedItems.Cast().Select(x => x.Annotation) .ToList(); await _mediator.Publish(new AnnotationsDeletedEvent(annotations)); ThumbnailsView.SelectedIndex = Math.Min(SelectedAnnotations.Count, tempSelected); } private async Task ReloadThumbnails() { SelectedAnnotations.Clear(); foreach (var ann in _annotationsDict[ExplorerEditor.CurrentAnnClass.Id]) { var annThumb = new AnnotationThumbnail(ann); SelectedAnnotations.Add(annThumb); SelectedAnnotationDict.Add(annThumb.Annotation.Name, annThumb); } } private async void ValidateAnnotationsClick(object sender, RoutedEventArgs e) { var result = MessageBox.Show("Підтверджуєте валідність обраних аннотацій?","Підтвердження валідності", MessageBoxButton.OKCancel, MessageBoxImage.Question); if (result != MessageBoxResult.OK) return; try { await _mediator.Publish(new DatasetExplorerControlEvent(PlaybackControlEnum.ValidateAnnotations), _cts.Token); } catch (Exception ex) { _logger.LogError(ex, ex.Message); } } }