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.Extensions; using Azaion.Common.Services; using LinqToDB; using MediatR; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Azaion.Dataset; public partial class DatasetExplorer { private readonly ILogger _logger; private readonly AnnotationConfig _annotationConfig; private readonly DirectoriesConfig _directoriesConfig; private readonly Dictionary> _annotationsDict; private readonly CancellationTokenSource _cts = new(); public List AllDetectionClasses { get; set; } 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 readonly List AnnotationsClasses; private IAzaionApi _azaionApi; 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, IAzaionApi azaionApi) { InitializeComponent(); _directoriesConfig = directoriesConfig.Value; _annotationConfig = annotationConfig.Value; _logger = logger; _galleryService = galleryService; _dbFactory = dbFactory; _mediator = mediator; _azaionApi = azaionApi; var photoModes = Enum.GetValues(typeof(PhotoMode)).Cast().ToList(); _annotationsDict = _annotationConfig.DetectionClasses.SelectMany(cls => photoModes.Select(mode => (int)mode + cls.Id)) .ToDictionary(x => x, _ => new Dictionary()); _annotationsDict.Add(-1, []); AnnotationsClasses = annotationConfig.Value.DetectionClasses; 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(ThumbnailsView.SelectedIndex); break; } }; ThumbnailsView.MouseDoubleClick += async (_, _) => await EditAnnotation(ThumbnailsView.SelectedIndex); 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(); AllDetectionClasses = new List( new List { new() {Id = -1, Name = "All", ShortName = "All"}} .Concat(_annotationConfig.DetectionClasses)); LvClasses.Init(AllDetectionClasses); } private async void OnLoaded(object sender, RoutedEventArgs e) { LvClasses.DetectionClassChanged += async (_, args) => { ExplorerEditor.CurrentAnnClass = args.DetectionClass; if (Switcher.SelectedIndex == 0) await ReloadThumbnails(); else foreach (var ann in ExplorerEditor.CurrentDetections.Where(x => x.IsSelected)) ann.DetectionClass = args.DetectionClass; }; ExplorerEditor.CurrentAnnClass = LvClasses.CurrentDetectionClass ?? _annotationConfig.DetectionClasses.First(); var allAnnotations = await _dbFactory.Run(async db => await db.Annotations.LoadWith(x => x.Detections) .OrderBy(x => x.AnnotationStatus) .ThenByDescending(x => x.CreatedDate) .ToListAsync()); foreach (var annotation in allAnnotations) AddAnnotationToDict(annotation); await ReloadThumbnails(); LoadClassDistribution(); DataContext = this; } public void AddAnnotationToDict(Annotation annotation) { foreach (var c in annotation.Classes) _annotationsDict[c][annotation.Name] = annotation; _annotationsDict[-1][annotation.Name] = annotation; } private void LoadClassDistribution() { var data = _annotationsDict .Where(x => x.Key != -1) .OrderBy(x => x.Key) .Select(gr => new ClusterDistribution { Label = $"{_annotationConfig.DetectionClassesDict[gr.Key].UIName}: {gr.Value.Count}", Color = _annotationConfig.DetectionClassesDict[gr.Key].Color, ClassCount = gr.Value.Count }) .Where(x => x.ClassCount > 0) .ToList(); var maxClassCount = Math.Max(1, data.Max(x => x.ClassCount)); foreach (var cl in data) { cl.Color = cl.Color.CreateTransparent(150); cl.BarWidth = Math.Clamp(cl.ClassCount / (double)maxClassCount, 0, 1); } ClassDistributionPlot.Items = data; } 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; } public async Task EditAnnotation(int index) { try { ThumbnailLoading = true; if (index == -1) return; CurrentAnnotation = (ThumbnailsView.Items[index] as AnnotationThumbnail)!; ThumbnailsView.SelectedIndex = index; var ann = CurrentAnnotation.Annotation; ExplorerEditor.Background = new ImageBrush { ImageSource = await ann.ImagePath.OpenImage() }; SwitchTab(toEditor: true); var time = ann.Time; ExplorerEditor.RemoveAllAnns(); ExplorerEditor.CreateDetections(time, ann.Detections, _annotationConfig.DetectionClasses, ExplorerEditor.RenderSize); } catch (Exception e) { _logger.LogError(e, e.Message); throw; } finally { _ = Task.Run(async () => { await Task.Delay(100); ThumbnailLoading = false; }); } } public void SwitchTab(bool toEditor) { if (toEditor) { AnnotationsTab.Visibility = Visibility.Collapsed; EditorTab.Visibility = Visibility.Visible; _tempSelectedClassIdx = LvClasses.CurrentClassNumber; LvClasses.DetectionDataGrid.ItemsSource = _annotationConfig.DetectionClasses; Switcher.SelectedIndex = 1; LvClasses.SelectNum(Math.Max(0, _tempSelectedClassIdx - 1)); } else { AnnotationsTab.Visibility = Visibility.Visible; EditorTab.Visibility = Visibility.Collapsed; LvClasses.DetectionDataGrid.ItemsSource = AllDetectionClasses; LvClasses.SelectNum(_tempSelectedClassIdx); Switcher.SelectedIndex = 0; } } public async Task DeleteAnnotations() { var tempSelected = ThumbnailsView.SelectedIndex; var result = MessageBox.Show("Чи дійсно видалити аннотації?","Підтвердження видалення", MessageBoxButton.YesNo, MessageBoxImage.Question); if (result != MessageBoxResult.Yes) return; var annotationNames = ThumbnailsView.SelectedItems.Cast().Select(x => x.Annotation.Name).ToList(); await _mediator.Publish(new AnnotationsDeletedEvent(annotationNames)); ThumbnailsView.SelectedIndex = Math.Min(SelectedAnnotations.Count, tempSelected); } private async Task ReloadThumbnails() { SelectedAnnotations.Clear(); SelectedAnnotationDict.Clear(); var annThumbnails = _annotationsDict[ExplorerEditor.CurrentAnnClass.YoloId] .Select(x => new AnnotationThumbnail(x.Value, _azaionApi.CurrentUser.Role.IsValidator())) .OrderBy(x => !x.IsSeed) .ThenByDescending(x =>x.Annotation.CreatedDate); //var dict = annThumbnails.Take(20).ToDictionary(x => x.Annotation.Name, x => x.IsSeed); foreach (var thumb in annThumbnails) { SelectedAnnotations.Add(thumb); SelectedAnnotationDict.Add(thumb.Annotation.Name, thumb); } await Task.CompletedTask; } 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); } } }