1438 lines
70 KiB
C#
1438 lines
70 KiB
C#
using System;
|
||
using System.Buffers;
|
||
using System.Collections.Concurrent;
|
||
using System.Runtime.InteropServices;
|
||
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.Common.Logging.Interfaces;
|
||
|
||
namespace XP.Hardware.Detector.Implementations
|
||
{
|
||
/// <summary>
|
||
/// Varex 探测器实现类 | Varex detector implementation class
|
||
/// 通过 XISL API 控制 Varex 探测器硬件
|
||
/// </summary>
|
||
public class VarexDetector : AreaDetectorBase, IVarexDetector
|
||
{
|
||
#region 私有字段 | Private Fields
|
||
|
||
// 配置 | Configuration
|
||
private readonly VarexDetectorConfig _config;
|
||
|
||
// 日志服务 | Logger service
|
||
private readonly ILoggerService _logger;
|
||
|
||
// 内存管理 | Memory Management
|
||
private readonly ArrayPool<ushort> _arrayPool;
|
||
private readonly ConcurrentQueue<short[]> _imageQueue;
|
||
|
||
// XISL 句柄 | XISL Handles
|
||
private IntPtr _hAcqDesc = IntPtr.Zero;
|
||
private IntPtr _pAcqBuffer = IntPtr.Zero;
|
||
private IntPtr _pOffsetBuffer = IntPtr.Zero;
|
||
private IntPtr _pGainBuffer = IntPtr.Zero;
|
||
private IntPtr _pGainAvgBuffer = IntPtr.Zero; // 增益映射输出缓冲区(DWORD*)| Gain map output buffer (DWORD*)
|
||
private IntPtr _pCorrList = IntPtr.Zero;
|
||
|
||
// 回调委托 | Callback Delegates
|
||
private XISLApi.EndFrameCallback _endFrameCallback;
|
||
private XISLApi.EndAcqCallback _endAcqCallback;
|
||
|
||
// 事件句柄 | Event Handles
|
||
private IntPtr _hevEndFrame = IntPtr.Zero;
|
||
private IntPtr _hevEndAcq = IntPtr.Zero;
|
||
|
||
// 配置参数 | Configuration Parameters
|
||
private BinningMode _binningMode;
|
||
private GainMode _gainMode;
|
||
private uint _exposureTime;
|
||
private (uint x, uint y, uint width, uint height) _roi;
|
||
|
||
// 采集控制 | Acquisition Control
|
||
private bool _isAcquiring = false;
|
||
private CancellationTokenSource _acquisitionCts;
|
||
private readonly object _acquisitionLock = new object();
|
||
|
||
// 缓存的图像分辨率(初始化/参数变更时更新)| Cached image resolution (updated on init/param change)
|
||
private uint _cachedRows;
|
||
private uint _cachedColumns;
|
||
|
||
#endregion
|
||
|
||
#region 属性 | Properties
|
||
|
||
/// <summary>
|
||
/// 探测器类型 | Detector type
|
||
/// </summary>
|
||
public override DetectorType Type => DetectorType.Varex;
|
||
|
||
#endregion
|
||
|
||
#region 构造函数 | Constructor
|
||
|
||
/// <summary>
|
||
/// 构造函数 | Constructor
|
||
/// </summary>
|
||
/// <param name="config">Varex 探测器配置 | Varex detector configuration</param>
|
||
/// <param name="eventAggregator">事件聚合器 | Event aggregator</param>
|
||
/// <param name="logger">日志服务 | Logger service</param>
|
||
public VarexDetector(VarexDetectorConfig config, IEventAggregator eventAggregator, ILoggerService logger = null)
|
||
: base(eventAggregator)
|
||
{
|
||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||
_logger = logger?.ForModule("VarexDetector");
|
||
|
||
// 初始化内存管理 | Initialize memory management
|
||
_arrayPool = ArrayPool<ushort>.Shared;
|
||
_imageQueue = new ConcurrentQueue<short[]>();
|
||
|
||
// 初始化配置参数 | Initialize configuration parameters
|
||
_binningMode = config.BinningMode;
|
||
_gainMode = config.GainMode;
|
||
_exposureTime = config.ExposureTime;
|
||
_roi = (config.RoiX, config.RoiY, config.RoiWidth, config.RoiHeight);
|
||
|
||
// 创建回调委托实例(防止被 GC 回收)| Create callback delegate instances (prevent GC collection)
|
||
_endFrameCallback = OnEndFrameCallback;
|
||
_endAcqCallback = OnEndAcqCallback;
|
||
|
||
_logger?.Info("VarexDetector 实例已创建 | VarexDetector instance created");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 抽象方法实现(占位符)| Abstract Method Implementations (Placeholders)
|
||
|
||
/// <summary>
|
||
/// 初始化探测器内部实现 | Initialize detector internal implementation
|
||
/// </summary>
|
||
protected override Task<DetectorResult> InitializeInternalAsync(CancellationToken cancellationToken)
|
||
{
|
||
return Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
_logger?.Info("开始初始化 Varex 探测器 | Starting Varex detector initialization");
|
||
|
||
// 1. 调用 VarexDetDll 底层初始化 | Call VarexDetDll low-level initialization
|
||
_logger?.Debug("调用 DoDetInit 进行底层初始化 | Calling DoDetInit for low-level initialization");
|
||
int initResult = XISLApi.DoDetInit(ref _hAcqDesc);
|
||
if (initResult != 0)
|
||
{
|
||
var errorMsg = $"探测器底层初始化失败 | Detector low-level initialization failed, DoDetInit 返回码:{initResult}";
|
||
_logger?.Error(null, errorMsg);
|
||
return DetectorResult.Failure(errorMsg, null, initResult);
|
||
}
|
||
_logger?.Info("DoDetInit 初始化成功 | DoDetInit initialization successful");
|
||
|
||
// 2. 获取探测器句柄 | Get detector handle
|
||
IntPtr hWnd = IntPtr.Zero;
|
||
var result = XISLApi.Acquisition_GetNextSensor(ref hWnd, ref _hAcqDesc);
|
||
if (_hAcqDesc == IntPtr.Zero)
|
||
{
|
||
var errorMsg = "获取探测器句柄失败,句柄为空 | Failed to get detector handle, handle is null";
|
||
_logger?.Error(null, errorMsg);
|
||
return DetectorResult.Failure(errorMsg, null, (int)result);
|
||
}
|
||
_logger?.Debug("探测器句柄获取成功 | Detector handle acquired successfully");
|
||
|
||
// 3. 注册回调函数 | Register callbacks
|
||
Thread.Sleep(200);
|
||
result = XISLApi.Acquisition_SetCallbacksAndMessages(
|
||
_hAcqDesc, IntPtr.Zero, 0, 0, _endFrameCallback, _endAcqCallback);
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
var errorMsg = $"注册回调函数失败 | Failed to register callbacks: {result}";
|
||
_logger?.Error(null, errorMsg + $",返回码:{(int)result}");
|
||
return DetectorResult.Failure(errorMsg, null, (int)result);
|
||
}
|
||
_logger?.Debug("回调函数注册成功 | Callbacks registered successfully");
|
||
|
||
// 4. 设置帧同步模式为内部定时器 | Set frame sync mode to internal timer
|
||
result = XISLApi.Acquisition_SetFrameSyncMode(_hAcqDesc, (uint)XISLApi.HIS_SYNCMODE_INTERNAL_TIMER);
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
_logger?.Warn($"设置帧同步模式失败 | Failed to set frame sync mode: {result}");
|
||
}
|
||
|
||
// 5. 获取探测器配置(分辨率等)| Get detector configuration (resolution etc.)
|
||
uint dwFrames, dwRows, dwColumns, dwDataType, dwSortFlags;
|
||
uint dwAcqType, dwSystemID, dwSyncMode, dwHwAccess;
|
||
int iIRQFlags;
|
||
result = XISLApi.Acquisition_GetConfiguration(
|
||
_hAcqDesc, out dwFrames, out dwRows, out dwColumns,
|
||
out dwDataType, out dwSortFlags, out iIRQFlags,
|
||
out dwAcqType, out dwSystemID, out dwSyncMode, out dwHwAccess);
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
var errorMsg = $"获取探测器配置失败 | Failed to get detector configuration: {result}";
|
||
_logger?.Error(null, errorMsg + $",返回码:{(int)result}");
|
||
return DetectorResult.Failure(errorMsg, null, (int)result);
|
||
}
|
||
_logger?.Info($"探测器配置:分辨率 {dwColumns}x{dwRows},数据类型 {dwDataType} | Detector config: resolution {dwColumns}x{dwRows}, data type {dwDataType}");
|
||
|
||
// 6. 分配采集缓冲区(校正缓冲区保持 IntPtr.Zero,未校正前不可传给 API)
|
||
// Allocate acquisition buffer (correction buffers stay IntPtr.Zero until calibration is done)
|
||
int bufferSize = (int)(dwRows * dwColumns * sizeof(ushort));
|
||
_pAcqBuffer = Marshal.AllocHGlobal(bufferSize);
|
||
_logger?.Debug($"采集缓冲区已分配,大小:{bufferSize} 字节 | Acquisition buffer allocated, size: {bufferSize} bytes");
|
||
|
||
// 7. 定义目标缓冲区 | Define destination buffers
|
||
result = XISLApi.Acquisition_DefineDestBuffers(
|
||
_hAcqDesc, _pAcqBuffer, 1, dwRows, dwColumns);
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
var errorMsg = $"定义目标缓冲区失败 | Failed to define destination buffers: {result}";
|
||
_logger?.Error(null, errorMsg + $",返回码:{(int)result}");
|
||
return DetectorResult.Failure(errorMsg, null, (int)result);
|
||
}
|
||
_logger?.Debug("目标缓冲区定义成功 | Destination buffers defined successfully");
|
||
|
||
// 9. 创建事件句柄 | Create event handles
|
||
_hevEndFrame = XISLApi.CreateEvent(IntPtr.Zero, false, false, null);
|
||
_hevEndAcq = XISLApi.CreateEvent(IntPtr.Zero, false, false, null);
|
||
_logger?.Debug("事件句柄已创建 | Event handles created");
|
||
|
||
// 10. 应用初始配置(Binning/增益/曝光时间)| Apply initial configuration (Binning/Gain/Exposure)
|
||
var configResult = ApplyConfiguration();
|
||
if (!configResult.IsSuccess)
|
||
{
|
||
_logger?.Error(null, $"应用初始配置失败 | Failed to apply initial configuration: {configResult.ErrorMessage}");
|
||
return configResult;
|
||
}
|
||
|
||
_logger?.Info("Varex 探测器初始化成功 | Varex detector initialized successfully");
|
||
return DetectorResult.Success("Varex 探测器初始化成功 | Varex detector initialized successfully");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var errorMsg = $"初始化异常 | Initialization exception: {ex.Message}";
|
||
_logger?.Error(ex, errorMsg);
|
||
return DetectorResult.Failure(errorMsg, ex, -1);
|
||
}
|
||
}, cancellationToken);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 应用配置参数 | Apply configuration parameters
|
||
/// </summary>
|
||
private DetectorResult ApplyConfiguration()
|
||
{
|
||
try
|
||
{
|
||
// 设置 Binning 模式(API 值从 1 开始)| Set binning mode (API value starts from 1)
|
||
var result = XISLApi.Acquisition_SetCameraBinningMode(_hAcqDesc, (uint)_binningMode + 1);
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
return DetectorResult.Failure($"设置 Binning 模式失败 | Failed to set binning mode: {result}");
|
||
}
|
||
|
||
// 设置增益模式 | Set gain mode
|
||
result = XISLApi.Acquisition_SetCameraGain(_hAcqDesc, (uint)_gainMode);
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
return DetectorResult.Failure($"设置增益模式失败 | Failed to set gain mode: {result}");
|
||
}
|
||
|
||
// 设置曝光时间 | Set exposure time
|
||
uint exposureTime = _exposureTime;
|
||
result = XISLApi.Acquisition_SetTimerSync(_hAcqDesc, ref exposureTime);
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
return DetectorResult.Failure($"设置曝光时间失败 | Failed to set exposure time: {result}");
|
||
}
|
||
|
||
return DetectorResult.Success("配置应用成功 | Configuration applied successfully");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return DetectorResult.Failure($"应用配置异常 | Apply configuration exception: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前帧号 | Get current frame number
|
||
/// </summary>
|
||
private int GetCurrentFrameNumber()
|
||
{
|
||
uint dwActFrame, dwSecFrame;
|
||
XISLApi.Acquisition_GetActFrame(_hAcqDesc, out dwActFrame, out dwSecFrame);
|
||
return (int)dwActFrame;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 启动连续采集内部实现 | Start continuous acquisition internal implementation
|
||
/// </summary>
|
||
/// <summary>
|
||
/// 采集前准备:获取配置、分配缓冲区、定义目标缓冲区、设置采集标志
|
||
/// Prepare for acquisition: get config, allocate buffers, define dest buffers, set acq data
|
||
/// </summary>
|
||
private DetectorResult PrepareForAcquisition(uint acqDataFlag, int targetFrames, out uint dwRows, out uint dwColumns)
|
||
{
|
||
dwRows = 0;
|
||
dwColumns = 0;
|
||
|
||
// 1. 获取当前探测器配置(Binning 改变后分辨率会变)| Get current config (resolution changes with binning)
|
||
uint dwFrames, dwDataType, dwSortFlags, dwAcqType, dwSystemID, dwSyncMode, dwHwAccess;
|
||
int iIRQFlags;
|
||
var result = XISLApi.Acquisition_GetConfiguration(
|
||
_hAcqDesc, out dwFrames, out dwRows, out dwColumns,
|
||
out dwDataType, out dwSortFlags, out iIRQFlags,
|
||
out dwAcqType, out dwSystemID, out dwSyncMode, out dwHwAccess);
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
return DetectorResult.Failure($"获取探测器配置失败 | Failed to get detector configuration: {result}", null, (int)result);
|
||
}
|
||
_logger?.Debug($"采集前配置:分辨率 {dwColumns}x{dwRows} | Pre-acquisition config: resolution {dwColumns}x{dwRows}");
|
||
|
||
// 缓存分辨率供回调使用 | Cache resolution for callback use
|
||
_cachedRows = dwRows;
|
||
_cachedColumns = dwColumns;
|
||
|
||
// 2. 设置采集数据标志 | Set acquisition data flag
|
||
uint acqData = acqDataFlag;
|
||
result = XISLApi.Acquisition_SetAcqData(_hAcqDesc, ref acqData);
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
return DetectorResult.Failure($"设置采集数据标志失败 | Failed to set acquisition data flag: {result}", null, (int)result);
|
||
}
|
||
|
||
// 3. 重新分配采集缓冲区 | Reallocate acquisition buffer
|
||
if (_pAcqBuffer != IntPtr.Zero)
|
||
{
|
||
Marshal.FreeHGlobal(_pAcqBuffer);
|
||
}
|
||
_pAcqBuffer = Marshal.AllocHGlobal(targetFrames * (int)dwRows * (int)dwColumns * sizeof(ushort));
|
||
|
||
// 4. 定义目标缓冲区 | Define destination buffers
|
||
result = XISLApi.Acquisition_DefineDestBuffers(_hAcqDesc, _pAcqBuffer, targetFrames, dwRows, dwColumns);
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
return DetectorResult.Failure($"定义目标缓冲区失败 | Failed to define destination buffers: {result}", null, (int)result);
|
||
}
|
||
|
||
return DetectorResult.Success();
|
||
}
|
||
|
||
protected override Task<DetectorResult> StartAcquisitionInternalAsync(CancellationToken cancellationToken)
|
||
{
|
||
return Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
_logger?.Info("开始启动连续采集 | Starting continuous acquisition");
|
||
|
||
// 检查取消令牌 | Check cancellation token
|
||
if (cancellationToken.IsCancellationRequested)
|
||
{
|
||
_logger?.Warn("启动采集操作已取消 | Start acquisition operation cancelled");
|
||
return DetectorResult.Failure("操作已取消 | Operation cancelled");
|
||
}
|
||
|
||
lock (_acquisitionLock)
|
||
{
|
||
// 防止重复启动 | Prevent duplicate start
|
||
if (_isAcquiring)
|
||
{
|
||
var errorMsg = "采集已在进行中,无法重复启动 | Acquisition already in progress, cannot start again";
|
||
_logger?.Warn(errorMsg);
|
||
return DetectorResult.Failure(errorMsg);
|
||
}
|
||
|
||
// 确保之前的 CancellationTokenSource 已清理 | Ensure previous CancellationTokenSource is cleaned up
|
||
if (_acquisitionCts != null)
|
||
{
|
||
_logger?.Debug("清理之前的 CancellationTokenSource | Cleaning up previous CancellationTokenSource");
|
||
try
|
||
{
|
||
_acquisitionCts.Cancel();
|
||
_acquisitionCts.Dispose();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger?.Warn($"清理 CancellationTokenSource 时发生异常 | Exception while cleaning CancellationTokenSource: {ex.Message}");
|
||
}
|
||
_acquisitionCts = null;
|
||
}
|
||
|
||
_isAcquiring = true;
|
||
_acquisitionCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||
}
|
||
|
||
// 再次检查取消令牌 | Check cancellation token again
|
||
if (cancellationToken.IsCancellationRequested)
|
||
{
|
||
lock (_acquisitionLock)
|
||
{
|
||
_isAcquiring = false;
|
||
_acquisitionCts?.Dispose();
|
||
_acquisitionCts = null;
|
||
}
|
||
_logger?.Warn("启动采集操作已取消 | Start acquisition operation cancelled");
|
||
return DetectorResult.Failure("操作已取消 | Operation cancelled");
|
||
}
|
||
|
||
// 启动连续采集前准备 | Prepare before starting continuous acquisition
|
||
var prepResult = PrepareForAcquisition(XISLApi.ACQ_SNAP, 1, out uint dwRows, out uint dwColumns);
|
||
if (!prepResult.IsSuccess)
|
||
{
|
||
lock (_acquisitionLock)
|
||
{
|
||
_isAcquiring = false;
|
||
_acquisitionCts?.Dispose();
|
||
_acquisitionCts = null;
|
||
}
|
||
_logger?.Error(null, $"采集准备失败 | Acquisition preparation failed: {prepResult.ErrorMessage}");
|
||
return prepResult;
|
||
}
|
||
|
||
// 启动连续采集 | Start continuous acquisition
|
||
var result = XISLApi.Acquisition_Acquire_Image(
|
||
_hAcqDesc,
|
||
1, // 帧数 | Frame count
|
||
0, // 跳过帧数 | Skip frames
|
||
(uint)XISLApi.HIS_SEQ_CONTINUOUS,
|
||
_pOffsetBuffer, // 未校正时为 IntPtr.Zero | IntPtr.Zero before calibration
|
||
_pGainAvgBuffer, // 未校正时为 IntPtr.Zero,校正后为增益映射 | IntPtr.Zero before calibration, gain map after
|
||
_pCorrList); // 未校正时为 IntPtr.Zero | IntPtr.Zero before calibration
|
||
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
lock (_acquisitionLock)
|
||
{
|
||
_isAcquiring = false;
|
||
_acquisitionCts?.Dispose();
|
||
_acquisitionCts = null;
|
||
}
|
||
var errorMsg = $"启动采集失败 | Failed to start acquisition: {result}";
|
||
_logger?.Error(null, errorMsg + $",返回码:{(int)result}");
|
||
return DetectorResult.Failure(errorMsg, null, (int)result);
|
||
}
|
||
|
||
_logger?.Info("连续采集已启动 | Continuous acquisition started");
|
||
return DetectorResult.Success("采集已启动 | Acquisition started");
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
lock (_acquisitionLock)
|
||
{
|
||
_isAcquiring = false;
|
||
_acquisitionCts?.Dispose();
|
||
_acquisitionCts = null;
|
||
}
|
||
_logger?.Warn("启动采集操作已取消 | Start acquisition operation cancelled");
|
||
return DetectorResult.Failure("操作已取消 | Operation cancelled");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
lock (_acquisitionLock)
|
||
{
|
||
_isAcquiring = false;
|
||
_acquisitionCts?.Dispose();
|
||
_acquisitionCts = null;
|
||
}
|
||
var errorMsg = $"启动采集异常 | Start acquisition exception: {ex.Message}";
|
||
_logger?.Error(ex, errorMsg);
|
||
return DetectorResult.Failure(errorMsg, ex, -1);
|
||
}
|
||
}, cancellationToken);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止采集内部实现 | Stop acquisition internal implementation
|
||
/// </summary>
|
||
protected override Task<DetectorResult> StopAcquisitionInternalAsync(CancellationToken cancellationToken)
|
||
{
|
||
return Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
_logger?.Info("开始停止采集 | Starting to stop acquisition");
|
||
|
||
// 检查取消令牌 | Check cancellation token
|
||
if (cancellationToken.IsCancellationRequested)
|
||
{
|
||
_logger?.Warn("停止采集操作已取消 | Stop acquisition operation cancelled");
|
||
return DetectorResult.Failure("操作已取消 | Operation cancelled");
|
||
}
|
||
|
||
lock (_acquisitionLock)
|
||
{
|
||
// 幂等性检查:如果未在采集,直接返回成功 | Idempotency check: if not acquiring, return success
|
||
if (!_isAcquiring)
|
||
{
|
||
_logger?.Debug("采集未运行,无需停止 | Acquisition not running, no need to stop");
|
||
return DetectorResult.Success("采集未运行 | Acquisition not running");
|
||
}
|
||
}
|
||
|
||
// 停止采集 | Stop acquisition
|
||
var result = XISLApi.Acquisition_Abort(_hAcqDesc);
|
||
_logger?.Debug($"调用 Acquisition_Abort,返回码:{result} | Called Acquisition_Abort, return code: {result}");
|
||
|
||
// 等待采集结束事件,支持取消令牌 | Wait for acquisition end event, support cancellation token
|
||
const int timeoutMs = 5000; // 5 秒超时 | 5 seconds timeout
|
||
const int pollIntervalMs = 100; // 轮询间隔 | Poll interval
|
||
int elapsedMs = 0;
|
||
|
||
while (elapsedMs < timeoutMs)
|
||
{
|
||
// 检查取消令牌 | Check cancellation token
|
||
if (cancellationToken.IsCancellationRequested)
|
||
{
|
||
_logger?.Warn("停止采集操作已取消,但仍会清理资源 | Stop acquisition operation cancelled, but will still clean up resources");
|
||
break;
|
||
}
|
||
|
||
uint waitResult = XISLApi.WaitForSingleObject(_hevEndAcq, (uint)pollIntervalMs);
|
||
|
||
if (waitResult == XISLApi.WAIT_OBJECT_0)
|
||
{
|
||
// 成功等到事件 | Successfully waited for event
|
||
break;
|
||
}
|
||
|
||
elapsedMs += pollIntervalMs;
|
||
}
|
||
|
||
// 清理资源 | Clean up resources
|
||
lock (_acquisitionLock)
|
||
{
|
||
_isAcquiring = false;
|
||
|
||
if (_acquisitionCts != null)
|
||
{
|
||
try
|
||
{
|
||
_acquisitionCts.Cancel();
|
||
_acquisitionCts.Dispose();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger?.Warn($"清理 CancellationTokenSource 时发生异常 | Exception while cleaning CancellationTokenSource: {ex.Message}");
|
||
}
|
||
_acquisitionCts = null;
|
||
}
|
||
}
|
||
|
||
// 检查是否超时 | Check if timeout occurred
|
||
if (elapsedMs >= timeoutMs && !cancellationToken.IsCancellationRequested)
|
||
{
|
||
var errorMsg = $"停止采集超时({timeoutMs}ms),但资源已清理 | Stop acquisition timeout ({timeoutMs}ms), but resources cleaned up";
|
||
_logger?.Warn(errorMsg);
|
||
return DetectorResult.Failure(errorMsg, null, -2);
|
||
}
|
||
|
||
if (cancellationToken.IsCancellationRequested)
|
||
{
|
||
_logger?.Info("停止采集操作已取消,资源已清理 | Stop acquisition operation cancelled, resources cleaned up");
|
||
return DetectorResult.Failure("操作已取消 | Operation cancelled");
|
||
}
|
||
|
||
// 等待硬件完全释放采集资源,避免后续操作出现 HIS_ERROR_TIMEOUT
|
||
// Wait for hardware to fully release acquisition resources to avoid HIS_ERROR_TIMEOUT on subsequent operations
|
||
Thread.Sleep(200);
|
||
|
||
_logger?.Info("采集已停止 | Acquisition stopped");
|
||
return DetectorResult.Success("采集已停止 | Acquisition stopped");
|
||
}
|
||
catch (OperationCanceledException)
|
||
{
|
||
// 确保资源清理 | Ensure resource cleanup
|
||
lock (_acquisitionLock)
|
||
{
|
||
_isAcquiring = false;
|
||
_acquisitionCts?.Dispose();
|
||
_acquisitionCts = null;
|
||
}
|
||
_logger?.Warn("停止采集操作已取消 | Stop acquisition operation cancelled");
|
||
return DetectorResult.Failure("操作已取消 | Operation cancelled");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 确保资源清理 | Ensure resource cleanup
|
||
lock (_acquisitionLock)
|
||
{
|
||
_isAcquiring = false;
|
||
_acquisitionCts?.Dispose();
|
||
_acquisitionCts = null;
|
||
}
|
||
var errorMsg = $"停止采集异常 | Stop acquisition exception: {ex.Message}";
|
||
_logger?.Error(ex, errorMsg);
|
||
return DetectorResult.Failure(errorMsg, ex, -1);
|
||
}
|
||
}, cancellationToken);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 单帧采集内部实现 | Single frame acquisition internal implementation
|
||
/// </summary>
|
||
protected override Task<DetectorResult> AcquireSingleFrameInternalAsync(CancellationToken cancellationToken)
|
||
{
|
||
return Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
// 单帧采集前准备(含重试机制,防止连续采集停止后硬件未完全释放)
|
||
// Prepare before single frame acquisition (with retry to handle hardware not fully released after continuous acquisition)
|
||
DetectorResult prepResult = null;
|
||
const int maxRetries = 3;
|
||
const int retryDelayMs = 300;
|
||
|
||
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
||
{
|
||
prepResult = PrepareForAcquisition(XISLApi.ACQ_SNAP, 1, out uint dwRows, out uint dwColumns);
|
||
if (prepResult.IsSuccess)
|
||
{
|
||
// 采集单帧 | Acquire single frame
|
||
var result = XISLApi.Acquisition_Acquire_Image(
|
||
_hAcqDesc,
|
||
1, // 采集 1 帧 | Acquire 1 frame
|
||
0, // 跳过帧数 | Skip frames
|
||
(uint)XISLApi.HIS_SEQ_ONE_BUFFER,
|
||
_pOffsetBuffer,
|
||
_pGainAvgBuffer,
|
||
_pCorrList);
|
||
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
return DetectorResult.Failure($"单帧采集失败 | Failed to acquire single frame: {result}");
|
||
}
|
||
|
||
// 等待帧结束事件 | Wait for frame end event
|
||
uint waitResult = XISLApi.WaitForSingleObject(_hevEndFrame, 5000);
|
||
|
||
if (waitResult != XISLApi.WAIT_OBJECT_0)
|
||
{
|
||
return DetectorResult.Failure("单帧采集超时 | Single frame acquisition timeout");
|
||
}
|
||
|
||
return DetectorResult.Success("单帧采集成功 | Single frame acquired successfully");
|
||
}
|
||
|
||
// 准备失败,等待后重试 | Preparation failed, wait and retry
|
||
if (attempt < maxRetries)
|
||
{
|
||
_logger?.Warn($"单帧采集准备失败(第 {attempt} 次),{retryDelayMs}ms 后重试 | " +
|
||
$"Single frame preparation failed (attempt {attempt}), retrying in {retryDelayMs}ms: {prepResult.ErrorMessage}");
|
||
Thread.Sleep(retryDelayMs);
|
||
}
|
||
}
|
||
|
||
// 所有重试均失败 | All retries failed
|
||
_logger?.Error(null, $"单帧采集准备失败(已重试 {maxRetries} 次)| Single frame preparation failed after {maxRetries} retries: {prepResult?.ErrorMessage}");
|
||
return prepResult ?? DetectorResult.Failure("单帧采集准备失败 | Single frame preparation failed");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return DetectorResult.Failure($"单帧采集异常 | Single frame acquisition exception: {ex.Message}", ex);
|
||
}
|
||
}, cancellationToken);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 暗场校正内部实现 | Dark correction internal implementation
|
||
/// </summary>
|
||
protected override Task<DetectorResult> DarkCorrectionInternalAsync(int frameCount, CancellationToken cancellationToken)
|
||
{
|
||
return Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
_logger?.Info($"开始暗场校正,帧数:{frameCount} | Starting dark correction, frame count: {frameCount}");
|
||
|
||
// 1. 获取探测器配置信息 | Get detector configuration
|
||
uint dwFrames, dwRows, dwColumns, dwDataType, dwSortFlags;
|
||
uint dwAcqType, dwSystemID, dwSyncMode, dwHwAccess;
|
||
int iIRQFlags;
|
||
var result = XISLApi.Acquisition_GetConfiguration(
|
||
_hAcqDesc, out dwFrames, out dwRows, out dwColumns,
|
||
out dwDataType, out dwSortFlags, out iIRQFlags,
|
||
out dwAcqType, out dwSystemID, out dwSyncMode, out dwHwAccess);
|
||
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
var errorMsg = $"获取探测器配置失败 | Failed to get detector configuration: {result}";
|
||
_logger?.Error(null, errorMsg + $",返回码:{(int)result}");
|
||
return DetectorResult.Failure(errorMsg, null, (int)result);
|
||
}
|
||
|
||
// 2. 分配暗场缓冲区(如果尚未分配或大小不匹配)| Allocate offset buffer if not allocated or size mismatch
|
||
int requiredSize = (int)(dwRows * dwColumns) * sizeof(ushort);
|
||
if (_pOffsetBuffer == IntPtr.Zero)
|
||
{
|
||
_pOffsetBuffer = Marshal.AllocHGlobal(requiredSize);
|
||
_logger?.Debug($"已分配暗场缓冲区,大小:{requiredSize} 字节 | Allocated offset buffer, size: {requiredSize} bytes");
|
||
}
|
||
|
||
// 3. 调用 XISL API 采集暗场图像 | Call XISL API to acquire offset image
|
||
_logger?.Debug($"调用 Acquisition_Acquire_OffsetImage,分辨率:{dwColumns}x{dwRows} | Calling Acquisition_Acquire_OffsetImage, resolution: {dwColumns}x{dwRows}");
|
||
result = XISLApi.Acquisition_Acquire_OffsetImage(
|
||
_hAcqDesc, _pOffsetBuffer, dwRows, dwColumns, (uint)frameCount);
|
||
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
var errorMsg = $"采集暗场图像失败 | Failed to acquire offset image: {result}";
|
||
_logger?.Error(null, errorMsg + $",返回码:{(int)result}");
|
||
return DetectorResult.Failure(errorMsg, null, (int)result);
|
||
}
|
||
|
||
// 3. 等待采集完成 | Wait for acquisition to complete
|
||
uint waitResult = XISLApi.WaitForSingleObject(_hevEndAcq, 60000); // 60 秒超时
|
||
if (waitResult != XISLApi.WAIT_OBJECT_0)
|
||
{
|
||
var errorMsg = $"等待暗场采集完成超时 | Timeout waiting for offset acquisition: {waitResult}";
|
||
_logger?.Warn(errorMsg);
|
||
return DetectorResult.Failure(errorMsg, null, (int)waitResult);
|
||
}
|
||
|
||
// 4. 保存暗场数据到配置的存储路径 | Save offset data to configured storage path
|
||
if (_config.AutoSave && !string.IsNullOrEmpty(_config.SavePath))
|
||
{
|
||
try
|
||
{
|
||
string offsetFilePath = System.IO.Path.Combine(_config.SavePath, $"offset_{DateTime.Now:yyyyMMdd_HHmmss}.raw");
|
||
System.IO.Directory.CreateDirectory(_config.SavePath);
|
||
|
||
int bufferSize = (int)(dwRows * dwColumns * sizeof(ushort));
|
||
byte[] offsetData = new byte[bufferSize];
|
||
Marshal.Copy(_pOffsetBuffer, offsetData, 0, bufferSize);
|
||
System.IO.File.WriteAllBytes(offsetFilePath, offsetData);
|
||
_logger?.Info($"暗场数据已保存到:{offsetFilePath} | Offset data saved to: {offsetFilePath}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 保存失败不影响校正结果,仅记录错误 | Save failure doesn't affect correction result, just log error
|
||
var errorMsg = $"保存暗场数据失败 | Failed to save offset data: {ex.Message}";
|
||
_logger?.Warn(errorMsg);
|
||
PublishError(DetectorResult.Failure(errorMsg, ex));
|
||
}
|
||
}
|
||
|
||
// 5. 发布校正完成事件 | Publish correction completed event
|
||
var correctionResult = DetectorResult.Success("暗场校正完成 | Dark correction completed");
|
||
PublishCorrectionCompleted(CorrectionType.Dark, correctionResult);
|
||
_logger?.Info("暗场校正完成 | Dark correction completed");
|
||
|
||
return correctionResult;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var errorMsg = $"暗场校正异常 | Dark correction exception: {ex.Message}";
|
||
_logger?.Error(ex, errorMsg);
|
||
var errorResult = DetectorResult.Failure(errorMsg, ex, -1);
|
||
PublishError(errorResult);
|
||
return errorResult;
|
||
}
|
||
}, cancellationToken);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 增益校正内部实现 | Gain correction internal implementation
|
||
/// </summary>
|
||
protected override Task<DetectorResult> GainCorrectionInternalAsync(int frameCount, CancellationToken cancellationToken)
|
||
{
|
||
return Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
_logger?.Info($"开始增益校正,帧数:{frameCount} | Starting gain correction, frame count: {frameCount}");
|
||
|
||
// 1. 获取探测器配置信息 | Get detector configuration
|
||
uint dwFrames, dwRows, dwColumns, dwDataType, dwSortFlags;
|
||
uint dwAcqType, dwSystemID, dwSyncMode, dwHwAccess;
|
||
int iIRQFlags;
|
||
var result = XISLApi.Acquisition_GetConfiguration(
|
||
_hAcqDesc, out dwFrames, out dwRows, out dwColumns,
|
||
out dwDataType, out dwSortFlags, out iIRQFlags,
|
||
out dwAcqType, out dwSystemID, out dwSyncMode, out dwHwAccess);
|
||
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
var errorMsg = $"获取探测器配置失败 | Failed to get detector configuration: {result}";
|
||
_logger?.Error(null, errorMsg + $",返回码:{(int)result}");
|
||
return DetectorResult.Failure(errorMsg, null, (int)result);
|
||
}
|
||
|
||
// 2. 分配增益采集缓冲区(ushort,用于 Acquire_GainImage 输入)| Allocate gain acquisition buffer (ushort, for Acquire_GainImage)
|
||
int gainAcqSize = (int)(dwRows * dwColumns) * sizeof(ushort);
|
||
if (_pGainBuffer == IntPtr.Zero)
|
||
{
|
||
_pGainBuffer = Marshal.AllocHGlobal(gainAcqSize);
|
||
_logger?.Debug($"已分配增益采集缓冲区,大小:{gainAcqSize} 字节 | Allocated gain acquisition buffer, size: {gainAcqSize} bytes");
|
||
}
|
||
|
||
// 分配增益映射输出缓冲区(uint/DWORD,用于 CreateGainMap 输出)| Allocate gain map output buffer (uint/DWORD, for CreateGainMap)
|
||
int gainAvgSize = (int)(dwRows * dwColumns) * sizeof(uint);
|
||
if (_pGainAvgBuffer == IntPtr.Zero)
|
||
{
|
||
_pGainAvgBuffer = Marshal.AllocHGlobal(gainAvgSize);
|
||
_logger?.Debug($"已分配增益映射缓冲区,大小:{gainAvgSize} 字节 | Allocated gain map buffer, size: {gainAvgSize} bytes");
|
||
}
|
||
|
||
// 3. 调用 XISL API 采集增益图像 | Call XISL API to acquire gain image
|
||
_logger?.Debug($"调用 Acquisition_Acquire_GainImage,分辨率:{dwColumns}x{dwRows} | Calling Acquisition_Acquire_GainImage, resolution: {dwColumns}x{dwRows}");
|
||
result = XISLApi.Acquisition_Acquire_GainImage(
|
||
_hAcqDesc, _pOffsetBuffer, _pGainBuffer, dwRows, dwColumns, (uint)frameCount);
|
||
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
var errorMsg = $"采集增益图像失败 | Failed to acquire gain image: {result}";
|
||
_logger?.Error(null, errorMsg + $",返回码:{(int)result}");
|
||
return DetectorResult.Failure(errorMsg, null, (int)result);
|
||
}
|
||
|
||
// 3. 等待采集完成 | Wait for acquisition to complete
|
||
uint waitResult = XISLApi.WaitForSingleObject(_hevEndAcq, 60000); // 60 秒超时
|
||
if (waitResult != XISLApi.WAIT_OBJECT_0)
|
||
{
|
||
var errorMsg = $"等待增益采集完成超时 | Timeout waiting for gain acquisition: {waitResult}";
|
||
_logger?.Warn(errorMsg);
|
||
return DetectorResult.Failure(errorMsg, null, (int)waitResult);
|
||
}
|
||
|
||
// 4. 创建增益映射(输入为采集的 ushort 数据,输出为 DWORD 映射)| Create gain map (input: ushort data, output: DWORD map)
|
||
_logger?.Debug("创建增益映射 | Creating gain map");
|
||
result = XISLApi.Acquisition_CreateGainMap(_pGainBuffer, _pGainAvgBuffer, (int)(dwRows * dwColumns), frameCount);
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
var errorMsg = $"创建增益映射失败 | Failed to create gain map: {result}";
|
||
_logger?.Error(null, errorMsg + $",返回码:{(int)result}");
|
||
return DetectorResult.Failure(errorMsg, null, (int)result);
|
||
}
|
||
|
||
// 5. 保存增益数据到配置的存储路径 | Save gain data to configured storage path
|
||
if (_config.AutoSave && !string.IsNullOrEmpty(_config.SavePath))
|
||
{
|
||
try
|
||
{
|
||
string gainFilePath = System.IO.Path.Combine(_config.SavePath, $"gain_{DateTime.Now:yyyyMMdd_HHmmss}.raw");
|
||
System.IO.Directory.CreateDirectory(_config.SavePath);
|
||
|
||
int bufferSize = (int)(dwRows * dwColumns * sizeof(uint));
|
||
byte[] gainData = new byte[bufferSize];
|
||
Marshal.Copy(_pGainAvgBuffer, gainData, 0, bufferSize);
|
||
System.IO.File.WriteAllBytes(gainFilePath, gainData);
|
||
_logger?.Info($"增益数据已保存到:{gainFilePath} | Gain data saved to: {gainFilePath}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 保存失败不影响校正结果,仅记录错误 | Save failure doesn't affect correction result, just log error
|
||
var errorMsg = $"保存增益数据失败 | Failed to save gain data: {ex.Message}";
|
||
_logger?.Warn(errorMsg);
|
||
PublishError(DetectorResult.Failure(errorMsg, ex));
|
||
}
|
||
}
|
||
|
||
// 6. 发布校正完成事件 | Publish correction completed event
|
||
var correctionResult = DetectorResult.Success("增益校正完成 | Gain correction completed");
|
||
PublishCorrectionCompleted(CorrectionType.Gain, correctionResult);
|
||
_logger?.Info("增益校正完成 | Gain correction completed");
|
||
|
||
return correctionResult;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var errorMsg = $"增益校正异常 | Gain correction exception: {ex.Message}";
|
||
_logger?.Error(ex, errorMsg);
|
||
var errorResult = DetectorResult.Failure(errorMsg, ex, -1);
|
||
PublishError(errorResult);
|
||
return errorResult;
|
||
}
|
||
}, cancellationToken);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 自动校正内部实现 | Auto correction internal implementation
|
||
/// </summary>
|
||
protected override async Task<DetectorResult> AutoCorrectionInternalAsync(int frameCount, CancellationToken cancellationToken)
|
||
{
|
||
try
|
||
{
|
||
_logger?.Info($"开始自动校正,帧数:{frameCount} | Starting auto correction, frame count: {frameCount}");
|
||
|
||
// 1. 执行暗场校正 | Execute dark correction
|
||
_logger?.Debug("执行暗场校正 | Executing dark correction");
|
||
var darkResult = await DarkCorrectionInternalAsync(frameCount, cancellationToken);
|
||
if (!darkResult.IsSuccess)
|
||
{
|
||
var errorMsg = $"自动校正失败:暗场校正失败 | Auto correction failed: dark correction failed - {darkResult.ErrorMessage}";
|
||
_logger?.Error(null, errorMsg);
|
||
return DetectorResult.Failure(errorMsg, darkResult.Exception, darkResult.ErrorCode);
|
||
}
|
||
|
||
// 2. 执行增益校正 | Execute gain correction
|
||
_logger?.Debug("执行增益校正 | Executing gain correction");
|
||
var gainResult = await GainCorrectionInternalAsync(frameCount, cancellationToken);
|
||
if (!gainResult.IsSuccess)
|
||
{
|
||
var errorMsg = $"自动校正失败:增益校正失败 | Auto correction failed: gain correction failed - {gainResult.ErrorMessage}";
|
||
_logger?.Error(null, errorMsg);
|
||
return DetectorResult.Failure(errorMsg, gainResult.Exception, gainResult.ErrorCode);
|
||
}
|
||
|
||
// 3. 执行坏像素校正 | Execute bad pixel correction
|
||
_logger?.Debug("执行坏像素校正 | Executing bad pixel correction");
|
||
var badPixelResult = await BadPixelCorrectionInternalAsync(cancellationToken);
|
||
if (!badPixelResult.IsSuccess)
|
||
{
|
||
var errorMsg = $"自动校正失败:坏像素校正失败 | Auto correction failed: bad pixel correction failed - {badPixelResult.ErrorMessage}";
|
||
_logger?.Error(null, errorMsg);
|
||
return DetectorResult.Failure(errorMsg, badPixelResult.Exception, badPixelResult.ErrorCode);
|
||
}
|
||
|
||
// 4. 发布自动校正完成事件 | Publish auto correction completed event
|
||
var correctionResult = DetectorResult.Success("自动校正完成 | Auto correction completed");
|
||
PublishCorrectionCompleted(CorrectionType.Auto, correctionResult);
|
||
_logger?.Info("自动校正完成 | Auto correction completed");
|
||
|
||
return correctionResult;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var errorMsg = $"自动校正异常 | Auto correction exception: {ex.Message}";
|
||
_logger?.Error(ex, errorMsg);
|
||
var errorResult = DetectorResult.Failure(errorMsg, ex, -1);
|
||
PublishError(errorResult);
|
||
return errorResult;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 坏像素校正内部实现 | Bad pixel correction internal implementation
|
||
/// </summary>
|
||
protected override Task<DetectorResult> BadPixelCorrectionInternalAsync(CancellationToken cancellationToken)
|
||
{
|
||
return Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
// 1. 获取探测器配置信息 | Get detector configuration
|
||
uint dwFrames, dwRows, dwColumns, dwDataType, dwSortFlags;
|
||
uint dwAcqType, dwSystemID, dwSyncMode, dwHwAccess;
|
||
int iIRQFlags;
|
||
var result = XISLApi.Acquisition_GetConfiguration(
|
||
_hAcqDesc, out dwFrames, out dwRows, out dwColumns,
|
||
out dwDataType, out dwSortFlags, out iIRQFlags,
|
||
out dwAcqType, out dwSystemID, out dwSyncMode, out dwHwAccess);
|
||
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
return DetectorResult.Failure($"获取探测器配置失败 | Failed to get detector configuration: {result}");
|
||
}
|
||
|
||
// 2. 分配坏像素映射数据缓冲区 | Allocate bad pixel map data buffer
|
||
int pixelMapSize = (int)(dwRows * dwColumns);
|
||
IntPtr pPixelMapData = Marshal.AllocHGlobal(pixelMapSize * sizeof(ushort));
|
||
|
||
try
|
||
{
|
||
// 3. 第一次调用获取列表大小 | First call to get list size
|
||
int corrListSize = 0;
|
||
result = XISLApi.Acquisition_CreatePixelMap(
|
||
pPixelMapData, (int)dwRows, (int)dwColumns, IntPtr.Zero, ref corrListSize);
|
||
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK && corrListSize == 0)
|
||
{
|
||
Marshal.FreeHGlobal(pPixelMapData);
|
||
return DetectorResult.Failure($"获取坏像素列表大小失败 | Failed to get bad pixel list size: {result}");
|
||
}
|
||
|
||
// 4. 分配坏像素校正列表缓冲区 | Allocate bad pixel correction list buffer
|
||
if (_pCorrList != IntPtr.Zero)
|
||
{
|
||
Marshal.FreeHGlobal(_pCorrList);
|
||
}
|
||
_pCorrList = Marshal.AllocHGlobal(corrListSize);
|
||
_logger?.Debug($"已分配坏像素校正列表缓冲区,大小:{corrListSize} 字节 | Allocated bad pixel correction list buffer, size: {corrListSize} bytes");
|
||
|
||
// 5. 第二次调用创建坏像素列表 | Second call to create bad pixel list
|
||
result = XISLApi.Acquisition_CreatePixelMap(
|
||
pPixelMapData, (int)dwRows, (int)dwColumns, _pCorrList, ref corrListSize);
|
||
|
||
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
Marshal.FreeHGlobal(pPixelMapData);
|
||
return DetectorResult.Failure($"创建坏像素列表失败 | Failed to create bad pixel list: {result}");
|
||
}
|
||
|
||
// 5. 保存坏像素数据到配置的存储路径 | Save bad pixel data to configured storage path
|
||
if (_config.AutoSave && !string.IsNullOrEmpty(_config.SavePath))
|
||
{
|
||
try
|
||
{
|
||
string badPixelFilePath = System.IO.Path.Combine(_config.SavePath, $"badpixel_{DateTime.Now:yyyyMMdd_HHmmss}.raw");
|
||
System.IO.Directory.CreateDirectory(_config.SavePath);
|
||
|
||
byte[] badPixelData = new byte[corrListSize];
|
||
Marshal.Copy(_pCorrList, badPixelData, 0, corrListSize);
|
||
System.IO.File.WriteAllBytes(badPixelFilePath, badPixelData);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 保存失败不影响校正结果,仅记录错误 | Save failure doesn't affect correction result, just log error
|
||
PublishError(DetectorResult.Failure($"保存坏像素数据失败 | Failed to save bad pixel data: {ex.Message}", ex));
|
||
}
|
||
}
|
||
|
||
// 6. 发布校正完成事件 | Publish correction completed event
|
||
var correctionResult = DetectorResult.Success($"坏像素校正完成,检测到 {corrListSize} 个坏像素 | Bad pixel correction completed, detected {corrListSize} bad pixels");
|
||
PublishCorrectionCompleted(CorrectionType.BadPixel, correctionResult);
|
||
|
||
return correctionResult;
|
||
}
|
||
finally
|
||
{
|
||
// 释放临时缓冲区 | Release temporary buffer
|
||
Marshal.FreeHGlobal(pPixelMapData);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var errorResult = DetectorResult.Failure($"坏像素校正异常 | Bad pixel correction exception: {ex.Message}", ex);
|
||
PublishError(errorResult);
|
||
return errorResult;
|
||
}
|
||
}, cancellationToken);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取探测器信息 | Get detector information
|
||
/// </summary>
|
||
public override DetectorInfo GetInfo()
|
||
{
|
||
try
|
||
{
|
||
var info = new DetectorInfo
|
||
{
|
||
Type = DetectorType.Varex,
|
||
Model = "Varex 4343N", // 默认型号 | Default model
|
||
SerialNumber = "Unknown", // 序列号需要从硬件查询 | Serial number needs to be queried from hardware
|
||
FirmwareVersion = "Unknown", // 固件版本需要从硬件查询 | Firmware version needs to be queried from hardware
|
||
BitDepth = 16 // Varex 探测器使用 16 位数据 | Varex detector uses 16-bit data
|
||
};
|
||
|
||
// 如果探测器已初始化,从 XISL API 获取配置信息 | If detector is initialized, get configuration from XISL API
|
||
if (_hAcqDesc != IntPtr.Zero)
|
||
{
|
||
uint dwFrames, dwRows, dwColumns, dwDataType, dwSortFlags;
|
||
uint dwAcqType, dwSystemID, dwSyncMode, dwHwAccess;
|
||
int iIRQFlags;
|
||
|
||
var result = XISLApi.Acquisition_GetConfiguration(
|
||
_hAcqDesc, out dwFrames, out dwRows, out dwColumns,
|
||
out dwDataType, out dwSortFlags, out iIRQFlags,
|
||
out dwAcqType, out dwSystemID, out dwSyncMode, out dwHwAccess);
|
||
|
||
if (result == XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
info.MaxWidth = dwColumns;
|
||
info.MaxHeight = dwRows;
|
||
|
||
// 根据型号设置像素尺寸 | Set pixel size based on model
|
||
// Varex 4343N 的像素尺寸为 139 微米 | Varex 4343N pixel size is 139 micrometers
|
||
info.PixelSize = 139.0;
|
||
|
||
// 根据 SystemID 确定具体型号 | Determine specific model based on SystemID
|
||
info.Model = dwSystemID switch
|
||
{
|
||
_ => "Varex 4343N" // 默认型号 | Default model
|
||
};
|
||
}
|
||
else
|
||
{
|
||
// 如果无法获取配置,使用默认值 | If configuration cannot be obtained, use default values
|
||
info.MaxWidth = 2880;
|
||
info.MaxHeight = 2880;
|
||
info.PixelSize = 139.0;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 探测器未初始化,使用默认值 | Detector not initialized, use default values
|
||
info.MaxWidth = 2880;
|
||
info.MaxHeight = 2880;
|
||
info.PixelSize = 139.0;
|
||
}
|
||
|
||
return info;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 发生异常时返回基本信息 | Return basic information when exception occurs
|
||
return new DetectorInfo
|
||
{
|
||
Type = DetectorType.Varex,
|
||
Model = "Varex (Error)",
|
||
SerialNumber = $"Error: {ex.Message}",
|
||
FirmwareVersion = "Unknown",
|
||
MaxWidth = 2880,
|
||
MaxHeight = 2880,
|
||
PixelSize = 139.0,
|
||
BitDepth = 16
|
||
};
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region IVarexDetector 接口实现(占位符)| IVarexDetector Interface Implementations (Placeholders)
|
||
|
||
/// <summary>
|
||
/// 设置 Binning 模式 | Set binning mode
|
||
/// </summary>
|
||
public Task<DetectorResult> SetBinningModeAsync(BinningMode mode)
|
||
{
|
||
return Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
var result = XISLApi.Acquisition_SetCameraBinningMode(_hAcqDesc, (uint)mode + 1);
|
||
if (result == XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
_binningMode = mode;
|
||
return DetectorResult.Success($"Binning 模式已设置为 {mode} | Binning mode set to {mode}");
|
||
}
|
||
return DetectorResult.Failure($"设置 Binning 模式失败 | Failed to set binning mode: {result}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return DetectorResult.Failure($"设置 Binning 模式异常 | Set binning mode exception: {ex.Message}", ex);
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 Binning 模式 | Get binning mode
|
||
/// </summary>
|
||
public BinningMode GetBinningMode()
|
||
{
|
||
return _binningMode;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置增益模式 | Set gain mode
|
||
/// </summary>
|
||
public Task<DetectorResult> SetGainModeAsync(GainMode mode)
|
||
{
|
||
return Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
var result = XISLApi.Acquisition_SetCameraGain(_hAcqDesc, (uint)mode);
|
||
if (result == XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
_gainMode = mode;
|
||
return DetectorResult.Success($"增益模式已设置为 {mode} | Gain mode set to {mode}");
|
||
}
|
||
return DetectorResult.Failure($"设置增益模式失败 | Failed to set gain mode: {result}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return DetectorResult.Failure($"设置增益模式异常 | Set gain mode exception: {ex.Message}", ex);
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取增益模式 | Get gain mode
|
||
/// </summary>
|
||
public GainMode GetGainMode()
|
||
{
|
||
return _gainMode;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置曝光时间 | Set exposure time
|
||
/// </summary>
|
||
public Task<DetectorResult> SetExposureTimeAsync(uint milliseconds)
|
||
{
|
||
return Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
uint exposureTime = milliseconds;
|
||
var result = XISLApi.Acquisition_SetTimerSync(_hAcqDesc, ref exposureTime);
|
||
if (result == XISLApi.HIS_RETURN.HIS_ALL_OK)
|
||
{
|
||
_exposureTime = milliseconds;
|
||
return DetectorResult.Success($"曝光时间已设置为 {milliseconds} ms | Exposure time set to {milliseconds} ms");
|
||
}
|
||
return DetectorResult.Failure($"设置曝光时间失败 | Failed to set exposure time: {result}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return DetectorResult.Failure($"设置曝光时间异常 | Set exposure time exception: {ex.Message}", ex);
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取曝光时间 | Get exposure time
|
||
/// </summary>
|
||
public uint GetExposureTime()
|
||
{
|
||
return _exposureTime;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置 ROI 区域 | Set ROI region
|
||
/// </summary>
|
||
public Task<DetectorResult> SetROIAsync(uint x, uint y, uint width, uint height)
|
||
{
|
||
return Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
// 注意:XISL API 可能需要特定的 ROI 设置函数
|
||
// 这里暂时只更新内部状态,实际 API 调用需要根据 XISL 文档补充
|
||
// Note: XISL API may require specific ROI setting functions
|
||
// Currently only updating internal state, actual API call needs to be added based on XISL documentation
|
||
_roi = (x, y, width, height);
|
||
return DetectorResult.Success($"ROI 区域已设置 | ROI region set: ({x}, {y}, {width}x{height})");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return DetectorResult.Failure($"设置 ROI 区域异常 | Set ROI region exception: {ex.Message}", ex);
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 ROI 区域 | Get ROI region
|
||
/// </summary>
|
||
public (uint x, uint y, uint width, uint height) GetROI()
|
||
{
|
||
return _roi;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 回调函数(占位符)| Callback Functions (Placeholders)
|
||
|
||
/// <summary>
|
||
/// 帧结束回调函数 | End frame callback
|
||
/// </summary>
|
||
/// <param name="hAcqDesc">采集描述符句柄 | Acquisition descriptor handle</param>
|
||
private void OnEndFrameCallback(IntPtr hAcqDesc)
|
||
{
|
||
try
|
||
{
|
||
uint dwRows = _cachedRows;
|
||
uint dwColumns = _cachedColumns;
|
||
int imageSize = (int)(dwRows * dwColumns);
|
||
|
||
if (imageSize <= 0) return;
|
||
|
||
// 分配独立数组(由消费者负责生命周期,避免 ArrayPool 竞态)
|
||
// Allocate independent array (consumer owns lifetime, avoids ArrayPool race condition)
|
||
ushort[] buffer = new ushort[imageSize];
|
||
|
||
// 拷贝图像数据 | Copy image data
|
||
unsafe
|
||
{
|
||
fixed (ushort* pDest = buffer)
|
||
{
|
||
Buffer.MemoryCopy(
|
||
(void*)_pAcqBuffer,
|
||
pDest,
|
||
imageSize * sizeof(ushort),
|
||
imageSize * sizeof(ushort));
|
||
}
|
||
}
|
||
|
||
// 获取当前帧号 | Get current frame number
|
||
int frameNumber = GetCurrentFrameNumber();
|
||
|
||
// 创建事件参数 | Create event arguments
|
||
var eventArgs = new ImageCapturedEventArgs
|
||
{
|
||
ImageData = buffer,
|
||
Width = dwColumns,
|
||
Height = dwRows,
|
||
FrameNumber = frameNumber,
|
||
CaptureTime = DateTime.Now,
|
||
ExposureTime = _exposureTime,
|
||
SavePath = _config.SavePath
|
||
};
|
||
|
||
// 发布图像采集事件 | Publish image captured event
|
||
PublishImageCaptured(eventArgs);
|
||
_logger?.Debug($"图像采集完成,帧号:{frameNumber},分辨率:{dwColumns}x{dwRows} | Image captured, frame: {frameNumber}, resolution: {dwColumns}x{dwRows}");
|
||
|
||
// 设置帧结束事件 | Set frame end event
|
||
if (_hevEndFrame != IntPtr.Zero)
|
||
{
|
||
XISLApi.SetEvent(_hevEndFrame);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var errorMsg = $"回调函数异常 | Callback exception: {ex.Message}";
|
||
_logger?.Error(ex, errorMsg);
|
||
var errorResult = DetectorResult.Failure(errorMsg, ex, -1);
|
||
PublishError(errorResult);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 采集结束回调函数 | End acquisition callback
|
||
/// </summary>
|
||
/// <param name="hAcqDesc">采集描述符句柄 | Acquisition descriptor handle</param>
|
||
private void OnEndAcqCallback(IntPtr hAcqDesc)
|
||
{
|
||
try
|
||
{
|
||
// 重置板载选项 | Reset onboard options
|
||
XISLApi.Acquisition_Reset_OnboardOptions(hAcqDesc);
|
||
|
||
// 设置采集结束事件 | Set acquisition end event
|
||
if (_hevEndAcq != IntPtr.Zero)
|
||
{
|
||
XISLApi.SetEvent(_hevEndAcq);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var errorResult = DetectorResult.Failure($"采集结束回调异常 | End acquisition callback exception: {ex.Message}", ex);
|
||
PublishError(errorResult);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 资源释放 | Resource Disposal
|
||
|
||
/// <summary>
|
||
/// 释放资源 | Dispose resources
|
||
/// </summary>
|
||
/// <param name="disposing">是否释放托管资源 | Whether to dispose managed resources</param>
|
||
protected override void Dispose(bool disposing)
|
||
{
|
||
if (!_disposed)
|
||
{
|
||
_logger?.Info("开始释放 VarexDetector 资源 | Starting to dispose VarexDetector resources");
|
||
|
||
if (disposing)
|
||
{
|
||
// 释放托管资源 | Release managed resources
|
||
try
|
||
{
|
||
// 取消并释放采集取消令牌源 | Cancel and dispose acquisition cancellation token source
|
||
_acquisitionCts?.Cancel();
|
||
_acquisitionCts?.Dispose();
|
||
_acquisitionCts = null;
|
||
_logger?.Debug("采集取消令牌源已释放 | Acquisition cancellation token source disposed");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 记录异常但继续释放其他资源 | Log exception but continue disposing other resources
|
||
_logger?.Warn($"释放 CancellationTokenSource 时发生异常 | Exception disposing CancellationTokenSource: {ex.Message}");
|
||
System.Diagnostics.Debug.WriteLine($"释放 CancellationTokenSource 时发生异常 | Exception disposing CancellationTokenSource: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 释放非托管资源 | Release unmanaged resources
|
||
try
|
||
{
|
||
// 停止采集 | Stop acquisition
|
||
if (_hAcqDesc != IntPtr.Zero && _isAcquiring)
|
||
{
|
||
XISLApi.Acquisition_Abort(_hAcqDesc);
|
||
_isAcquiring = false;
|
||
_logger?.Debug("采集已停止 | Acquisition stopped");
|
||
}
|
||
|
||
// 关闭所有连接 | Close all connections
|
||
if (_hAcqDesc != IntPtr.Zero)
|
||
{
|
||
XISLApi.Acquisition_CloseAll();
|
||
_hAcqDesc = IntPtr.Zero;
|
||
_logger?.Debug("XISL 连接已关闭 | XISL connections closed");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger?.Warn($"关闭 XISL 连接时发生异常 | Exception closing XISL connections: {ex.Message}");
|
||
System.Diagnostics.Debug.WriteLine($"关闭 XISL 连接时发生异常 | Exception closing XISL connections: {ex.Message}");
|
||
}
|
||
|
||
// 释放图像缓冲区 | Free image buffers
|
||
try
|
||
{
|
||
if (_pAcqBuffer != IntPtr.Zero)
|
||
{
|
||
Marshal.FreeHGlobal(_pAcqBuffer);
|
||
_pAcqBuffer = IntPtr.Zero;
|
||
}
|
||
|
||
if (_pOffsetBuffer != IntPtr.Zero)
|
||
{
|
||
Marshal.FreeHGlobal(_pOffsetBuffer);
|
||
_pOffsetBuffer = IntPtr.Zero;
|
||
}
|
||
|
||
if (_pGainBuffer != IntPtr.Zero)
|
||
{
|
||
Marshal.FreeHGlobal(_pGainBuffer);
|
||
_pGainBuffer = IntPtr.Zero;
|
||
}
|
||
|
||
if (_pGainAvgBuffer != IntPtr.Zero)
|
||
{
|
||
Marshal.FreeHGlobal(_pGainAvgBuffer);
|
||
_pGainAvgBuffer = IntPtr.Zero;
|
||
}
|
||
|
||
if (_pCorrList != IntPtr.Zero)
|
||
{
|
||
Marshal.FreeHGlobal(_pCorrList);
|
||
_pCorrList = IntPtr.Zero;
|
||
}
|
||
_logger?.Debug("图像缓冲区已释放 | Image buffers freed");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger?.Warn($"释放缓冲区时发生异常 | Exception freeing buffers: {ex.Message}");
|
||
System.Diagnostics.Debug.WriteLine($"释放缓冲区时发生异常 | Exception freeing buffers: {ex.Message}");
|
||
}
|
||
|
||
// 释放事件句柄 | Free event handles
|
||
try
|
||
{
|
||
if (_hevEndFrame != IntPtr.Zero)
|
||
{
|
||
XISLApi.CloseHandle(_hevEndFrame);
|
||
_hevEndFrame = IntPtr.Zero;
|
||
}
|
||
|
||
if (_hevEndAcq != IntPtr.Zero)
|
||
{
|
||
XISLApi.CloseHandle(_hevEndAcq);
|
||
_hevEndAcq = IntPtr.Zero;
|
||
}
|
||
_logger?.Debug("事件句柄已释放 | Event handles freed");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger?.Warn($"释放事件句柄时发生异常 | Exception freeing event handles: {ex.Message}");
|
||
System.Diagnostics.Debug.WriteLine($"释放事件句柄时发生异常 | Exception freeing event handles: {ex.Message}");
|
||
}
|
||
|
||
_logger?.Info("VarexDetector 资源释放完成 | VarexDetector resources disposed");
|
||
|
||
// 调用基类的 Dispose 方法 | Call base class Dispose method
|
||
base.Dispose(disposing);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|