已合并 PR 89: 探测器Hardware.Detector兼容多探测器的重构

探测器XP.Hardware.Detector类库为了更好集成新的探测器,统一接口方法,DetectorService重构为通过统一接口;
新增暗场校正和亮场校正帧数配置属性(默认 64,范围 1-128),config 加载校正帧数;
修正探测器IsConnected连接状态的判断逻辑。
This commit is contained in:
QI Mingxuan
2026-05-21 13:30:59 +08:00
12 changed files with 333 additions and 69 deletions
@@ -386,6 +386,44 @@ namespace XP.Hardware.Detector.Abstractions
/// </summary>
public abstract DetectorInfo GetInfo();
/// <summary>
/// 应用探测器参数 | Apply detector parameters
/// </summary>
public async Task<DetectorResult> ApplyParametersAsync(int binningIndex, int pga, decimal frameRate, CancellationToken cancellationToken = default)
{
if (Status != DetectorStatus.Ready)
{
return DetectorResult.Failure($"探测器状态不正确,当前状态:{Status} | Detector status incorrect, current status: {Status}");
}
try
{
return await ApplyParametersInternalAsync(binningIndex, pga, frameRate, cancellationToken);
}
catch (Exception ex)
{
var errorResult = DetectorResult.Failure($"应用参数异常 | Apply parameters exception: {ex.Message}", ex);
PublishError(errorResult);
return errorResult;
}
}
/// <summary>
/// 获取校正能力描述(子类可重写)| Get correction capabilities (subclass can override)
/// </summary>
public virtual CorrectionCapabilities GetCorrectionCapabilities()
{
return new CorrectionCapabilities();
}
/// <summary>
/// 显式失效校正数据(子类可重写)| Explicitly invalidate correction data (subclass can override)
/// </summary>
public virtual void InvalidateCorrectionData()
{
// 默认空实现,子类按需重写 | Default empty implementation, subclass overrides as needed
}
// 模板方法,由子类实现 | Template methods, implemented by derived classes
protected abstract Task<DetectorResult> InitializeInternalAsync(CancellationToken cancellationToken);
protected abstract Task<DetectorResult> StartAcquisitionInternalAsync(CancellationToken cancellationToken);
@@ -395,6 +433,7 @@ namespace XP.Hardware.Detector.Abstractions
protected abstract Task<DetectorResult> GainCorrectionInternalAsync(int frameCount, CancellationToken cancellationToken);
protected abstract Task<DetectorResult> AutoCorrectionInternalAsync(int frameCount, CancellationToken cancellationToken);
protected abstract Task<DetectorResult> BadPixelCorrectionInternalAsync(CancellationToken cancellationToken);
protected abstract Task<DetectorResult> ApplyParametersInternalAsync(int binningIndex, int pga, decimal frameRate, CancellationToken cancellationToken);
/// <summary>
/// 更新状态并发布事件 | Update status and publish event
@@ -0,0 +1,49 @@
namespace XP.Hardware.Detector.Abstractions
{
/// <summary>
/// 校正能力描述 | Correction capabilities description
/// 描述探测器支持的校正行为和参数范围,不同探测器可返回不同配置
/// </summary>
public class CorrectionCapabilities
{
/// <summary>
/// 是否需要在校正前停止采集 | Whether to stop acquisition before correction
/// </summary>
public bool RequiresStopBeforeCorrection { get; set; } = true;
/// <summary>
/// 是否需要在暗场校正前应用参数 | Whether to apply parameters before dark correction
/// </summary>
public bool RequiresParameterApplyBeforeDark { get; set; } = true;
/// <summary>
/// 亮场校正后是否自动执行坏像素校正 | Auto bad pixel correction after gain correction
/// </summary>
public bool AutoBadPixelAfterGain { get; set; } = true;
/// <summary>
/// 停止采集后等待时间(ms| Post-stop delay (ms)
/// </summary>
public int PostStopDelayMs { get; set; } = 500;
/// <summary>
/// 暗场校正帧数(从配置文件加载)| Dark correction frame count (loaded from config)
/// </summary>
public int DarkFrameCount { get; set; } = 64;
/// <summary>
/// 亮场校正帧数(从配置文件加载)| Gain correction frame count (loaded from config)
/// </summary>
public int GainFrameCount { get; set; } = 64;
/// <summary>
/// 校正帧数最小值 | Correction frame count minimum
/// </summary>
public int FrameCountMin { get; set; } = 1;
/// <summary>
/// 校正帧数最大值 | Correction frame count maximum
/// </summary>
public int FrameCountMax { get; set; } = 128;
}
}
@@ -85,5 +85,26 @@ namespace XP.Hardware.Detector.Abstractions
/// </summary>
/// <returns>探测器信息 | Detector information</returns>
DetectorInfo GetInfo();
/// <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>
/// 获取校正能力描述 | Get correction capabilities
/// </summary>
/// <returns>校正能力描述 | Correction capabilities</returns>
CorrectionCapabilities GetCorrectionCapabilities();
/// <summary>
/// 显式失效校正数据(参数变更后调用)| Explicitly invalidate correction data (called after parameter change)
/// </summary>
void InvalidateCorrectionData();
}
}
@@ -48,6 +48,16 @@ namespace XP.Hardware.Detector.Config
config.SavePath = ConfigurationManager.AppSettings["Detector:SavePath"] ?? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Images");
config.AutoSave = bool.TryParse(ConfigurationManager.AppSettings["Detector:AutoSave"], out var autoSave) && autoSave;
// 加载校正帧数配置(钳位到 1-128| Load correction frame count config (clamp to 1-128)
if (int.TryParse(ConfigurationManager.AppSettings["Detector:Correction:DarkFrameCount"], out var darkFrames))
{
config.DarkCorrectionFrameCount = Math.Clamp(darkFrames, 1, 128);
}
if (int.TryParse(ConfigurationManager.AppSettings["Detector:Correction:GainFrameCount"], out var gainFrames))
{
config.GainCorrectionFrameCount = Math.Clamp(gainFrames, 1, 128);
}
// 验证配置 | Validate configuration
var validationResult = ValidateConfiguration(config);
if (!validationResult.IsSuccess)
@@ -34,6 +34,16 @@ namespace XP.Hardware.Detector.Config
/// </summary>
public bool AutoSave { get; set; }
/// <summary>
/// 暗场校正帧数(1-128,默认 64| Dark correction frame count (1-128, default 64)
/// </summary>
public int DarkCorrectionFrameCount { get; set; } = 64;
/// <summary>
/// 亮场校正帧数(1-128,默认 64| Gain correction frame count (1-128, default 64)
/// </summary>
public int GainCorrectionFrameCount { get; set; } = 64;
/// <summary>
/// 获取支持的 Binning 选项(显示名称 → 索引)| Get supported binning options (display name → index)
/// 子类可重写以提供不同的选项列表
@@ -111,6 +111,15 @@ namespace XP.Hardware.Detector.Implementations
throw new NotImplementedException("iRay 探测器坏像素校正尚未实现 | iRay detector bad pixel correction not implemented yet");
}
/// <summary>
/// 应用参数(内部实现)| Apply parameters (internal implementation)
/// </summary>
protected override Task<DetectorResult> ApplyParametersInternalAsync(int binningIndex, int pga, decimal frameRate, CancellationToken cancellationToken)
{
// TODO: 实现 iRay 探测器参数应用逻辑 | Implement iRay detector parameter application logic
throw new NotImplementedException("iRay 探测器参数应用尚未实现 | iRay detector parameter application not implemented yet");
}
/// <summary>
/// 获取探测器信息 | Get detector information
/// </summary>
@@ -1155,6 +1155,115 @@ namespace XP.Hardware.Detector.Implementations
#endregion
#region | Unified Interface Implementations
/// <summary>
/// 应用参数内部实现 | Apply parameters internal implementation
/// </summary>
protected override Task<DetectorResult> ApplyParametersInternalAsync(int binningIndex, int pga, decimal frameRate, CancellationToken cancellationToken)
{
return Task.Run(() =>
{
try
{
_logger?.Info($"应用参数:Binning={binningIndex}PGA={pga},帧率={frameRate} | Applying parameters: Binning={binningIndex}, PGA={pga}, FrameRate={frameRate}");
// 设置 Binning 模式 | Set binning mode
var binningMode = (BinningMode)binningIndex;
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}");
}
// Binning 变化时失效校正数据 | Invalidate correction data on binning change
if (_binningMode != binningMode)
{
_logger?.Info($"Binning 模式从 {_binningMode} 变更为 {binningMode},校正数据已失效 | Binning changed, correction data invalidated");
InvalidateCorrectionData();
}
_binningMode = binningMode;
// 设置增益模式 | Set gain mode
var gainMode = (GainMode)pga;
result = XISLApi.Acquisition_SetCameraGain(_hAcqDesc, (uint)gainMode);
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
{
return DetectorResult.Failure($"设置增益模式失败 | Failed to set gain mode: {result}");
}
// PGA 变化时失效校正数据 | Invalidate correction data on PGA change
if (_gainMode != gainMode)
{
_logger?.Info($"PGA 从 {_gainMode} 变更为 {gainMode},校正数据已失效 | PGA changed, correction data invalidated");
InvalidateCorrectionData();
}
_gainMode = gainMode;
// 设置曝光时间(帧率→微秒)| Set exposure time (frame rate → microseconds)
uint exposureUs = frameRate > 0 ? (uint)(1_000_000m / frameRate) : 66667;
result = XISLApi.Acquisition_SetTimerSync(_hAcqDesc, ref exposureUs);
if (result != XISLApi.HIS_RETURN.HIS_ALL_OK)
{
return DetectorResult.Failure($"设置曝光时间失败 | Failed to set exposure time: {result}");
}
_exposureTime = exposureUs;
_logger?.Info("参数应用成功 | Parameters applied successfully");
return DetectorResult.Success("参数应用成功 | Parameters applied successfully");
}
catch (Exception ex)
{
return DetectorResult.Failure($"应用参数异常 | Apply parameters exception: {ex.Message}", ex);
}
}, cancellationToken);
}
/// <summary>
/// 获取 Varex 校正能力描述 | Get Varex correction capabilities
/// </summary>
public override CorrectionCapabilities GetCorrectionCapabilities()
{
return new CorrectionCapabilities
{
RequiresStopBeforeCorrection = true,
RequiresParameterApplyBeforeDark = true,
AutoBadPixelAfterGain = true,
PostStopDelayMs = 500,
DarkFrameCount = _config.DarkCorrectionFrameCount,
GainFrameCount = _config.GainCorrectionFrameCount,
FrameCountMin = 1,
FrameCountMax = 128
};
}
/// <summary>
/// 失效校正数据(释放校正缓冲区)| Invalidate correction data (free correction buffers)
/// </summary>
public override void InvalidateCorrectionData()
{
if (_pOffsetBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(_pOffsetBuffer);
_pOffsetBuffer = IntPtr.Zero;
_offsetBufferRows = 0;
_offsetBufferColumns = 0;
}
if (_pGainBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(_pGainBuffer);
_pGainBuffer = IntPtr.Zero;
}
if (_pCorrList != IntPtr.Zero)
{
Marshal.FreeHGlobal(_pCorrList);
_pCorrList = IntPtr.Zero;
}
_logger?.Debug("校正数据已失效并释放 | Correction data invalidated and freed");
}
#endregion
#region IVarexDetector | IVarexDetector Interface Implementations (Placeholders)
/// <summary>
@@ -62,7 +62,11 @@ namespace XP.Hardware.Detector.Services
{
lock (_lock)
{
return _detector != null && _detector.Status != DetectorStatus.Uninitialized;
if (_detector == null) return false;
var status = _detector.Status;
return status == DetectorStatus.Ready
|| status == DetectorStatus.Acquiring
|| status == DetectorStatus.Correcting;
}
}
}
@@ -509,64 +513,44 @@ namespace XP.Hardware.Detector.Services
var detector = GetDetectorOrThrow();
// 通过 IVarexDetector 接口下发参数 | Apply parameters via IVarexDetector interface
if (detector is IVarexDetector varexDetector)
// 如果正在采集,先停止 | Stop acquisition first if running
bool wasAcquiring = detector.Status == DetectorStatus.Acquiring;
if (wasAcquiring)
{
// 如果正在采集,先停止(XISL SDK 不允许采集中修改参数| Stop acquisition first if running (XISL SDK does not allow parameter changes during acquisition)
bool wasAcquiring = detector.Status == DetectorStatus.Acquiring;
if (wasAcquiring)
_logger?.Info("探测器正在采集,先停止采集再应用参数 | Detector is acquiring, stopping before applying parameters");
var stopResult = await detector.StopAcquisitionAsync(cancellationToken);
if (!stopResult.IsSuccess)
{
_logger?.Info("探测器正在采集,先停止采集再应用参数 | Detector is acquiring, stopping before applying parameters");
var stopResult = await detector.StopAcquisitionAsync(cancellationToken);
if (!stopResult.IsSuccess)
{
_lastError = stopResult;
_logger?.Error(stopResult.Exception, "停止采集失败,无法应用参数:{Message} | Failed to stop acquisition, cannot apply parameters: {Message}", stopResult.ErrorMessage);
return DetectorResult.Failure($"停止采集失败,无法应用参数 | Failed to stop acquisition, cannot apply parameters: {stopResult.ErrorMessage}");
}
_lastError = stopResult;
_logger?.Error(stopResult.Exception, "停止采集失败,无法应用参数:{Message} | Failed to stop acquisition, cannot apply parameters: {Message}", stopResult.ErrorMessage);
return DetectorResult.Failure($"停止采集失败,无法应用参数 | Failed to stop acquisition, cannot apply parameters: {stopResult.ErrorMessage}");
}
// 设置 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;
}
// 如果之前在采集,恢复采集 | Resume acquisition if it was running before
if (wasAcquiring)
{
_logger?.Info("参数应用完成,恢复连续采集 | Parameters applied, resuming continuous acquisition");
var startResult = await detector.StartAcquisitionAsync(cancellationToken);
if (!startResult.IsSuccess)
{
_logger?.Warn("恢复采集失败:{Message}(参数已成功应用)| Failed to resume acquisition: {Message} (parameters were applied successfully)", startResult.ErrorMessage);
}
}
_logger?.Info("参数应用成功 | Parameters applied successfully");
return DetectorResult.Success("参数应用成功 | Parameters applied successfully");
}
return DetectorResult.Failure("当前探测器不支持参数下发 | Current detector does not support parameter application");
// 通过统一接口下发参数(不依赖具体探测器类型)| Apply parameters via unified interface (no dependency on specific detector type)
var result = await detector.ApplyParametersAsync(binningIndex, pga, frameRate, cancellationToken);
if (!result.IsSuccess)
{
_lastError = result;
_logger?.Error(result.Exception, "应用参数失败:{Message} | Apply parameters failed: {Message}", result.ErrorMessage);
}
else
{
_logger?.Info("参数应用成功 | Parameters applied successfully");
}
// 如果之前在采集,恢复采集 | Resume acquisition if it was running before
if (wasAcquiring)
{
_logger?.Info("参数应用完成,恢复连续采集 | Parameters applied, resuming continuous acquisition");
var startResult = await detector.StartAcquisitionAsync(cancellationToken);
if (!startResult.IsSuccess)
{
_logger?.Warn("恢复采集失败:{Message}(参数已成功应用)| Failed to resume acquisition: {Message} (parameters were applied successfully)", startResult.ErrorMessage);
}
}
return result;
}
catch (Exception ex)
{
@@ -640,6 +624,29 @@ namespace XP.Hardware.Detector.Services
return null;
}
/// <summary>
/// 获取当前探测器的校正能力描述 | Get correction capabilities of current detector
/// 未初始化时返回基于配置文件的默认值
/// </summary>
public CorrectionCapabilities GetCorrectionCapabilities()
{
lock (_lock)
{
if (_detector != null)
{
return _detector.GetCorrectionCapabilities();
}
}
// 未初始化时从配置文件构建默认值 | Build default from config when not initialized
var config = GetCurrentConfig();
return new CorrectionCapabilities
{
DarkFrameCount = config?.DarkCorrectionFrameCount ?? 64,
GainFrameCount = config?.GainCorrectionFrameCount ?? 64
};
}
/// <summary>
/// 获取探测器实例或抛出异常 | Get detector instance or throw exception
/// </summary>
@@ -130,5 +130,11 @@ namespace XP.Hardware.Detector.Services
/// </summary>
/// <returns>探测器配置,未初始化时返回 null | Detector config, null if not initialized</returns>
DetectorConfig GetCurrentConfig();
/// <summary>
/// 获取当前探测器的校正能力描述 | Get correction capabilities of current detector
/// </summary>
/// <returns>校正能力描述,未初始化时返回默认值 | Correction capabilities, default if not initialized</returns>
CorrectionCapabilities GetCorrectionCapabilities();
}
}
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
@@ -8,6 +8,7 @@ using Prism.Mvvm;
using XP.Common.GeneralForm.Views;
using XP.Common.Localization;
using XP.Common.Logging.Interfaces;
using XP.Hardware.Detector.Abstractions;
using XP.Hardware.Detector.Abstractions.Events;
using XP.Hardware.Detector.Abstractions.Enums;
using XP.Hardware.Detector.Config;
@@ -420,8 +421,10 @@ namespace XP.Hardware.Detector.ViewModels
/// </summary>
private void OnDetectorStatusChanged(DetectorStatus status)
{
// 同步连接状态:非 Uninitialized 即视为已连接 | Sync connection status: connected if not Uninitialized
IsConnected = status != DetectorStatus.Uninitialized;
// 同步连接状态:只有 Ready、Acquiring、Correcting 视为已连接 | Only Ready, Acquiring, Correcting are considered connected
IsConnected = status == DetectorStatus.Ready
|| status == DetectorStatus.Acquiring
|| status == DetectorStatus.Correcting;
}
/// <summary>
@@ -476,9 +479,9 @@ namespace XP.Hardware.Detector.ViewModels
}
/// <summary>
/// 校正采集帧数(固定 64 帧,确保校正质量)| Correction frame count (fixed 64 frames for quality)
/// 获取校正帧数(从配置加载)| Get correction frame count (loaded from config)
/// </summary>
private const int CorrectionFrameCount = 64;
private CorrectionCapabilities GetCorrectionCaps() => _detectorService.GetCorrectionCapabilities();
/// <summary>
/// 执行暗场校正 | Execute dark correction
@@ -500,7 +503,7 @@ namespace XP.Hardware.Detector.ViewModels
var binningName = _selectedBinningIndex < BinningItems.Count ? BinningItems[_selectedBinningIndex].DisplayName : "?";
_logger?.Info("开始暗场校正,Binning={Binning}PGA={PGA},帧率={FrameRate},校正帧数={FrameCount} | Starting dark correction",
binningName, _selectedPga, _frameRate, CorrectionFrameCount);
binningName, _selectedPga, _frameRate, GetCorrectionCaps().DarkFrameCount);
// 显示进度条窗口 | Show progress window
var progressWindow = new ProgressWindow(
@@ -536,9 +539,9 @@ namespace XP.Hardware.Detector.ViewModels
return;
}
// 3. 执行暗场校正(固定 64 帧)| Execute dark correction (fixed 64 frames)
// 3. 执行暗场校正 | Execute dark correction
progressWindow.UpdateProgress(LocalizationHelper.Get("Detector_Progress_AcquiringDarkData"), 30);
var result = await _detectorService.DarkCorrectionAsync(CorrectionFrameCount);
var result = await _detectorService.DarkCorrectionAsync(GetCorrectionCaps().DarkFrameCount);
if (result.IsSuccess)
{
@@ -616,7 +619,7 @@ namespace XP.Hardware.Detector.ViewModels
var binningName = _selectedBinningIndex < BinningItems.Count ? BinningItems[_selectedBinningIndex].DisplayName : "?";
_logger?.Info("开始亮场校正,Binning={Binning}PGA={PGA},帧率={FrameRate},校正帧数={FrameCount} | Starting light correction",
binningName, _selectedPga, _frameRate, CorrectionFrameCount);
binningName, _selectedPga, _frameRate, GetCorrectionCaps().GainFrameCount);
// 显示进度条窗口 | Show progress window
var progressWindow = new ProgressWindow(
@@ -643,9 +646,9 @@ namespace XP.Hardware.Detector.ViewModels
}
}
// 1. 执行亮场校正(固定 64 帧)| Execute light correction (fixed 64 frames)
// 1. 执行亮场校正 | Execute light correction
progressWindow.UpdateProgress(LocalizationHelper.Get("Detector_Progress_AcquiringLightData"), 20);
var result = await _detectorService.GainCorrectionAsync(CorrectionFrameCount);
var result = await _detectorService.GainCorrectionAsync(GetCorrectionCaps().GainFrameCount);
if (result.IsSuccess)
{
@@ -97,6 +97,7 @@
<telerik:RadButton Grid.Column="1"
Content="{loc:Localization Detector_ApplyParametersButton}"
Command="{Binding ApplyParametersCommand}"
IsEnabled="{Binding IsParametersEditable}"
Height="32" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal" Margin="4 0 0 0"/>
</Grid>
@@ -115,6 +116,7 @@
<telerik:RadButton Grid.Column="0"
Content="{loc:Localization Detector_DarkCorrectionButton}"
Command="{Binding DarkCorrectionCommand}"
IsEnabled="{Binding IsParametersEditable}"
Height="32" Margin="0 0 4 0" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal">
<telerik:RadButton.Background>
@@ -129,6 +131,7 @@
<telerik:RadButton Grid.Column="1"
Content="{loc:Localization Detector_LightCorrectionButton}"
Command="{Binding LightCorrectionCommand}"
IsEnabled="{Binding IsParametersEditable}"
Height="32" Margin="4 0 0 0" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal">
<telerik:RadButton.Background>
+2 -4
View File
@@ -66,7 +66,6 @@
<add key="Detector:Port" value="5000" />
<add key="Detector:SavePath" value="D:\XplorePlane\DetectorImages" />
<add key="Detector:AutoSave" value="true" />
<!-- Varex 探测器专属配置 | Varex Detector Specific Configuration -->
<!-- Binning 模式: Bin1x1, Bin2x2, Bin4x4 | Binning mode: Bin1x1, Bin2x2, Bin4x4 -->
<add key="Detector:Varex:BinningMode" value="Bin1x1" />
<!-- 增益模式: Low, High | Gain mode: Low, High -->
@@ -78,14 +77,13 @@
<add key="Detector:Varex:ROI_Y" value="0" />
<add key="Detector:Varex:ROI_Width" value="2880" />
<add key="Detector:Varex:ROI_Height" value="2880" />
<!-- iRay 探测器专属配置 | iRay Detector Specific Configuration -->
<!-- 采集模式: Continuous, SingleFrame | Acquisition mode: Continuous, SingleFrame -->
<add key="Detector:IRay:AcquisitionMode" value="Continuous" />
<!-- 默认增益值 | Default gain value -->
<add key="Detector:IRay:DefaultGain" value="1.0" />
<!-- 校正配置 | Correction Configuration -->
<add key="Detector:Correction:DarkFrameCount" value="10" />
<add key="Detector:Correction:GainFrameCount" value="10" />
<add key="Detector:Correction:DarkFrameCount" value="64" />
<add key="Detector:Correction:GainFrameCount" value="64" />
<add key="Detector:Correction:SaveCorrectionData" value="true" />
<!-- 操作超时配置 | Operation Timeout Configuration -->
<add key="Detector:InitializationTimeout" value="30000" />