将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。
This commit is contained in:
@@ -0,0 +1,584 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Prism.Events;
|
||||
using XP.Hardware.Detector.Abstractions;
|
||||
using XP.Hardware.Detector.Abstractions.Enums;
|
||||
using XP.Hardware.Detector.Config;
|
||||
using XP.Hardware.Detector.Factories;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
|
||||
namespace XP.Hardware.Detector.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 探测器服务实现 | Detector service implementation
|
||||
/// 单例模式,封装业务逻辑,屏蔽厂商差异
|
||||
/// </summary>
|
||||
public class DetectorService : IDetectorService, IDisposable
|
||||
{
|
||||
private readonly IDetectorFactory _factory;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly object _lock = new object();
|
||||
private IAreaDetector _detector;
|
||||
private DetectorConfig _config;
|
||||
private DetectorResult _lastError;
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// 当前探测器状态 | Current detector status
|
||||
/// </summary>
|
||||
public DetectorStatus Status
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _detector?.Status ?? DetectorStatus.Uninitialized;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前探测器类型 | Current detector type
|
||||
/// </summary>
|
||||
public DetectorType? Type
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _detector?.Type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 探测器是否已连接 | Whether detector is connected
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _detector != null && _detector.Status != DetectorStatus.Uninitialized;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="factory">探测器工厂 | Detector factory</param>
|
||||
/// <param name="eventAggregator">事件聚合器 | Event aggregator</param>
|
||||
/// <param name="logger">日志服务 | Logger service</param>
|
||||
public DetectorService(IDetectorFactory factory, IEventAggregator eventAggregator, ILoggerService logger = null)
|
||||
{
|
||||
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_logger = logger?.ForModule("DetectorService");
|
||||
_logger?.Info("DetectorService 实例已创建 | DetectorService instance created");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化探测器 | Initialize detector
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
public async Task<DetectorResult> InitializeAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 若已连接,先断开再重连 | If already connected, disconnect first then reconnect
|
||||
bool alreadyConnected;
|
||||
lock (_lock) { alreadyConnected = _detector != null; }
|
||||
if (alreadyConnected)
|
||||
{
|
||||
_logger?.Info("探测器已连接,先断开再重新初始化 | Detector already connected, disconnecting before reinitializing");
|
||||
await DisconnectAsync();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger?.Info("开始初始化探测器服务 | Starting to initialize detector service");
|
||||
|
||||
// 加载配置 | Load configuration
|
||||
_logger?.Debug("加载配置文件 | Loading configuration file");
|
||||
var configResult = ConfigLoader.LoadConfiguration();
|
||||
if (!configResult.IsSuccess)
|
||||
{
|
||||
_lastError = configResult;
|
||||
_logger?.Error(null, $"加载配置失败 | Failed to load configuration: {configResult.ErrorMessage}");
|
||||
return configResult;
|
||||
}
|
||||
_logger?.Info($"配置加载成功,探测器类型:{configResult.Data.Type} | Configuration loaded successfully, detector type: {configResult.Data.Type}");
|
||||
|
||||
// 保存配置引用 | Save config reference
|
||||
_config = configResult.Data;
|
||||
|
||||
// 创建探测器实例 | Create detector instance
|
||||
_logger?.Debug("创建探测器实例 | Creating detector instance");
|
||||
var createResult = _factory.CreateDetector(configResult.Data);
|
||||
if (!createResult.IsSuccess)
|
||||
{
|
||||
_lastError = createResult;
|
||||
_logger?.Error(createResult.Exception, $"创建探测器实例失败 | Failed to create detector instance: {createResult.ErrorMessage}");
|
||||
return DetectorResult.Failure(createResult.ErrorMessage, createResult.Exception, createResult.ErrorCode);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_detector = createResult.Data;
|
||||
}
|
||||
|
||||
// 初始化探测器 | Initialize detector
|
||||
_logger?.Debug("初始化探测器硬件 | Initializing detector hardware");
|
||||
var initResult = await _detector.InitializeAsync(cancellationToken);
|
||||
if (!initResult.IsSuccess)
|
||||
{
|
||||
_lastError = initResult;
|
||||
_logger?.Error(initResult.Exception, $"初始化探测器硬件失败 | Failed to initialize detector hardware: {initResult.ErrorMessage}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.Info("探测器服务初始化成功 | Detector service initialized successfully");
|
||||
}
|
||||
|
||||
return initResult;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"初始化服务失败 | Failed to initialize service: {ex.Message}";
|
||||
_logger?.Error(ex, errorMsg);
|
||||
var errorResult = DetectorResult.Failure(errorMsg, ex, -1);
|
||||
_lastError = errorResult;
|
||||
return errorResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断开探测器连接 | Disconnect detector
|
||||
/// </summary>
|
||||
public async Task<DetectorResult> DisconnectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IAreaDetector detector;
|
||||
lock (_lock)
|
||||
{
|
||||
detector = _detector;
|
||||
if (detector == null)
|
||||
{
|
||||
_logger?.Warn("探测器未连接,无需断开 | Detector not connected, no need to disconnect");
|
||||
return DetectorResult.Success("探测器未连接 | Detector not connected");
|
||||
}
|
||||
}
|
||||
|
||||
_logger?.Info("开始断开探测器连接 | Starting to disconnect detector");
|
||||
|
||||
// 如果正在采集,先停止 | If acquiring, stop first
|
||||
if (detector.Status == DetectorStatus.Acquiring)
|
||||
{
|
||||
_logger?.Info("探测器正在采集,先停止采集 | Detector is acquiring, stopping first");
|
||||
await detector.StopAcquisitionAsync();
|
||||
}
|
||||
|
||||
// 释放探测器资源 | Dispose detector resources
|
||||
lock (_lock)
|
||||
{
|
||||
_detector?.Dispose();
|
||||
_detector = null;
|
||||
}
|
||||
|
||||
_config = null;
|
||||
_logger?.Info("探测器已断开连接 | Detector disconnected");
|
||||
return DetectorResult.Success("探测器已断开连接 | Detector disconnected");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"断开探测器连接异常 | Disconnect detector exception: {ex.Message}";
|
||||
_logger?.Error(ex, errorMsg);
|
||||
// 强制清理 | Force cleanup
|
||||
lock (_lock)
|
||||
{
|
||||
_detector?.Dispose();
|
||||
_detector = null;
|
||||
}
|
||||
_config = null;
|
||||
var errorResult = DetectorResult.Failure(errorMsg, ex, -1);
|
||||
_lastError = errorResult;
|
||||
return errorResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动连续采集 | Start continuous acquisition
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
public async Task<DetectorResult> StartAcquisitionAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger?.Info("服务层:启动连续采集 | Service layer: Starting continuous acquisition");
|
||||
var detector = GetDetectorOrThrow();
|
||||
var result = await detector.StartAcquisitionAsync(cancellationToken);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
_lastError = result;
|
||||
_logger?.Error(result.Exception, $"启动采集失败 | Failed to start acquisition: {result.ErrorMessage}");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"服务层启动采集异常 | Service layer start acquisition exception: {ex.Message}";
|
||||
_logger?.Error(ex, errorMsg);
|
||||
var errorResult = DetectorResult.Failure(errorMsg, ex, -1);
|
||||
_lastError = errorResult;
|
||||
return errorResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止采集 | Stop acquisition
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
public async Task<DetectorResult> StopAcquisitionAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger?.Info("服务层:停止采集 | Service layer: Stopping acquisition");
|
||||
var detector = GetDetectorOrThrow();
|
||||
var result = await detector.StopAcquisitionAsync(cancellationToken);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
_lastError = result;
|
||||
_logger?.Error(result.Exception, $"停止采集失败 | Failed to stop acquisition: {result.ErrorMessage}");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"服务层停止采集异常 | Service layer stop acquisition exception: {ex.Message}";
|
||||
_logger?.Error(ex, errorMsg);
|
||||
var errorResult = DetectorResult.Failure(errorMsg, ex, -1);
|
||||
_lastError = errorResult;
|
||||
return errorResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单帧采集 | Single frame acquisition
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
public async Task<DetectorResult> AcquireSingleFrameAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger?.Debug("服务层:单帧采集 | Service layer: Single frame acquisition");
|
||||
var detector = GetDetectorOrThrow();
|
||||
var result = await detector.AcquireSingleFrameAsync(cancellationToken);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
_lastError = result;
|
||||
_logger?.Error(result.Exception, $"单帧采集失败 | Failed to acquire single frame: {result.ErrorMessage}");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"服务层单帧采集异常 | Service layer single frame acquisition exception: {ex.Message}";
|
||||
_logger?.Error(ex, errorMsg);
|
||||
var errorResult = DetectorResult.Failure(errorMsg, ex, -1);
|
||||
_lastError = errorResult;
|
||||
return errorResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暗场校正 | Dark field correction
|
||||
/// </summary>
|
||||
/// <param name="frameCount">采集帧数 | Frame count</param>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
public async Task<DetectorResult> DarkCorrectionAsync(int frameCount = 10, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger?.Info("服务层:执行暗场校正,帧数:{FrameCount} | Service layer: Executing dark correction, frame count: {FrameCount}", frameCount);
|
||||
var detector = GetDetectorOrThrow();
|
||||
var result = await detector.DarkCorrectionAsync(frameCount, cancellationToken);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
_lastError = result;
|
||||
_logger?.Error(result.Exception, "暗场校正失败:{Message} | Dark correction failed: {Message}", result.ErrorMessage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"服务层暗场校正异常 | Service layer dark correction exception: {ex.Message}";
|
||||
_logger?.Error(ex, errorMsg);
|
||||
var errorResult = DetectorResult.Failure(errorMsg, ex, -1);
|
||||
_lastError = errorResult;
|
||||
return errorResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增益校正(亮场校正)| Gain correction (bright field correction)
|
||||
/// </summary>
|
||||
/// <param name="frameCount">采集帧数 | Frame count</param>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
public async Task<DetectorResult> GainCorrectionAsync(int frameCount = 10, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger?.Info("服务层:执行亮场校正,帧数:{FrameCount} | Service layer: Executing gain correction, frame count: {FrameCount}", frameCount);
|
||||
var detector = GetDetectorOrThrow();
|
||||
var result = await detector.GainCorrectionAsync(frameCount, cancellationToken);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
_lastError = result;
|
||||
_logger?.Error(result.Exception, "亮场校正失败:{Message} | Gain correction failed: {Message}", result.ErrorMessage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"服务层亮场校正异常 | Service layer gain correction exception: {ex.Message}";
|
||||
_logger?.Error(ex, errorMsg);
|
||||
var errorResult = DetectorResult.Failure(errorMsg, ex, -1);
|
||||
_lastError = errorResult;
|
||||
return errorResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 坏像素校正 | Bad pixel correction
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
public async Task<DetectorResult> BadPixelCorrectionAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger?.Info("服务层:执行坏像素校正 | Service layer: Executing bad pixel correction");
|
||||
var detector = GetDetectorOrThrow();
|
||||
var result = await detector.BadPixelCorrectionAsync(cancellationToken);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
_lastError = result;
|
||||
_logger?.Error(result.Exception, "坏像素校正失败:{Message} | Bad pixel correction failed: {Message}", result.ErrorMessage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"服务层坏像素校正异常 | Service layer bad pixel correction exception: {ex.Message}";
|
||||
_logger?.Error(ex, errorMsg);
|
||||
var errorResult = DetectorResult.Failure(errorMsg, ex, -1);
|
||||
_lastError = errorResult;
|
||||
return errorResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行自动校正(暗场+增益+坏像素)| Execute auto correction (dark + gain + bad pixel)
|
||||
/// </summary>
|
||||
/// <param name="frameCount">采集帧数 | Frame count</param>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
public async Task<DetectorResult> AutoCorrectionAsync(int frameCount = 10, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger?.Info("服务层:执行自动校正,帧数:{FrameCount} | Service layer: Executing auto correction, frame count: {FrameCount}", frameCount);
|
||||
var detector = GetDetectorOrThrow();
|
||||
var result = await detector.AutoCorrectionAsync(frameCount, cancellationToken);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
_lastError = result;
|
||||
_logger?.Error(result.Exception, "自动校正失败:{Message} | Auto correction failed: {Message}", result.ErrorMessage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"服务层自动校正异常 | Service layer auto correction exception: {ex.Message}";
|
||||
_logger?.Error(ex, errorMsg);
|
||||
var errorResult = DetectorResult.Failure(errorMsg, ex, -1);
|
||||
_lastError = errorResult;
|
||||
return errorResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用探测器参数(Binning/PGA/帧率统一下发)| Apply detector parameters (Binning/PGA/FrameRate)
|
||||
/// </summary>
|
||||
public async Task<DetectorResult> ApplyParametersAsync(int binningIndex, int pga, decimal frameRate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger?.Info("服务层:应用参数,Binning={Binning},PGA={PGA},帧率={FrameRate} | Service layer: Applying parameters",
|
||||
binningIndex, pga, frameRate);
|
||||
|
||||
var detector = GetDetectorOrThrow();
|
||||
|
||||
// 通过 IVarexDetector 接口下发参数 | Apply parameters via IVarexDetector interface
|
||||
if (detector is IVarexDetector varexDetector)
|
||||
{
|
||||
// 设置 Binning | Set binning
|
||||
var binningResult = await varexDetector.SetBinningModeAsync((BinningMode)binningIndex);
|
||||
if (!binningResult.IsSuccess)
|
||||
{
|
||||
_lastError = binningResult;
|
||||
return binningResult;
|
||||
}
|
||||
|
||||
// 设置增益(PGA)| Set gain (PGA)
|
||||
var gainResult = await varexDetector.SetGainModeAsync((GainMode)pga);
|
||||
if (!gainResult.IsSuccess)
|
||||
{
|
||||
_lastError = gainResult;
|
||||
return gainResult;
|
||||
}
|
||||
|
||||
// 设置曝光时间(帧率→微秒:1000*1000/帧率)| Set exposure time (frame rate → microseconds)
|
||||
uint exposureUs = frameRate > 0 ? (uint)(1_000_000m / frameRate) : 66667;
|
||||
var exposureResult = await varexDetector.SetExposureTimeAsync(exposureUs);
|
||||
if (!exposureResult.IsSuccess)
|
||||
{
|
||||
_lastError = exposureResult;
|
||||
return exposureResult;
|
||||
}
|
||||
|
||||
_logger?.Info("参数应用成功 | Parameters applied successfully");
|
||||
return DetectorResult.Success("参数应用成功 | Parameters applied successfully");
|
||||
}
|
||||
|
||||
return DetectorResult.Failure("当前探测器不支持参数下发 | Current detector does not support parameter application");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"服务层应用参数异常 | Service layer apply parameters exception: {ex.Message}";
|
||||
_logger?.Error(ex, errorMsg);
|
||||
var errorResult = DetectorResult.Failure(errorMsg, ex, -1);
|
||||
_lastError = errorResult;
|
||||
return errorResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前配置到文件 | Save current configuration to file
|
||||
/// </summary>
|
||||
public void SaveParameters(int binningIndex, int pga, decimal frameRate, int avgFrames)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger?.Info("保存探测器参数,Binning={Binning},PGA={PGA},帧率={FrameRate},帧合并={AvgFrames} | Saving detector parameters",
|
||||
binningIndex, pga, frameRate, avgFrames);
|
||||
ConfigLoader.SaveParameters(binningIndex, pga, frameRate, avgFrames);
|
||||
_logger?.Info("探测器参数保存成功 | Detector parameters saved successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.Error(ex, "保存探测器参数失败:{Message} | Failed to save detector parameters: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取探测器信息 | Get detector information
|
||||
/// </summary>
|
||||
/// <returns>探测器信息 | Detector information</returns>
|
||||
public DetectorInfo GetInfo()
|
||||
{
|
||||
var detector = GetDetectorOrThrow();
|
||||
return detector.GetInfo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取最后的错误信息 | Get last error information
|
||||
/// </summary>
|
||||
/// <returns>错误结果 | Error result</returns>
|
||||
public DetectorResult GetLastError()
|
||||
{
|
||||
return _lastError ?? DetectorResult.Success();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前探测器配置 | Get current detector configuration
|
||||
/// 未初始化时尝试从配置文件加载(仅读取配置,不创建探测器实例)
|
||||
/// </summary>
|
||||
public DetectorConfig GetCurrentConfig()
|
||||
{
|
||||
if (_config != null) return _config;
|
||||
|
||||
// 未初始化时,尝试从配置文件加载以提供 UI 选项 | If not initialized, try loading from config for UI options
|
||||
try
|
||||
{
|
||||
var configResult = ConfigLoader.LoadConfiguration();
|
||||
if (configResult.IsSuccess)
|
||||
{
|
||||
_config = configResult.Data;
|
||||
return _config;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略加载异常,返回 null | Ignore load exception, return null
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取探测器实例或抛出异常 | Get detector instance or throw exception
|
||||
/// </summary>
|
||||
/// <returns>探测器实例 | Detector instance</returns>
|
||||
/// <exception cref="InvalidOperationException">探测器未初始化 | Detector not initialized</exception>
|
||||
private IAreaDetector GetDetectorOrThrow()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_detector == null)
|
||||
{
|
||||
throw new InvalidOperationException("探测器未初始化,请先调用 InitializeAsync | Detector not initialized, please call InitializeAsync first");
|
||||
}
|
||||
return _detector;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源 | Dispose resources
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源 | Dispose resources
|
||||
/// </summary>
|
||||
/// <param name="disposing">是否释放托管资源 | Whether to dispose managed resources</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// 释放托管资源 | Release managed resources
|
||||
lock (_lock)
|
||||
{
|
||||
_detector?.Dispose();
|
||||
_detector = null;
|
||||
}
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using XP.Hardware.Detector.Abstractions;
|
||||
using XP.Hardware.Detector.Abstractions.Enums;
|
||||
using XP.Hardware.Detector.Config;
|
||||
|
||||
namespace XP.Hardware.Detector.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 探测器服务接口 | Detector service interface
|
||||
/// 提供无厂商耦合的通用服务方法
|
||||
/// </summary>
|
||||
public interface IDetectorService
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前探测器状态 | Current detector status
|
||||
/// </summary>
|
||||
DetectorStatus Status { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前探测器类型 | Current detector type
|
||||
/// </summary>
|
||||
DetectorType? Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 探测器是否已连接(已初始化)| Whether detector is connected (initialized)
|
||||
/// </summary>
|
||||
bool IsConnected { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化探测器(连接)| Initialize detector (connect)
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
Task<DetectorResult> InitializeAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 断开探测器连接 | Disconnect detector
|
||||
/// </summary>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
Task<DetectorResult> DisconnectAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 启动连续采集 | Start continuous acquisition
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
Task<DetectorResult> StartAcquisitionAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 停止采集 | Stop acquisition
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
Task<DetectorResult> StopAcquisitionAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 单帧采集 | Single frame acquisition
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
Task<DetectorResult> AcquireSingleFrameAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 暗场校正 | Dark field correction
|
||||
/// </summary>
|
||||
/// <param name="frameCount">采集帧数 | Frame count</param>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
Task<DetectorResult> DarkCorrectionAsync(int frameCount = 10, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 增益校正(亮场校正)| Gain correction (bright field correction)
|
||||
/// </summary>
|
||||
/// <param name="frameCount">采集帧数 | Frame count</param>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
Task<DetectorResult> GainCorrectionAsync(int frameCount = 10, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 坏像素校正 | Bad pixel correction
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
Task<DetectorResult> BadPixelCorrectionAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 执行自动校正(暗场+增益+坏像素)| Execute auto correction (dark + gain + bad pixel)
|
||||
/// </summary>
|
||||
/// <param name="frameCount">采集帧数 | Frame count</param>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
Task<DetectorResult> AutoCorrectionAsync(int frameCount = 10, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 应用探测器参数(Binning/PGA/帧率统一下发)| Apply detector parameters (Binning/PGA/FrameRate)
|
||||
/// </summary>
|
||||
/// <param name="binningIndex">Binning 索引 | Binning index</param>
|
||||
/// <param name="pga">PGA 灵敏度值 | PGA sensitivity value</param>
|
||||
/// <param name="frameRate">帧率 | Frame rate</param>
|
||||
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
|
||||
/// <returns>操作结果 | Operation result</returns>
|
||||
Task<DetectorResult> ApplyParametersAsync(int binningIndex, int pga, decimal frameRate, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前配置到文件 | Save current configuration to file
|
||||
/// </summary>
|
||||
/// <param name="binningIndex">Binning 索引 | Binning index</param>
|
||||
/// <param name="pga">PGA 灵敏度值 | PGA sensitivity value</param>
|
||||
/// <param name="frameRate">帧率 | Frame rate</param>
|
||||
/// <param name="avgFrames">帧合并数 | Average frame count</param>
|
||||
void SaveParameters(int binningIndex, int pga, decimal frameRate, int avgFrames);
|
||||
|
||||
/// <summary>
|
||||
/// 获取探测器信息 | Get detector information
|
||||
/// </summary>
|
||||
/// <returns>探测器信息 | Detector information</returns>
|
||||
DetectorInfo GetInfo();
|
||||
|
||||
/// <summary>
|
||||
/// 获取最后的错误信息 | Get last error information
|
||||
/// </summary>
|
||||
/// <returns>错误结果 | Error result</returns>
|
||||
DetectorResult GetLastError();
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前探测器配置 | Get current detector configuration
|
||||
/// 用于 UI 层获取探测器类型相关的参数选项
|
||||
/// </summary>
|
||||
/// <returns>探测器配置,未初始化时返回 null | Detector config, null if not initialized</returns>
|
||||
DetectorConfig GetCurrentConfig();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Threading.Tasks;
|
||||
using XP.Hardware.Detector.Abstractions;
|
||||
|
||||
namespace XP.Hardware.Detector.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 图像服务接口 | Image service interface
|
||||
/// 提供图像获取和 16 位 TIFF 保存功能,适用于连续采集和单帧采集
|
||||
/// </summary>
|
||||
public interface IImageService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取最新采集的原始 16 位图像数据 | Get latest captured raw 16-bit image data
|
||||
/// 返回 null 表示尚无图像 | Returns null if no image available
|
||||
/// </summary>
|
||||
ImageCapturedEventArgs LatestFrame { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 保存 16 位灰度图像为 TIFF 文件 | Save 16-bit grayscale image as TIFF file
|
||||
/// </summary>
|
||||
Task<DetectorResult> SaveAsTiffAsync(ushort[] imageData, int width, int height, string filePath);
|
||||
|
||||
/// <summary>
|
||||
/// 保存 16 位灰度图像为 TIFF 文件,自动生成文件名 | Save with auto-generated filename
|
||||
/// </summary>
|
||||
Task<DetectorResult> SaveAsTiffAsync(ushort[] imageData, int width, int height, string saveDirectory, string prefix, int frameNumber);
|
||||
|
||||
/// <summary>
|
||||
/// 保存最新帧为 16 位 TIFF(便捷方法)| Save latest frame as 16-bit TIFF (convenience method)
|
||||
/// </summary>
|
||||
/// <param name="saveDirectory">保存目录 | Save directory</param>
|
||||
/// <param name="prefix">文件名前缀 | Filename prefix</param>
|
||||
/// <returns>操作结果,无可用帧时返回失败 | Operation result, failure if no frame available</returns>
|
||||
Task<DetectorResult> SaveLatestFrameAsync(string saveDirectory, string prefix);
|
||||
|
||||
/// <summary>
|
||||
/// 获取配置的默认保存目录 | Get configured default save directory
|
||||
/// </summary>
|
||||
string GetDefaultSaveDirectory();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Prism.Events;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.Hardware.Detector.Abstractions;
|
||||
using XP.Hardware.Detector.Abstractions.Events;
|
||||
|
||||
namespace XP.Hardware.Detector.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 图像服务实现 | Image service implementation
|
||||
/// 提供最新帧获取和 16 位 TIFF 保存功能
|
||||
/// </summary>
|
||||
public class ImageService : IImageService
|
||||
{
|
||||
private readonly IDetectorService _detectorService;
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
/// <summary>
|
||||
/// 最新采集的原始图像数据 | Latest captured raw image data
|
||||
/// </summary>
|
||||
private volatile ImageCapturedEventArgs _latestFrame;
|
||||
|
||||
public ImageService(
|
||||
IDetectorService detectorService,
|
||||
IEventAggregator eventAggregator,
|
||||
ILoggerService logger)
|
||||
{
|
||||
_detectorService = detectorService ?? throw new ArgumentNullException(nameof(detectorService));
|
||||
_logger = logger?.ForModule<ImageService>();
|
||||
|
||||
// 订阅图像采集事件,缓存最新帧 | Subscribe to image captured event, cache latest frame
|
||||
if (eventAggregator == null) throw new ArgumentNullException(nameof(eventAggregator));
|
||||
eventAggregator.GetEvent<ImageCapturedEvent>()
|
||||
.Subscribe(OnImageCaptured, ThreadOption.BackgroundThread);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImageCapturedEventArgs LatestFrame => _latestFrame;
|
||||
|
||||
/// <summary>
|
||||
/// 图像采集事件回调,缓存最新帧 | Image captured callback, cache latest frame
|
||||
/// </summary>
|
||||
private void OnImageCaptured(ImageCapturedEventArgs args)
|
||||
{
|
||||
if (args?.ImageData != null && args.Width > 0 && args.Height > 0)
|
||||
_latestFrame = args;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<DetectorResult> SaveAsTiffAsync(ushort[] imageData, int width, int height, string filePath)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (imageData == null || imageData.Length == 0)
|
||||
return DetectorResult.Failure("图像数据为空 | Image data is empty");
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
return DetectorResult.Failure($"图像尺寸无效:{width}x{height} | Invalid image size: {width}x{height}");
|
||||
|
||||
// 确保目录存在 | Ensure directory exists
|
||||
var directory = Path.GetDirectoryName(filePath);
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// 创建 16 位灰度 BitmapSource | Create 16-bit grayscale BitmapSource
|
||||
int stride = width * sizeof(ushort);
|
||||
var bitmap = BitmapSource.Create(width, height, 96, 96, PixelFormats.Gray16, null, imageData, stride);
|
||||
bitmap.Freeze();
|
||||
|
||||
// 编码为 TIFF(无压缩)| Encode as TIFF (no compression)
|
||||
var encoder = new TiffBitmapEncoder { Compression = TiffCompressOption.None };
|
||||
encoder.Frames.Add(BitmapFrame.Create(bitmap));
|
||||
|
||||
using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
encoder.Save(stream);
|
||||
}
|
||||
|
||||
_logger?.Info("图像已保存:{FilePath},分辨率:{Width}x{Height} | Image saved: {FilePath}, resolution: {Width}x{Height}", filePath, width, height);
|
||||
return DetectorResult.Success($"图像已保存 | Image saved: {filePath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var errorMsg = $"保存图像失败:{ex.Message} | Failed to save image: {ex.Message}";
|
||||
_logger?.Error(ex, errorMsg);
|
||||
return DetectorResult.Failure(errorMsg, ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<DetectorResult> SaveAsTiffAsync(ushort[] imageData, int width, int height, string saveDirectory, string prefix, int frameNumber)
|
||||
{
|
||||
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff");
|
||||
var fileName = $"{prefix}_{timestamp}_F{frameNumber}.tif";
|
||||
var filePath = Path.Combine(saveDirectory, fileName);
|
||||
return SaveAsTiffAsync(imageData, width, height, filePath);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<DetectorResult> SaveLatestFrameAsync(string saveDirectory, string prefix)
|
||||
{
|
||||
var frame = _latestFrame;
|
||||
if (frame?.ImageData == null)
|
||||
return Task.FromResult(DetectorResult.Failure("无可用的图像数据 | No image data available"));
|
||||
|
||||
return SaveAsTiffAsync(frame.ImageData, (int)frame.Width, (int)frame.Height, saveDirectory, prefix, frame.FrameNumber);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetDefaultSaveDirectory()
|
||||
{
|
||||
var config = _detectorService.GetCurrentConfig();
|
||||
if (config != null && !string.IsNullOrEmpty(config.SavePath))
|
||||
return config.SavePath;
|
||||
|
||||
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Images");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user