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.Extensions; 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? _selectedRecord; [ObservableProperty] private SeparatorRecord _editingRecord; [ObservableProperty] private bool _isMenuOpen = 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)); SelectedRecord = Records.FirstOrDefault(); if (SelectedRecord is null) EditingRecord = new() { Type = "", Batch = "", Images = new() }; else EditingRecord = CopyRecord(SelectedRecord); _logger.LogInformation("Initialized"); } partial void OnSelectedRecordChanged(SeparatorRecord? value) { if (SelectedRecord is null) EditingRecord = new() { Type = "", Batch = "", Images = new() }; else EditingRecord = CopyRecord(SelectedRecord); } partial void OnEditingRecordChanged(SeparatorRecord value) { var images = value.Images .Select(i => { var path = _imageStorageService.GetFullPath(i.ImagePath, "separator"); if (path is null) return null; return new AsyncImageRecordViewModel(path, i); }) .OfType(); Images = new(images); } [RelayCommand] private void ToggleMenu() => IsMenuOpen = !IsMenuOpen; [RelayCommand] private void CloseMenu() => IsMenuOpen = false; [RelayCommand] private void CreateRecord() => SelectedRecord = null; [RelayCommand] private void DeleteRecord() { var deletedRecord = SelectedRecord; CreateRecord(); if (deletedRecord is not null) { Records.Remove(deletedRecord); _context.SeparatorRecords.Remove(deletedRecord); _context.SaveChanges(); } } [RelayCommand] private void SaveRecord() { if (SelectedRecord is null) { var newEntity = CopyRecord(EditingRecord); for (int i = 0; i < newEntity.Images.Count(); i++) { var image = newEntity.Images[i]; if (_context.Entry(image).State is not EntityState.Detached) continue; var attachedImage = _context.ImageRecords.Add(image); newEntity.Images.RemoveAt(i); newEntity.Images.Insert(i, attachedImage.Entity); } if (_context.Entry(newEntity).State is EntityState.Detached) { var attachedRecord = _context.SeparatorRecords.Add(newEntity); newEntity = attachedRecord.Entity; } _context.SaveChanges(); Records.Add(newEntity); SelectedRecord = newEntity; } else { SelectedRecord.Type = EditingRecord.Type; SelectedRecord.Batch = EditingRecord.Batch; var imagesToRemove = SelectedRecord.Images .Where(e => EditingRecord.Images.All(i => i.Id != e.Id)) .ToList(); foreach (var image in imagesToRemove) SelectedRecord.Images.Remove(image); foreach (var editedImage in EditingRecord.Images) { var existing = EditingRecord.Images .FirstOrDefault(i => i.Id == editedImage.Id); if (existing is null) { SelectedRecord.Images.Add( new ImageRecord { ImagePath = editedImage.ImagePath, VisibleIntensity = editedImage.VisibleIntensity, Uv365Intensity = editedImage.Uv365Intensity, Uv254Intensity = editedImage.Uv254Intensity } ); } else { existing.ImagePath = editedImage.ImagePath; existing.VisibleIntensity = editedImage.VisibleIntensity; existing.Uv365Intensity = editedImage.Uv365Intensity; existing.Uv254Intensity = editedImage.Uv254Intensity; } } for (int i = 0; i < SelectedRecord.Images.Count(); i++) { var image = SelectedRecord.Images[i]; if (_context.Entry(image).State is not EntityState.Detached) continue; var attachedImage = _context.ImageRecords.Add(image); SelectedRecord.Images.RemoveAt(i); SelectedRecord.Images.Insert(i, attachedImage.Entity); } if (_context.Entry(SelectedRecord).State is EntityState.Detached) { var attachedRecord = _context.SeparatorRecords.Add(SelectedRecord); var index = Records.IndexOf(SelectedRecord); Records[index] = attachedRecord.Entity; SelectedRecord = attachedRecord.Entity; } _context.SaveChanges(); } } [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 = Task.Run(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()) { _logger.LogInformation("Capturing images: {} / {} ({};{};{})", i + 1, intensities.Count(), intensity.visible, intensity.uv365, intensity.uv254); 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 .Select(i => { var path = _imageStorageService.GetFullPath(i.ImagePath, "separator"); if (path is null) return null; return new AsyncImageRecordViewModel(path, i); }) .OfType(); Dispatcher.UIThread.Invoke(() => { EditingRecord.Images.Clear(); EditingRecord.Images.AddRange(imageRecords); Images.Clear(); Images = new(images); }); } 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); } [RelayCommand] private void CancelImageCapturing() => _captureImagesTaskCts?.Cancel(); [RelayCommand] private void ImagesSectionPrevious() => ImagesSectionCurrentIndex--; [RelayCommand] private void ImagesSectionNext() => ImagesSectionCurrentIndex++; private static SeparatorRecord CopyRecord(SeparatorRecord source) { return new SeparatorRecord { Id = source.Id, Type = source.Type, Batch = source.Batch, Images = source.Images .Select(i => new ImageRecord { Id = i.Id, ImagePath = i.ImagePath, VisibleIntensity = i.VisibleIntensity, Uv365Intensity = i.Uv365Intensity, Uv254Intensity = i.Uv254Intensity }) .ToList() }; } }