using System.Collections.Specialized; using System.Collections.ObjectModel; using Microsoft.Extensions.Logging; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Threading; using GSS2.Core.Hardware; using GSS2.Core.Extentions; using GSS2.Core.Analysis.AmineContent.Database.Calibration; using Microsoft.EntityFrameworkCore; namespace GSS2.UI.Core.ViewModels; public partial class AmineContentSeparatorCalibrationViewModel : ViewModelBase { private readonly ILogger _logger; private readonly AmineContentCalibrationContext _context; private readonly IlluminatorService _illuminatorService; private readonly CameraService _cameraService; private readonly ImageStorageService _imageStorageService; private CancellationTokenSource? _captureImagesTaskCts = null; [ObservableProperty] private ObservableCollection _records; [ObservableProperty] private SeparatorRecord _currentRecord; [ObservableProperty] private bool _isMenuOpen = false; [ObservableProperty] private bool _isNewRecord = false; [ObservableProperty] private int _mainSectionsCurrentIndex = 0; [ObservableProperty] private bool _mainSectionCanScrollBackward = false; [ObservableProperty] private bool _mainSectionCanScrollForward = true; [ObservableProperty] private bool _mainSectionIsScrolling = true; [ObservableProperty] private Task? _captureImagesTask = null; [ObservableProperty] private ObservableCollection> _images = new(); [ObservableProperty] private bool _imagesIsCapturing = false; [ObservableProperty] private string _imagesCapturingProgress = ""; [ObservableProperty] private int _imagesSectionCurrentIndex = 0; [ObservableProperty] private bool _imagesSectionCanScrollForward = false; [ObservableProperty] private bool _imagesSectionCanScrollBackward = false; [ObservableProperty] private bool _imagesSectionIsScrolling = true; public AmineContentSeparatorCalibrationViewModel(ILogger logger, AmineContentCalibrationContext context, IlluminatorService illuminatorService, CameraService cameraService, ImageStorageService imageStorageService) { _logger = logger; _logger.LogInformation("Initialization"); _context = context; _illuminatorService = illuminatorService; _cameraService = cameraService; _imageStorageService = imageStorageService; Records = new ObservableCollection(_context.SeparatorRecords.Include(r => r.Images)); if (Records.Count() > 0) { CurrentRecord = Records.First(); IsNewRecord = false; } else { CurrentRecord = new SeparatorRecord() { Type = "", Batch = "", Images = new() }; IsNewRecord = true; } _logger.LogInformation("Initialized"); } partial void OnRecordsChanged(ObservableCollection? oldValue, ObservableCollection newValue) { oldValue?.CollectionChanged -= OnRecordsCollectionChanged; newValue?.CollectionChanged += OnRecordsCollectionChanged; } partial void OnCurrentRecordChanged(SeparatorRecord value) { var images = value.Images .ToAsyncEnumerable() .Select(i => new KeyValuePair(_imageStorageService.GetFullPath(i.ImagePath, "separator"), i)) .Where(kv => kv.Key is not null) .Select(kv => new KeyValuePair(new Bitmap(kv.Key!), kv.Value)); Images = new(images.ToBlockingEnumerable()); IsNewRecord = _context.Entry(value).State == EntityState.Detached; } private void OnRecordsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs ea) { Console.WriteLine($"RecordsCollectionChanged {ea.Action}"); } [RelayCommand] private void ToggleMenu() => IsMenuOpen = !IsMenuOpen; [RelayCommand] private void CloseMenu() => IsMenuOpen = false; [RelayCommand] private void CreateRecord() { var record = new SeparatorRecord() { Type = "", Batch = "", Images = new() }; CurrentRecord = record; IsNewRecord = true; } [RelayCommand] private void DeleteRecord() { var deletedRecord = CurrentRecord; var deletedIsNewRecord = IsNewRecord; IsNewRecord = true; CreateRecord(); if (!deletedIsNewRecord) { Records.Remove(deletedRecord); _context.SeparatorRecords.Remove(deletedRecord); _context.SaveChanges(); } } [RelayCommand] private void SaveRecord() { for (int i = 0; i < CurrentRecord.Images.Count(); i++) { var image = CurrentRecord.Images[i]; if (_context.Entry(image).State == EntityState.Detached) { var attachedImage = _context.ImageRecords.Add(image); CurrentRecord.Images.RemoveAt(i); CurrentRecord.Images.Insert(i, attachedImage.Entity); } } if (_context.Entry(CurrentRecord).State == EntityState.Detached) { var attachedRecord = _context.SeparatorRecords.Add(CurrentRecord); CurrentRecord = attachedRecord.Entity; } if (IsNewRecord) Records.Add(CurrentRecord); _context.SaveChanges(); IsNewRecord = false; } [RelayCommand] private void SwitchMainSection() => MainSectionsCurrentIndex = MainSectionsCurrentIndex == 0 ? 1 : 0; [RelayCommand] private void CaptureImage() { if (CaptureImagesTask is not null) return; _captureImagesTaskCts = new CancellationTokenSource(); var cancellationToken = _captureImagesTaskCts.Token; CaptureImagesTask = new Task(async () => { _illuminatorService.TurnOn(); (double visible, double uv365, double uv254)[] intensities = { (0.02500, 0.0, 0.0), (0.01250, 0.0, 0.0), (0.00625, 0.0, 0.0), (0.00000, 1.0, 0.0), (0.00000, 0.0, 1.0), (0.00000, 1.0, 1.0), (0.01250, 1.0, 0.0), (0.01250, 0.0, 1.0), (0.01250, 1.0, 1.0), (0.00625, 1.0, 0.0), (0.00625, 0.0, 1.0), (0.00625, 1.0, 1.0) }; Dispatcher.UIThread.Invoke(() => ImagesIsCapturing = true); try { var imageRecords = new List(); foreach (var (i, intensity) in intensities.Enumerate()) { cancellationToken.ThrowIfCancellationRequested(); Dispatcher.UIThread.Invoke(() => ImagesCapturingProgress = $"Получение изображений: {i + 1} / {intensities.Count()}"); _illuminatorService.SetIntensity(intensity.visible, intensity.uv365, intensity.uv254); OpenCvSharp.Mat? image; image = await _cameraService.CaptureImage(cancellationToken, 5); if (image is null) { _logger.LogError("Cannot get image"); return; } var bytes = image.ImEncode(".png"); string fileName; using (var stream = new MemoryStream(bytes)) fileName = await _imageStorageService.SaveAsync(stream, ".png", "separator", cancellationToken); image.Dispose(); image = null; imageRecords.Add( new ImageRecord() { ImagePath = fileName, VisibleIntensity = intensity.visible, Uv365Intensity = intensity.uv365, Uv254Intensity = intensity.uv254, } ); } _illuminatorService.TurnOff(); var images = imageRecords .ToAsyncEnumerable() .Select(i => new KeyValuePair(_imageStorageService.GetFullPath(i.ImagePath, "separator"), i)) .Where(kv => kv.Key is not null) .Select(kv => new KeyValuePair(new Bitmap(kv.Key!), kv.Value)); Dispatcher.UIThread.Invoke(() => { CurrentRecord.Images.Clear(); CurrentRecord.Images.AddRange(imageRecords); Images.Clear(); Images = new(images.ToBlockingEnumerable()); }); } catch (TaskCanceledException) { } catch (Exception ex) { _logger.LogError(ex, "Exception while image capturing"); } finally { _illuminatorService.TurnOff(); _captureImagesTaskCts = null; Dispatcher.UIThread.Invoke(() => { ImagesIsCapturing = false; ImagesCapturingProgress = ""; CaptureImagesTask = null; }); } }, cancellationToken); CaptureImagesTask.Start(); } [RelayCommand] private void CencelImageCapturing() => _captureImagesTaskCts?.Cancel(); [RelayCommand] private void ImagesSectionPrevious() => ImagesSectionCurrentIndex--; [RelayCommand] private void ImagesSectionNext() => ImagesSectionCurrentIndex++; }