using System; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; using Prism.Commands; using Prism.Events; using Prism.Mvvm; using XP.Common.Logging.Interfaces; using XP.Hardware.Detector.Abstractions; using XP.Hardware.Detector.Abstractions.Enums; using XP.Hardware.Detector.Abstractions.Events; using XP.Hardware.Detector.Services; namespace XP.Hardware.Detector.ViewModels { /// /// 探测器操作 ViewModel | Detector operation ViewModel /// 负责连接/断开、连续采集、单帧采集、图像显示及状态跟踪 /// public class DetectorImageWindowViewModel : BindableBase { private readonly IDetectorService _detectorService; private readonly IEventAggregator _eventAggregator; private readonly IImageService _imageSaveService; private readonly ILoggerService _logger; /// /// 帧处理节流标志,防止后台线程堆积 | Frame processing throttle flag to prevent thread pile-up /// 0 = 空闲,1 = 正在处理 /// private int _isProcessingFrame; #region 连接状态 | Connection status private bool _isConnected; /// /// 探测器是否已连接 | Whether detector is connected /// public bool IsConnected { get => _isConnected; private set { if (SetProperty(ref _isConnected, value)) { RaiseAllCommandsCanExecuteChanged(); RaisePropertyChanged(nameof(ConnectionStatusText)); RaisePropertyChanged(nameof(ConnectionStatusColor)); } } } /// /// 连接状态文本 | Connection status text /// public string ConnectionStatusText => _isConnected ? "已连接 | Connected" : "未连接 | Disconnected"; /// /// 连接状态指示颜色 | Connection status indicator color /// public Brush ConnectionStatusColor => _isConnected ? new SolidColorBrush(Color.FromRgb(0x4C, 0xAF, 0x50)) // 绿色 | Green : new SolidColorBrush(Color.FromRgb(0xF4, 0x43, 0x36)); // 红色 | Red #endregion #region 探测器状态 | Detector status private DetectorStatus _detectorStatus = DetectorStatus.Uninitialized; /// /// 当前探测器状态 | Current detector status /// public DetectorStatus DetectorStatus { get => _detectorStatus; private set { if (SetProperty(ref _detectorStatus, value)) { RaisePropertyChanged(nameof(DetectorStatusText)); RaiseAllCommandsCanExecuteChanged(); } } } /// /// 探测器状态文本 | Detector status text /// public string DetectorStatusText => _detectorStatus switch { Abstractions.Enums.DetectorStatus.Uninitialized => "未初始化 | Uninitialized", Abstractions.Enums.DetectorStatus.Initializing => "初始化中 | Initializing", Abstractions.Enums.DetectorStatus.Ready => "就绪 | Ready", Abstractions.Enums.DetectorStatus.Acquiring => "采集中 | Acquiring", Abstractions.Enums.DetectorStatus.Correcting => "校正中 | Correcting", Abstractions.Enums.DetectorStatus.Error => "错误 | Error", _ => "未知 | Unknown" }; #endregion #region 探测器信息 | Detector info private string _detectorModel = "--"; /// /// 探测器型号 | Detector model /// public string DetectorModel { get => _detectorModel; private set => SetProperty(ref _detectorModel, value); } private string _detectorResolution = "--"; /// /// 探测器分辨率 | Detector resolution /// public string DetectorResolution { get => _detectorResolution; private set => SetProperty(ref _detectorResolution, value); } private string _detectorPixelSize = "--"; /// /// 探测器像素尺寸 | Detector pixel size /// public string DetectorPixelSize { get => _detectorPixelSize; private set => SetProperty(ref _detectorPixelSize, value); } private string _detectorType = "--"; /// /// 探测器类型 | Detector type /// public string DetectorTypeText { get => _detectorType; private set => SetProperty(ref _detectorType, value); } #endregion #region 图像属性 | Image properties private BitmapSource _imageSource; /// /// 当前显示的图像 | Currently displayed image /// public BitmapSource ImageSource { get => _imageSource; private set => SetProperty(ref _imageSource, value); } private int _frameNumber; /// /// 当前帧号 | Current frame number /// public int FrameNumber { get => _frameNumber; private set => SetProperty(ref _frameNumber, value); } private string _imageInfo = "等待采集... | Waiting for acquisition..."; /// /// 图像信息文本 | Image info text /// public string ImageInfo { get => _imageInfo; private set => SetProperty(ref _imageInfo, value); } private bool _isAcquiring; /// /// 是否正在连续采集 | Whether continuous acquisition is in progress /// public bool IsAcquiring { get => _isAcquiring; private set { if (SetProperty(ref _isAcquiring, value)) RaiseAllCommandsCanExecuteChanged(); } } #endregion #region 操作忙碌状态 | Operation busy state private bool _isBusy; /// /// 是否正在执行连接/断开操作 | Whether connect/disconnect is in progress /// public bool IsBusy { get => _isBusy; private set { if (SetProperty(ref _isBusy, value)) RaiseAllCommandsCanExecuteChanged(); } } private string _statusMessage = ""; /// /// 操作状态消息 | Operation status message /// public string StatusMessage { get => _statusMessage; private set => SetProperty(ref _statusMessage, value); } #endregion #region 命令 | Commands /// /// 连接探测器命令 | Connect detector command /// public DelegateCommand ConnectCommand { get; } /// /// 断开探测器命令 | Disconnect detector command /// public DelegateCommand DisconnectCommand { get; } /// /// 启动连续采集命令 | Start continuous acquisition command /// public DelegateCommand StartAcquisitionCommand { get; } /// /// 停止采集命令 | Stop acquisition command /// public DelegateCommand StopAcquisitionCommand { get; } /// /// 单帧采集命令 | Single frame acquisition command /// public DelegateCommand SingleFrameCommand { get; } /// /// 保存图像命令 | Save image command /// public DelegateCommand SaveImageCommand { get; } #endregion #region 构造函数 | Constructor public DetectorImageWindowViewModel( IDetectorService detectorService, IEventAggregator eventAggregator, IImageService imageSaveService, ILoggerService logger) { _detectorService = detectorService ?? throw new ArgumentNullException(nameof(detectorService)); _eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator)); _imageSaveService = imageSaveService ?? throw new ArgumentNullException(nameof(imageSaveService)); _logger = logger?.ForModule() ?? throw new ArgumentNullException(nameof(logger)); ConnectCommand = new DelegateCommand(ExecuteConnectAsync, CanExecuteConnect); DisconnectCommand = new DelegateCommand(ExecuteDisconnectAsync, CanExecuteDisconnect); StartAcquisitionCommand = new DelegateCommand(ExecuteStartAcquisitionAsync, CanExecuteStartAcquisition); StopAcquisitionCommand = new DelegateCommand(ExecuteStopAcquisitionAsync, CanExecuteStopAcquisition); SingleFrameCommand = new DelegateCommand(ExecuteSingleFrameAsync, CanExecuteSingleFrame); SaveImageCommand = new DelegateCommand(ExecuteSaveImageAsync, CanExecuteSaveImage); // 订阅图像采集事件(后台线程)| Subscribe to image captured event (background thread) // 图像转换在后台线程执行,避免阻塞 UI | Image conversion runs on background thread to avoid blocking UI _eventAggregator.GetEvent() .Subscribe(OnImageCaptured, ThreadOption.BackgroundThread); // 订阅探测器状态变更事件 | Subscribe to detector status changed event _eventAggregator.GetEvent() .Subscribe(OnDetectorStatusChanged, ThreadOption.UIThread); // 同步初始状态 | Sync initial state SyncStatusFromService(); } #endregion #region CanExecute private bool CanExecuteConnect() => !_isBusy && !_isConnected; private bool CanExecuteDisconnect() => !_isBusy && _isConnected && !_isAcquiring; private bool CanExecuteStartAcquisition() => !_isBusy && _isConnected && _detectorStatus == Abstractions.Enums.DetectorStatus.Ready && !_isAcquiring; private bool CanExecuteStopAcquisition() => _isConnected && _isAcquiring; private bool CanExecuteSingleFrame() => !_isBusy && _isConnected && _detectorStatus == Abstractions.Enums.DetectorStatus.Ready && !_isAcquiring; private bool CanExecuteSaveImage() => _isConnected && _imageSaveService.LatestFrame != null; private void RaiseAllCommandsCanExecuteChanged() { ConnectCommand.RaiseCanExecuteChanged(); DisconnectCommand.RaiseCanExecuteChanged(); StartAcquisitionCommand.RaiseCanExecuteChanged(); StopAcquisitionCommand.RaiseCanExecuteChanged(); SingleFrameCommand.RaiseCanExecuteChanged(); SaveImageCommand.RaiseCanExecuteChanged(); } #endregion #region 命令执行 | Command execution /// /// 连接探测器 | Connect detector /// private async void ExecuteConnectAsync() { _logger?.Info("开始连接探测器 | Connecting detector"); IsBusy = true; StatusMessage = "正在连接探测器... | Connecting..."; try { var result = await _detectorService.InitializeAsync(); if (result.IsSuccess) { IsConnected = true; StatusMessage = "探测器连接成功,正在应用参数... | Detector connected, applying parameters..."; _logger?.Info("探测器连接成功 | Detector connected successfully"); RefreshDetectorInfo(); // 连接成功后自动应用持久化的探测器参数 | Auto apply saved detector parameters after connection var saved = Config.ConfigLoader.LoadSavedParameters(); if (saved.HasValue) { var (binIdx, pga, fr, _) = saved.Value; var applyResult = await _detectorService.ApplyParametersAsync(binIdx, pga, fr); if (applyResult.IsSuccess) { StatusMessage = "探测器就绪 | Detector ready"; _logger?.Info("探测器参数已自动应用,Binning={Binning},PGA={PGA},帧率={FrameRate} | Detector parameters auto-applied", binIdx, pga, fr); } else { StatusMessage = $"参数应用失败:{applyResult.ErrorMessage}"; _logger?.Warn("自动应用参数失败:{Message} | Auto apply parameters failed: {Message}", applyResult.ErrorMessage); } } else { StatusMessage = "探测器就绪(使用默认参数)| Detector ready (default parameters)"; _logger?.Info("未找到持久化参数,使用默认配置 | No saved parameters found, using default config"); } } else { StatusMessage = $"连接失败:{result.ErrorMessage}"; _logger?.Error(result.Exception, "探测器连接失败:{Message} | Detector connection failed: {Message}", result.ErrorMessage); } } catch (Exception ex) { StatusMessage = $"连接异常:{ex.Message}"; _logger?.Error(ex, "探测器连接异常:{Message} | Detector connection exception: {Message}", ex.Message); } finally { IsBusy = false; } } /// /// 断开探测器 | Disconnect detector /// private async void ExecuteDisconnectAsync() { _logger?.Info("开始断开探测器 | Disconnecting detector"); IsBusy = true; StatusMessage = "正在断开探测器... | Disconnecting..."; try { var result = await _detectorService.DisconnectAsync(); IsConnected = false; IsAcquiring = false; ClearDetectorInfo(); ImageInfo = "等待采集... | Waiting for acquisition..."; if (result.IsSuccess) { StatusMessage = "探测器已断开 | Detector disconnected"; _logger?.Info("探测器已断开 | Detector disconnected"); } else { StatusMessage = $"断开时发生错误:{result.ErrorMessage}"; _logger?.Warn("断开探测器时发生错误:{Message} | Error during disconnect: {Message}", result.ErrorMessage); } } catch (Exception ex) { IsConnected = false; IsAcquiring = false; ClearDetectorInfo(); StatusMessage = $"断开异常:{ex.Message}"; _logger?.Error(ex, "断开探测器异常:{Message} | Disconnect exception: {Message}", ex.Message); } finally { IsBusy = false; } } /// /// 启动连续采集 | Start continuous acquisition /// private async void ExecuteStartAcquisitionAsync() { _logger?.Info("启动连续采集 | Starting continuous acquisition"); IsAcquiring = true; StatusMessage = "采集中... | Acquiring..."; try { var result = await _detectorService.StartAcquisitionAsync(); if (!result.IsSuccess) { IsAcquiring = false; StatusMessage = $"启动采集失败:{result.ErrorMessage}"; _logger?.Error(result.Exception, "启动采集失败:{Message} | Start acquisition failed: {Message}", result.ErrorMessage); } } catch (Exception ex) { IsAcquiring = false; StatusMessage = $"启动采集异常:{ex.Message}"; _logger?.Error(ex, "启动采集异常:{Message} | Start acquisition exception: {Message}", ex.Message); } } /// /// 停止采集 | Stop acquisition /// private async void ExecuteStopAcquisitionAsync() { _logger?.Info("停止采集 | Stopping acquisition"); StatusMessage = "正在停止采集... | Stopping..."; try { var result = await _detectorService.StopAcquisitionAsync(); IsAcquiring = false; StatusMessage = result.IsSuccess ? "采集已停止 | Acquisition stopped" : $"停止采集失败:{result.ErrorMessage}"; if (!result.IsSuccess) _logger?.Error(result.Exception, "停止采集失败:{Message} | Stop acquisition failed: {Message}", result.ErrorMessage); } catch (Exception ex) { IsAcquiring = false; StatusMessage = $"停止采集异常:{ex.Message}"; _logger?.Error(ex, "停止采集异常:{Message} | Stop acquisition exception: {Message}", ex.Message); } } /// /// 单帧采集 | Single frame acquisition /// private async void ExecuteSingleFrameAsync() { _logger?.Info("单帧采集 | Single frame acquisition"); IsBusy = true; StatusMessage = "单帧采集中... | Acquiring single frame..."; try { var result = await _detectorService.AcquireSingleFrameAsync(); StatusMessage = result.IsSuccess ? "单帧采集完成 | Single frame acquired" : $"单帧采集失败:{result.ErrorMessage}"; if (!result.IsSuccess) _logger?.Error(result.Exception, "单帧采集失败:{Message} | Single frame failed: {Message}", result.ErrorMessage); } catch (Exception ex) { StatusMessage = $"单帧采集异常:{ex.Message}"; _logger?.Error(ex, "单帧采集异常:{Message} | Single frame exception: {Message}", ex.Message); } finally { IsBusy = false; } } /// /// 保存当前图像为 16 位 TIFF | Save current image as 16-bit TIFF /// private async void ExecuteSaveImageAsync() { var frame = _imageSaveService.LatestFrame; if (frame?.ImageData == null) { StatusMessage = "无可保存的图像 | No image to save"; return; } _logger?.Info("保存图像,帧号:{FrameNumber} | Saving image, frame: {FrameNumber}", frame.FrameNumber); StatusMessage = "正在保存图像... | Saving image..."; try { var saveDir = _imageSaveService.GetDefaultSaveDirectory(); var result = await _imageSaveService.SaveLatestFrameAsync(saveDir, "IMG"); StatusMessage = result.IsSuccess ? $"图像已保存 | Image saved" : $"保存失败:{result.ErrorMessage}"; } catch (Exception ex) { StatusMessage = $"保存异常:{ex.Message}"; _logger?.Error(ex, "保存图像异常:{Message} | Save image exception: {Message}", ex.Message); } } #endregion #region 事件回调 | Event callbacks /// /// 图像采集回调,将 16 位原始数据归一化为 8 位灰度图 | Image captured callback /// private void OnImageCaptured(ImageCapturedEventArgs args) { if (args?.ImageData == null || args.Width == 0 || args.Height == 0) return; // 帧节流:上一帧尚未被 UI 线程消费完毕时,跳过当前帧 // Frame throttle: skip current frame if previous frame hasn't been consumed by UI thread yet if (Interlocked.CompareExchange(ref _isProcessingFrame, 1, 0) != 0) return; try { // 在后台线程执行图像转换 | Perform image conversion on background thread var bitmap = ConvertToBitmapSource(args.ImageData, (int)args.Width, (int)args.Height); bitmap.Freeze(); // Freeze 后可跨线程访问 | Freeze allows cross-thread access var frameNumber = args.FrameNumber; var imageInfo = $"{args.Width}×{args.Height} 帧#{args.FrameNumber} {args.CaptureTime:HH:mm:ss.fff}"; // 将属性赋值调度到 UI 线程,完成后才释放节流标志 // Dispatch property assignment to UI thread; release throttle flag only after UI completes Application.Current?.Dispatcher?.BeginInvoke(new Action(() => { try { ImageSource = bitmap; FrameNumber = frameNumber; ImageInfo = imageInfo; } finally { // UI 线程完成渲染后才允许下一帧进入 | Allow next frame only after UI thread finishes rendering Interlocked.Exchange(ref _isProcessingFrame, 0); } })); } catch (Exception ex) { // 异常时释放标志,避免永久锁死 | Release flag on exception to avoid permanent lock Interlocked.Exchange(ref _isProcessingFrame, 0); _logger?.Error(ex, "图像转换失败:{Message} | Image conversion failed: {Message}", ex.Message); } } /// /// 探测器状态变更回调 | Detector status changed callback /// private void OnDetectorStatusChanged(DetectorStatus status) { DetectorStatus = status; // 根据状态同步 IsAcquiring | Sync IsAcquiring from status if (status == Abstractions.Enums.DetectorStatus.Acquiring) IsAcquiring = true; else if (status == Abstractions.Enums.DetectorStatus.Ready || status == Abstractions.Enums.DetectorStatus.Error) IsAcquiring = false; // 探测器错误时同步连接状态 | Sync connection state on error if (status == Abstractions.Enums.DetectorStatus.Uninitialized) { IsConnected = false; ClearDetectorInfo(); } } #endregion #region 私有辅助方法 | Private helpers /// /// 从服务层同步当前状态(ViewModel 初始化时调用)| Sync state from service (called on init) /// private void SyncStatusFromService() { IsConnected = _detectorService.IsConnected; DetectorStatus = _detectorService.Status; if (IsConnected) RefreshDetectorInfo(); } /// /// 刷新探测器信息显示 | Refresh detector info display /// private void RefreshDetectorInfo() { try { var info = _detectorService.GetInfo(); if (info == null) return; DetectorModel = string.IsNullOrEmpty(info.Model) ? "--" : info.Model; DetectorResolution = (info.MaxWidth > 0 && info.MaxHeight > 0) ? $"{info.MaxWidth}×{info.MaxHeight}" : "--"; DetectorPixelSize = info.PixelSize > 0 ? $"{info.PixelSize:F3} mm" : "--"; DetectorTypeText = _detectorService.Type?.ToString() ?? "--"; } catch (Exception ex) { _logger?.Warn("获取探测器信息失败:{Message} | Failed to get detector info: {Message}", ex.Message); } } /// /// 清空探测器信息 | Clear detector info /// private void ClearDetectorInfo() { DetectorModel = "--"; DetectorResolution = "--"; DetectorPixelSize = "--"; DetectorTypeText = "--"; } /// /// 将 16 位灰度数据转换为 8 位 BitmapSource(委托给 XP.Common 通用转换器) /// Convert 16-bit grayscale data to 8-bit BitmapSource (delegates to XP.Common common converter) /// private static BitmapSource ConvertToBitmapSource(ushort[] data, int width, int height) { return XP.Common.Converters.ImageConverter.ConvertGray16ToBitmapSource(data, width, height); } #endregion } }