将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。

This commit is contained in:
QI Mingxuan
2026-04-16 17:31:13 +08:00
parent 6ec4c3ddaa
commit 2bd6e566c3
581 changed files with 74600 additions and 222 deletions
@@ -0,0 +1,533 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Prism.Events;
using XP.Hardware.Detector.Abstractions.Enums;
using XP.Hardware.Detector.Abstractions.Events;
namespace XP.Hardware.Detector.Abstractions
{
/// <summary>
/// 面阵探测器抽象基类 | Area detector abstract base class
/// 封装通用逻辑:参数校验、状态更新、事件发布
/// 使用模板方法模式,子类实现具体的硬件操作
/// </summary>
public abstract class AreaDetectorBase : IAreaDetector
{
protected readonly IEventAggregator _eventAggregator;
protected readonly object _statusLock = new object();
protected DetectorStatus _status = DetectorStatus.Uninitialized;
protected bool _disposed = false;
/// <summary>
/// 探测器状态 | Detector status
/// </summary>
public DetectorStatus Status
{
get
{
lock (_statusLock)
{
return _status;
}
}
}
/// <summary>
/// 探测器类型 | Detector type
/// </summary>
public abstract DetectorType Type { get; }
/// <summary>
/// 构造函数 | Constructor
/// </summary>
/// <param name="eventAggregator">事件聚合器 | Event aggregator</param>
protected AreaDetectorBase(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
}
/// <summary>
/// 初始化探测器 | Initialize detector
/// </summary>
public async Task<DetectorResult> InitializeAsync(CancellationToken cancellationToken = default)
{
// 参数校验 | Parameter validation
if (Status != DetectorStatus.Uninitialized)
{
return DetectorResult.Failure("探测器已初始化 | Detector already initialized");
}
try
{
// 更新状态 | Update status
UpdateStatus(DetectorStatus.Initializing);
// 调用子类实现 | Call derived class implementation
var result = await InitializeInternalAsync(cancellationToken);
if (result.IsSuccess)
{
UpdateStatus(DetectorStatus.Ready);
}
else
{
UpdateStatus(DetectorStatus.Error);
PublishError(result);
}
return result;
}
catch (Exception ex)
{
UpdateStatus(DetectorStatus.Error);
var errorResult = DetectorResult.Failure($"初始化异常 | Initialization exception: {ex.Message}", ex);
PublishError(errorResult);
return errorResult;
}
}
/// <summary>
/// 启动连续采集 | Start continuous acquisition
/// </summary>
public async Task<DetectorResult> StartAcquisitionAsync(CancellationToken cancellationToken = default)
{
// 参数校验 | Parameter validation
if (Status != DetectorStatus.Ready)
{
return DetectorResult.Failure($"探测器状态不正确,当前状态:{Status} | Detector status incorrect, current status: {Status}");
}
try
{
// 更新状态 | Update status
UpdateStatus(DetectorStatus.Acquiring);
// 调用子类实现 | Call derived class implementation
var result = await StartAcquisitionInternalAsync(cancellationToken);
if (!result.IsSuccess)
{
UpdateStatus(DetectorStatus.Ready);
PublishError(result);
}
return result;
}
catch (Exception ex)
{
UpdateStatus(DetectorStatus.Error);
var errorResult = DetectorResult.Failure($"启动采集异常 | Start acquisition exception: {ex.Message}", ex);
PublishError(errorResult);
return errorResult;
}
}
/// <summary>
/// 停止采集 | Stop acquisition
/// </summary>
public async Task<DetectorResult> StopAcquisitionAsync(CancellationToken cancellationToken = default)
{
// 参数校验 | Parameter validation
// 未初始化时不允许停止采集操作 | Stop acquisition not allowed when uninitialized
if (Status == DetectorStatus.Uninitialized)
{
return DetectorResult.Failure("探测器未初始化,无法执行停止采集操作 | Detector not initialized, cannot stop acquisition");
}
try
{
// 调用子类实现 | Call derived class implementation
var result = await StopAcquisitionInternalAsync(cancellationToken);
if (result.IsSuccess)
{
UpdateStatus(DetectorStatus.Ready);
}
else
{
PublishError(result);
}
return result;
}
catch (Exception ex)
{
UpdateStatus(DetectorStatus.Error);
var errorResult = DetectorResult.Failure($"停止采集异常 | Stop acquisition exception: {ex.Message}", ex);
PublishError(errorResult);
return errorResult;
}
}
/// <summary>
/// 单帧采集 | Single frame acquisition
/// </summary>
public async Task<DetectorResult> AcquireSingleFrameAsync(CancellationToken cancellationToken = default)
{
// 参数校验 | Parameter validation
if (Status != DetectorStatus.Ready)
{
return DetectorResult.Failure($"探测器状态不正确,当前状态:{Status} | Detector status incorrect, current status: {Status}");
}
try
{
// 更新状态 | Update status
UpdateStatus(DetectorStatus.Acquiring);
// 调用子类实现 | Call derived class implementation
var result = await AcquireSingleFrameInternalAsync(cancellationToken);
// 恢复状态 | Restore status
UpdateStatus(DetectorStatus.Ready);
if (!result.IsSuccess)
{
PublishError(result);
}
return result;
}
catch (Exception ex)
{
UpdateStatus(DetectorStatus.Error);
var errorResult = DetectorResult.Failure($"单帧采集异常 | Single frame acquisition exception: {ex.Message}", ex);
PublishError(errorResult);
return errorResult;
}
}
/// <summary>
/// 暗场校正 | Dark field correction
/// </summary>
public async Task<DetectorResult> DarkCorrectionAsync(int frameCount, CancellationToken cancellationToken = default)
{
// 参数校验 | Parameter validation
if (Status != DetectorStatus.Ready)
{
return DetectorResult.Failure($"探测器状态不正确,当前状态:{Status} | Detector status incorrect, current status: {Status}");
}
if (frameCount <= 0)
{
return DetectorResult.Failure("帧数必须大于 0 | Frame count must be greater than 0");
}
try
{
// 更新状态 | Update status
UpdateStatus(DetectorStatus.Correcting);
// 调用子类实现 | Call derived class implementation
var result = await DarkCorrectionInternalAsync(frameCount, cancellationToken);
// 恢复状态 | Restore status
UpdateStatus(DetectorStatus.Ready);
if (result.IsSuccess)
{
PublishCorrectionCompleted(CorrectionType.Dark, result);
}
else
{
PublishError(result);
}
return result;
}
catch (Exception ex)
{
UpdateStatus(DetectorStatus.Error);
var errorResult = DetectorResult.Failure($"暗场校正异常 | Dark correction exception: {ex.Message}", ex);
PublishError(errorResult);
return errorResult;
}
}
/// <summary>
/// 增益校正 | Gain correction
/// </summary>
public async Task<DetectorResult> GainCorrectionAsync(int frameCount, CancellationToken cancellationToken = default)
{
// 参数校验 | Parameter validation
if (Status != DetectorStatus.Ready)
{
return DetectorResult.Failure($"探测器状态不正确,当前状态:{Status} | Detector status incorrect, current status: {Status}");
}
if (frameCount <= 0)
{
return DetectorResult.Failure("帧数必须大于 0 | Frame count must be greater than 0");
}
try
{
// 更新状态 | Update status
UpdateStatus(DetectorStatus.Correcting);
// 调用子类实现 | Call derived class implementation
var result = await GainCorrectionInternalAsync(frameCount, cancellationToken);
// 恢复状态 | Restore status
UpdateStatus(DetectorStatus.Ready);
if (result.IsSuccess)
{
PublishCorrectionCompleted(CorrectionType.Gain, result);
}
else
{
PublishError(result);
}
return result;
}
catch (Exception ex)
{
UpdateStatus(DetectorStatus.Error);
var errorResult = DetectorResult.Failure($"增益校正异常 | Gain correction exception: {ex.Message}", ex);
PublishError(errorResult);
return errorResult;
}
}
/// <summary>
/// 自动校正 | Auto correction
/// </summary>
public async Task<DetectorResult> AutoCorrectionAsync(int frameCount, CancellationToken cancellationToken = default)
{
// 参数校验 | Parameter validation
if (Status != DetectorStatus.Ready)
{
return DetectorResult.Failure($"探测器状态不正确,当前状态:{Status} | Detector status incorrect, current status: {Status}");
}
if (frameCount <= 0)
{
return DetectorResult.Failure("帧数必须大于 0 | Frame count must be greater than 0");
}
try
{
// 更新状态 | Update status
UpdateStatus(DetectorStatus.Correcting);
// 调用子类实现 | Call derived class implementation
var result = await AutoCorrectionInternalAsync(frameCount, cancellationToken);
// 恢复状态 | Restore status
UpdateStatus(DetectorStatus.Ready);
if (result.IsSuccess)
{
PublishCorrectionCompleted(CorrectionType.Auto, result);
}
else
{
PublishError(result);
}
return result;
}
catch (Exception ex)
{
UpdateStatus(DetectorStatus.Error);
var errorResult = DetectorResult.Failure($"自动校正异常 | Auto correction exception: {ex.Message}", ex);
PublishError(errorResult);
return errorResult;
}
}
/// <summary>
/// 坏像素校正 | Bad pixel correction
/// </summary>
public async Task<DetectorResult> BadPixelCorrectionAsync(CancellationToken cancellationToken = default)
{
// 参数校验 | Parameter validation
if (Status != DetectorStatus.Ready)
{
return DetectorResult.Failure($"探测器状态不正确,当前状态:{Status} | Detector status incorrect, current status: {Status}");
}
try
{
// 更新状态 | Update status
UpdateStatus(DetectorStatus.Correcting);
// 调用子类实现 | Call derived class implementation
var result = await BadPixelCorrectionInternalAsync(cancellationToken);
// 恢复状态 | Restore status
UpdateStatus(DetectorStatus.Ready);
if (result.IsSuccess)
{
PublishCorrectionCompleted(CorrectionType.BadPixel, result);
}
else
{
PublishError(result);
}
return result;
}
catch (Exception ex)
{
UpdateStatus(DetectorStatus.Error);
var errorResult = DetectorResult.Failure($"坏像素校正异常 | Bad pixel correction exception: {ex.Message}", ex);
PublishError(errorResult);
return errorResult;
}
}
/// <summary>
/// 获取探测器信息 | Get detector information
/// </summary>
public abstract DetectorInfo GetInfo();
// 模板方法,由子类实现 | Template methods, implemented by derived classes
protected abstract Task<DetectorResult> InitializeInternalAsync(CancellationToken cancellationToken);
protected abstract Task<DetectorResult> StartAcquisitionInternalAsync(CancellationToken cancellationToken);
protected abstract Task<DetectorResult> StopAcquisitionInternalAsync(CancellationToken cancellationToken);
protected abstract Task<DetectorResult> AcquireSingleFrameInternalAsync(CancellationToken cancellationToken);
protected abstract Task<DetectorResult> DarkCorrectionInternalAsync(int frameCount, CancellationToken cancellationToken);
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);
/// <summary>
/// 更新状态并发布事件 | Update status and publish event
/// </summary>
/// <param name="newStatus">新状态 | New status</param>
/// <summary>
/// 更新状态并发布事件 | Update status and publish event
/// 验证状态转换的合法性 | Validate state transition legality
/// </summary>
/// <param name="newStatus">新状态 | New status</param>
protected void UpdateStatus(DetectorStatus newStatus)
{
lock (_statusLock)
{
// 验证状态转换的合法性 | Validate state transition legality
if (!IsValidStateTransition(_status, newStatus))
{
var errorMsg = $"非法的状态转换:{_status} -> {newStatus} | Invalid state transition: {_status} -> {newStatus}";
PublishError(DetectorResult.Failure(errorMsg));
return;
}
if (_status != newStatus)
{
var oldStatus = _status;
_status = newStatus;
_eventAggregator.GetEvent<StatusChangedEvent>().Publish(newStatus);
// 记录状态转换日志(如果需要)| Log state transition (if needed)
System.Diagnostics.Debug.WriteLine($"状态转换 | State transition: {oldStatus} -> {newStatus}");
}
}
}
/// <summary>
/// 验证状态转换是否合法 | Validate if state transition is legal
/// </summary>
/// <param name="currentStatus">当前状态 | Current status</param>
/// <param name="newStatus">新状态 | New status</param>
/// <returns>是否合法 | Whether it's legal</returns>
private bool IsValidStateTransition(DetectorStatus currentStatus, DetectorStatus newStatus)
{
// 相同状态总是允许的 | Same state is always allowed
if (currentStatus == newStatus)
{
return true;
}
// 定义合法的状态转换规则 | Define legal state transition rules
return (currentStatus, newStatus) switch
{
// 从未初始化状态只能转到初始化中 | From Uninitialized can only go to Initializing
(DetectorStatus.Uninitialized, DetectorStatus.Initializing) => true,
// 从初始化中可以转到就绪或错误 | From Initializing can go to Ready or Error
(DetectorStatus.Initializing, DetectorStatus.Ready) => true,
(DetectorStatus.Initializing, DetectorStatus.Error) => true,
// 从就绪状态可以转到采集中、校正中 | From Ready can go to Acquiring or Correcting
(DetectorStatus.Ready, DetectorStatus.Acquiring) => true,
(DetectorStatus.Ready, DetectorStatus.Correcting) => true,
// 从采集中可以转到就绪或错误 | From Acquiring can go to Ready or Error
(DetectorStatus.Acquiring, DetectorStatus.Ready) => true,
(DetectorStatus.Acquiring, DetectorStatus.Error) => true,
// 从校正中可以转到就绪或错误 | From Correcting can go to Ready or Error
(DetectorStatus.Correcting, DetectorStatus.Ready) => true,
(DetectorStatus.Correcting, DetectorStatus.Error) => true,
// 从错误状态可以转到初始化中(重新初始化)| From Error can go to Initializing (re-initialize)
(DetectorStatus.Error, DetectorStatus.Initializing) => true,
// 任何状态都可以转到错误状态 | Any state can go to Error
(_, DetectorStatus.Error) => true,
// 其他转换都是非法的 | All other transitions are illegal
_ => false
};
}
/// <summary>
/// 发布错误事件 | Publish error event
/// </summary>
/// <param name="result">错误结果 | Error result</param>
protected void PublishError(DetectorResult result)
{
_eventAggregator.GetEvent<ErrorOccurredEvent>().Publish(result);
}
/// <summary>
/// 发布图像采集事件 | Publish image captured event
/// </summary>
/// <param name="args">事件参数 | Event arguments</param>
protected void PublishImageCaptured(ImageCapturedEventArgs args)
{
_eventAggregator.GetEvent<ImageCapturedEvent>().Publish(args);
}
/// <summary>
/// 发布校正完成事件 | Publish correction completed event
/// </summary>
/// <param name="type">校正类型 | Correction type</param>
/// <param name="result">校正结果 | Correction result</param>
protected void PublishCorrectionCompleted(CorrectionType type, DetectorResult result)
{
_eventAggregator.GetEvent<CorrectionCompletedEvent>().Publish(
new CorrectionCompletedEventArgs(type, result));
}
// IDisposable 实现 | IDisposable implementation
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
}
// 释放非托管资源 | Release unmanaged resources
_disposed = true;
}
}
}
}
@@ -0,0 +1,51 @@
using XP.Hardware.Detector.Abstractions.Enums;
namespace XP.Hardware.Detector.Abstractions
{
/// <summary>
/// 探测器信息 | Detector information
/// 包含探测器的硬件参数和配置信息
/// </summary>
public class DetectorInfo
{
/// <summary>
/// 探测器类型 | Detector type
/// </summary>
public DetectorType Type { get; set; }
/// <summary>
/// 探测器型号 | Detector model
/// </summary>
public string Model { get; set; }
/// <summary>
/// 序列号 | Serial number
/// </summary>
public string SerialNumber { get; set; }
/// <summary>
/// 固件版本 | Firmware version
/// </summary>
public string FirmwareVersion { get; set; }
/// <summary>
/// 最大分辨率宽度 | Maximum resolution width
/// </summary>
public uint MaxWidth { get; set; }
/// <summary>
/// 最大分辨率高度 | Maximum resolution height
/// </summary>
public uint MaxHeight { get; set; }
/// <summary>
/// 像素尺寸(微米)| Pixel size (micrometers)
/// </summary>
public double PixelSize { get; set; }
/// <summary>
/// 位深度 | Bit depth
/// </summary>
public int BitDepth { get; set; }
}
}
@@ -0,0 +1,102 @@
using System;
namespace XP.Hardware.Detector.Abstractions
{
/// <summary>
/// 探测器操作结果封装 | Detector operation result wrapper
/// 统一封装成功/失败状态、数据和错误信息
/// </summary>
public class DetectorResult
{
/// <summary>
/// 操作是否成功 | Whether the operation succeeded
/// </summary>
public bool IsSuccess { get; }
/// <summary>
/// 错误消息 | Error message
/// </summary>
public string ErrorMessage { get; }
/// <summary>
/// 异常对象 | Exception object
/// </summary>
public Exception Exception { get; }
/// <summary>
/// 错误码 | Error code
/// </summary>
public int ErrorCode { get; }
protected DetectorResult(bool isSuccess, string errorMessage = null, Exception exception = null, int errorCode = 0)
{
IsSuccess = isSuccess;
ErrorMessage = errorMessage;
Exception = exception;
ErrorCode = errorCode;
}
/// <summary>
/// 创建成功结果 | Create success result
/// </summary>
/// <param name="message">成功消息 | Success message</param>
/// <returns>成功结果 | Success result</returns>
public static DetectorResult Success(string message = null)
{
return new DetectorResult(true, message);
}
/// <summary>
/// 创建失败结果 | Create failure result
/// </summary>
/// <param name="errorMessage">错误消息 | Error message</param>
/// <param name="exception">异常对象 | Exception object</param>
/// <param name="errorCode">错误码 | Error code</param>
/// <returns>失败结果 | Failure result</returns>
public static DetectorResult Failure(string errorMessage, Exception exception = null, int errorCode = -1)
{
return new DetectorResult(false, errorMessage, exception, errorCode);
}
}
/// <summary>
/// 带数据的探测器操作结果 | Detector operation result with data
/// </summary>
/// <typeparam name="T">数据类型 | Data type</typeparam>
public class DetectorResult<T> : DetectorResult
{
/// <summary>
/// 结果数据 | Result data
/// </summary>
public T Data { get; }
private DetectorResult(bool isSuccess, T data, string errorMessage = null, Exception exception = null, int errorCode = 0)
: base(isSuccess, errorMessage, exception, errorCode)
{
Data = data;
}
/// <summary>
/// 创建成功结果 | Create success result
/// </summary>
/// <param name="data">结果数据 | Result data</param>
/// <param name="message">成功消息 | Success message</param>
/// <returns>成功结果 | Success result</returns>
public static DetectorResult<T> Success(T data, string message = null)
{
return new DetectorResult<T>(true, data, message);
}
/// <summary>
/// 创建失败结果 | Create failure result
/// </summary>
/// <param name="errorMessage">错误消息 | Error message</param>
/// <param name="exception">异常对象 | Exception object</param>
/// <param name="errorCode">错误码 | Error code</param>
/// <returns>失败结果 | Failure result</returns>
public new static DetectorResult<T> Failure(string errorMessage, Exception exception = null, int errorCode = -1)
{
return new DetectorResult<T>(false, default(T), errorMessage, exception, errorCode);
}
}
}
@@ -0,0 +1,19 @@
namespace XP.Hardware.Detector.Abstractions.Enums
{
/// <summary>
/// 采集模式枚举 | Acquisition mode enumeration
/// 定义图像采集的工作模式
/// </summary>
public enum AcquisitionMode
{
/// <summary>
/// 连续采集模式 | Continuous acquisition mode
/// </summary>
Continuous = 0,
/// <summary>
/// 单帧采集模式 | Single frame acquisition mode
/// </summary>
SingleFrame = 1
}
}
@@ -0,0 +1,24 @@
namespace XP.Hardware.Detector.Abstractions.Enums
{
/// <summary>
/// Binning 模式枚举 | Binning mode enumeration
/// 定义像素合并模式,用于提高信噪比或采集速度
/// </summary>
public enum BinningMode
{
/// <summary>
/// 1x1 模式(无合并)| 1x1 mode (no binning)
/// </summary>
Bin1x1 = 0,
/// <summary>
/// 2x2 模式(2x2 像素合并)| 2x2 mode (2x2 pixel binning)
/// </summary>
Bin2x2 = 1,
/// <summary>
/// 4x4 模式(4x4 像素合并)| 4x4 mode (4x4 pixel binning)
/// </summary>
Bin4x4 = 2
}
}
@@ -0,0 +1,29 @@
namespace XP.Hardware.Detector.Abstractions.Enums
{
/// <summary>
/// 校正类型枚举 | Correction type enumeration
/// 定义探测器支持的校正类型
/// </summary>
public enum CorrectionType
{
/// <summary>
/// 暗场校正 | Dark field correction
/// </summary>
Dark = 0,
/// <summary>
/// 增益校正(亮场校正)| Gain correction (bright field correction)
/// </summary>
Gain = 1,
/// <summary>
/// 自动校正(暗场+增益+坏像素)| Auto correction (dark + gain + bad pixel)
/// </summary>
Auto = 2,
/// <summary>
/// 坏像素校正 | Bad pixel correction
/// </summary>
BadPixel = 3
}
}
@@ -0,0 +1,39 @@
namespace XP.Hardware.Detector.Abstractions.Enums
{
/// <summary>
/// 探测器状态枚举 | Detector status enumeration
/// 定义探测器的运行状态
/// </summary>
public enum DetectorStatus
{
/// <summary>
/// 未初始化 | Uninitialized
/// </summary>
Uninitialized = 0,
/// <summary>
/// 初始化中 | Initializing
/// </summary>
Initializing = 1,
/// <summary>
/// 就绪 | Ready
/// </summary>
Ready = 2,
/// <summary>
/// 采集中 | Acquiring
/// </summary>
Acquiring = 3,
/// <summary>
/// 校正中 | Correcting
/// </summary>
Correcting = 4,
/// <summary>
/// 错误 | Error
/// </summary>
Error = 5
}
}
@@ -0,0 +1,24 @@
namespace XP.Hardware.Detector.Abstractions.Enums
{
/// <summary>
/// 探测器类型枚举 | Detector type enumeration
/// 定义支持的探测器厂商类型
/// </summary>
public enum DetectorType
{
/// <summary>
/// Varex 探测器 | Varex detector
/// </summary>
Varex = 0,
/// <summary>
/// iRay 探测器 | iRay detector (预留)
/// </summary>
IRay = 1,
/// <summary>
/// Hamamatsu 探测器 | Hamamatsu detector (预留)
/// </summary>
Hamamatsu = 2
}
}
@@ -0,0 +1,19 @@
namespace XP.Hardware.Detector.Abstractions.Enums
{
/// <summary>
/// 增益模式枚举 | Gain mode enumeration
/// 定义探测器的信号放大倍数
/// </summary>
public enum GainMode
{
/// <summary>
/// 低增益模式 | Low gain mode
/// </summary>
Low = 0,
/// <summary>
/// 高增益模式 | High gain mode
/// </summary>
High = 1
}
}
@@ -0,0 +1,14 @@
using Prism.Events;
using XP.Hardware.Detector.Abstractions;
namespace XP.Hardware.Detector.Abstractions.Events
{
/// <summary>
/// 校正完成事件 | Correction completed event
/// 当探测器校正操作完成时触发
/// 线程安全:使用 Prism EventAggregator 确保线程安全的发布和订阅
/// </summary>
public class CorrectionCompletedEvent : PubSubEvent<CorrectionCompletedEventArgs>
{
}
}
@@ -0,0 +1,33 @@
using System;
using XP.Hardware.Detector.Abstractions.Enums;
namespace XP.Hardware.Detector.Abstractions
{
/// <summary>
/// 校正完成事件参数 | Correction completed event arguments
/// 携带校正类型和结果信息
/// </summary>
public class CorrectionCompletedEventArgs : EventArgs
{
/// <summary>
/// 校正类型 | Correction type
/// </summary>
public CorrectionType Type { get; set; }
/// <summary>
/// 校正结果 | Correction result
/// </summary>
public DetectorResult Result { get; set; }
/// <summary>
/// 构造函数 | Constructor
/// </summary>
/// <param name="type">校正类型 | Correction type</param>
/// <param name="result">校正结果 | Correction result</param>
public CorrectionCompletedEventArgs(CorrectionType type, DetectorResult result)
{
Type = type;
Result = result;
}
}
}
@@ -0,0 +1,14 @@
using Prism.Events;
using XP.Hardware.Detector.Abstractions;
namespace XP.Hardware.Detector.Abstractions.Events
{
/// <summary>
/// 错误发生事件 | Error occurred event
/// 当探测器操作发生错误时触发
/// 线程安全:使用 Prism EventAggregator 确保线程安全的发布和订阅
/// </summary>
public class ErrorOccurredEvent : PubSubEvent<DetectorResult>
{
}
}
@@ -0,0 +1,14 @@
using Prism.Events;
using XP.Hardware.Detector.Abstractions;
namespace XP.Hardware.Detector.Abstractions.Events
{
/// <summary>
/// 图像采集事件 | Image captured event
/// 当探测器采集到新图像时触发
/// 线程安全:使用 Prism EventAggregator 确保线程安全的发布和订阅
/// </summary>
public class ImageCapturedEvent : PubSubEvent<ImageCapturedEventArgs>
{
}
}
@@ -0,0 +1,46 @@
using System;
namespace XP.Hardware.Detector.Abstractions
{
/// <summary>
/// 图像采集事件参数 | Image captured event arguments
/// 携带图像数据和元信息
/// </summary>
public class ImageCapturedEventArgs : EventArgs
{
/// <summary>
/// 16 位图像原始数据(无符号)| 16-bit raw image data (unsigned)
/// </summary>
public ushort[] ImageData { get; set; }
/// <summary>
/// 图像宽度 | Image width
/// </summary>
public uint Width { get; set; }
/// <summary>
/// 图像高度 | Image height
/// </summary>
public uint Height { get; set; }
/// <summary>
/// 帧号 | Frame number
/// </summary>
public int FrameNumber { get; set; }
/// <summary>
/// 存储路径 | Save path
/// </summary>
public string SavePath { get; set; }
/// <summary>
/// 曝光时间(毫秒)| Exposure time (milliseconds)
/// </summary>
public uint ExposureTime { get; set; }
/// <summary>
/// 采集时间戳 | Capture timestamp
/// </summary>
public DateTime CaptureTime { get; set; }
}
}
@@ -0,0 +1,14 @@
using Prism.Events;
using XP.Hardware.Detector.Abstractions.Enums;
namespace XP.Hardware.Detector.Abstractions.Events
{
/// <summary>
/// 状态变更事件 | Status changed event
/// 当探测器状态发生变化时触发
/// 线程安全:使用 Prism EventAggregator 确保线程安全的发布和订阅
/// </summary>
public class StatusChangedEvent : PubSubEvent<DetectorStatus>
{
}
}
@@ -0,0 +1,89 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using XP.Hardware.Detector.Abstractions.Enums;
namespace XP.Hardware.Detector.Abstractions
{
/// <summary>
/// 面阵探测器通用接口 | Area detector common interface
/// 定义所有探测器必须实现的核心能力
/// </summary>
public interface IAreaDetector : IDisposable
{
/// <summary>
/// 探测器状态 | Detector status
/// </summary>
DetectorStatus Status { get; }
/// <summary>
/// 探测器类型 | Detector type
/// </summary>
DetectorType Type { get; }
/// <summary>
/// 初始化探测器 | Initialize detector
/// </summary>
/// <param name="cancellationToken">取消令牌 | Cancellation token</param>
/// <returns>操作结果 | Operation result</returns>
Task<DetectorResult> InitializeAsync(CancellationToken cancellationToken = default);
/// <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, 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, CancellationToken cancellationToken = default);
/// <summary>
/// 自动校正(暗场+增益+坏像素)| 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, 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>
/// 获取探测器信息 | Get detector information
/// </summary>
/// <returns>探测器信息 | Detector information</returns>
DetectorInfo GetInfo();
}
}
@@ -0,0 +1,38 @@
using System.Threading.Tasks;
using XP.Hardware.Detector.Abstractions.Enums;
namespace XP.Hardware.Detector.Abstractions
{
/// <summary>
/// iRay 探测器专属接口 | iRay detector specific interface
/// 扩展 iRay 特有的功能
/// </summary>
public interface IIRayDetector : IAreaDetector
{
/// <summary>
/// 设置采集模式 | Set acquisition mode
/// </summary>
/// <param name="mode">采集模式 | Acquisition mode</param>
/// <returns>操作结果 | Operation result</returns>
Task<DetectorResult> SetAcquisitionModeAsync(AcquisitionMode mode);
/// <summary>
/// 获取采集模式 | Get acquisition mode
/// </summary>
/// <returns>当前采集模式 | Current acquisition mode</returns>
AcquisitionMode GetAcquisitionMode();
/// <summary>
/// 设置增益值 | Set gain value
/// </summary>
/// <param name="gain">增益值 | Gain value</param>
/// <returns>操作结果 | Operation result</returns>
Task<DetectorResult> SetGainAsync(double gain);
/// <summary>
/// 获取增益值 | Get gain value
/// </summary>
/// <returns>当前增益值 | Current gain value</returns>
double GetGain();
}
}
@@ -0,0 +1,67 @@
using System.Threading.Tasks;
using XP.Hardware.Detector.Abstractions.Enums;
namespace XP.Hardware.Detector.Abstractions
{
/// <summary>
/// Varex 探测器专属接口 | Varex detector specific interface
/// 扩展 Varex 特有的功能
/// </summary>
public interface IVarexDetector : IAreaDetector
{
/// <summary>
/// 设置 Binning 模式 | Set binning mode
/// </summary>
/// <param name="mode">Binning 模式 | Binning mode</param>
/// <returns>操作结果 | Operation result</returns>
Task<DetectorResult> SetBinningModeAsync(BinningMode mode);
/// <summary>
/// 获取 Binning 模式 | Get binning mode
/// </summary>
/// <returns>当前 Binning 模式 | Current binning mode</returns>
BinningMode GetBinningMode();
/// <summary>
/// 设置增益模式 | Set gain mode
/// </summary>
/// <param name="mode">增益模式 | Gain mode</param>
/// <returns>操作结果 | Operation result</returns>
Task<DetectorResult> SetGainModeAsync(GainMode mode);
/// <summary>
/// 获取增益模式 | Get gain mode
/// </summary>
/// <returns>当前增益模式 | Current gain mode</returns>
GainMode GetGainMode();
/// <summary>
/// 设置曝光时间 | Set exposure time
/// </summary>
/// <param name="milliseconds">曝光时间(毫秒)| Exposure time (milliseconds)</param>
/// <returns>操作结果 | Operation result</returns>
Task<DetectorResult> SetExposureTimeAsync(uint milliseconds);
/// <summary>
/// 获取曝光时间 | Get exposure time
/// </summary>
/// <returns>曝光时间(毫秒)| Exposure time (milliseconds)</returns>
uint GetExposureTime();
/// <summary>
/// 设置 ROI 区域 | Set ROI region
/// </summary>
/// <param name="x">起始 X 坐标 | Start X coordinate</param>
/// <param name="y">起始 Y 坐标 | Start Y coordinate</param>
/// <param name="width">宽度 | Width</param>
/// <param name="height">高度 | Height</param>
/// <returns>操作结果 | Operation result</returns>
Task<DetectorResult> SetROIAsync(uint x, uint y, uint width, uint height);
/// <summary>
/// 获取 ROI 区域 | Get ROI region
/// </summary>
/// <returns>ROI 参数 | ROI parameters</returns>
(uint x, uint y, uint width, uint height) GetROI();
}
}
+245
View File
@@ -0,0 +1,245 @@
using System;
using System.Configuration;
using System.IO;
using System.Net;
using XP.Hardware.Detector.Abstractions;
using XP.Hardware.Detector.Abstractions.Enums;
namespace XP.Hardware.Detector.Config
{
/// <summary>
/// 配置加载器 | Configuration loader
/// 从 App.config 读取探测器配置并验证参数有效性
/// </summary>
public static class ConfigLoader
{
/// <summary>
/// 加载配置 | Load configuration
/// </summary>
/// <returns>配置结果 | Configuration result</returns>
public static DetectorResult<DetectorConfig> LoadConfiguration()
{
try
{
// 读取探测器类型 | Read detector type
string typeStr = ConfigurationManager.AppSettings["Detector:Type"];
if (string.IsNullOrEmpty(typeStr))
{
return DetectorResult<DetectorConfig>.Failure("配置文件中未找到 Detector:Type | Detector:Type not found in configuration");
}
if (!Enum.TryParse<DetectorType>(typeStr, true, out var detectorType))
{
return DetectorResult<DetectorConfig>.Failure($"无效的探测器类型:{typeStr} | Invalid detector type: {typeStr}");
}
// 根据类型创建配置对象 | Create configuration object based on type
DetectorConfig config = detectorType switch
{
DetectorType.Varex => LoadVarexConfiguration(),
DetectorType.IRay => LoadIRayConfiguration(),
_ => throw new NotSupportedException($"不支持的探测器类型:{detectorType} | Unsupported detector type: {detectorType}")
};
// 加载通用配置 | Load common configuration
config.Type = detectorType;
config.IP = ConfigurationManager.AppSettings["Detector:IP"] ?? "127.0.0.1";
config.Port = int.TryParse(ConfigurationManager.AppSettings["Detector:Port"], out var port) ? port : 5000;
config.SavePath = ConfigurationManager.AppSettings["Detector:SavePath"] ?? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Images");
config.AutoSave = bool.TryParse(ConfigurationManager.AppSettings["Detector:AutoSave"], out var autoSave) && autoSave;
// 验证配置 | Validate configuration
var validationResult = ValidateConfiguration(config);
if (!validationResult.IsSuccess)
{
return DetectorResult<DetectorConfig>.Failure(validationResult.ErrorMessage);
}
return DetectorResult<DetectorConfig>.Success(config, "配置加载成功 | Configuration loaded successfully");
}
catch (Exception ex)
{
return DetectorResult<DetectorConfig>.Failure($"加载配置异常 | Load configuration exception: {ex.Message}", ex);
}
}
/// <summary>
/// 加载 Varex 配置 | Load Varex configuration
/// </summary>
private static VarexDetectorConfig LoadVarexConfiguration()
{
var config = new VarexDetectorConfig();
// 读取 Binning 模式 | Read binning mode
string binningStr = ConfigurationManager.AppSettings["Detector:Varex:BinningMode"];
if (!string.IsNullOrEmpty(binningStr) && Enum.TryParse<BinningMode>(binningStr, true, out var binningMode))
{
config.BinningMode = binningMode;
}
// 读取增益模式 | Read gain mode
string gainStr = ConfigurationManager.AppSettings["Detector:Varex:GainMode"];
if (!string.IsNullOrEmpty(gainStr) && Enum.TryParse<GainMode>(gainStr, true, out var gainMode))
{
config.GainMode = gainMode;
}
// 读取曝光时间 | Read exposure time
if (uint.TryParse(ConfigurationManager.AppSettings["Detector:Varex:ExposureTime"], out var exposureTime))
{
config.ExposureTime = exposureTime;
}
// 读取 ROI 参数 | Read ROI parameters
if (uint.TryParse(ConfigurationManager.AppSettings["Detector:Varex:ROI_X"], out var roiX))
{
config.RoiX = roiX;
}
if (uint.TryParse(ConfigurationManager.AppSettings["Detector:Varex:ROI_Y"], out var roiY))
{
config.RoiY = roiY;
}
if (uint.TryParse(ConfigurationManager.AppSettings["Detector:Varex:ROI_Width"], out var roiWidth))
{
config.RoiWidth = roiWidth;
}
if (uint.TryParse(ConfigurationManager.AppSettings["Detector:Varex:ROI_Height"], out var roiHeight))
{
config.RoiHeight = roiHeight;
}
return config;
}
/// <summary>
/// 加载 iRay 配置 | Load iRay configuration
/// </summary>
private static IRayDetectorConfig LoadIRayConfiguration()
{
var config = new IRayDetectorConfig();
// 读取默认增益 | Read default gain
if (double.TryParse(ConfigurationManager.AppSettings["Detector:IRay:DefaultGain"], out var defaultGain))
{
config.DefaultGain = defaultGain;
}
// 读取采集模式 | Read acquisition mode
string modeStr = ConfigurationManager.AppSettings["Detector:IRay:AcquisitionMode"];
if (!string.IsNullOrEmpty(modeStr) && Enum.TryParse<AcquisitionMode>(modeStr, true, out var acquisitionMode))
{
config.AcquisitionMode = acquisitionMode;
}
return config;
}
/// <summary>
/// 保存探测器参数到 App.config | Save detector parameters to App.config
/// </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>
public static void SaveParameters(int binningIndex, int pga, decimal frameRate, int avgFrames)
{
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
SetAppSetting(config, "Detector:BinningIndex", binningIndex.ToString());
SetAppSetting(config, "Detector:PGA", pga.ToString());
SetAppSetting(config, "Detector:FrameRate", frameRate.ToString(System.Globalization.CultureInfo.InvariantCulture));
SetAppSetting(config, "Detector:AvgFrames", avgFrames.ToString());
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");
}
/// <summary>
/// 加载已保存的探测器参数 | Load saved detector parameters
/// </summary>
/// <returns>参数元组(binningIndex, pga, frameRate, avgFrames),加载失败返回 null | Parameter tuple, null if failed</returns>
public static (int binningIndex, int pga, decimal frameRate, int avgFrames)? LoadSavedParameters()
{
try
{
string binStr = ConfigurationManager.AppSettings["Detector:BinningIndex"];
string pgaStr = ConfigurationManager.AppSettings["Detector:PGA"];
string frStr = ConfigurationManager.AppSettings["Detector:FrameRate"];
string avgStr = ConfigurationManager.AppSettings["Detector:AvgFrames"];
if (string.IsNullOrEmpty(binStr) || string.IsNullOrEmpty(pgaStr) ||
string.IsNullOrEmpty(frStr) || string.IsNullOrEmpty(avgStr))
{
return null;
}
if (int.TryParse(binStr, out var binning) &&
int.TryParse(pgaStr, out var pgaVal) &&
decimal.TryParse(frStr, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var fr) &&
int.TryParse(avgStr, out var avg))
{
return (binning, pgaVal, fr, avg);
}
}
catch
{
// 忽略加载异常 | Ignore load exception
}
return null;
}
/// <summary>
/// 设置 AppSettings 键值 | Set AppSettings key-value
/// </summary>
private static void SetAppSetting(Configuration config, string key, string value)
{
if (config.AppSettings.Settings[key] != null)
{
config.AppSettings.Settings[key].Value = value;
}
else
{
config.AppSettings.Settings.Add(key, value);
}
}
/// <summary>
/// 验证配置参数 | Validate configuration parameters
/// </summary>
private static DetectorResult ValidateConfiguration(DetectorConfig config)
{
// 验证 IP 地址 | Validate IP address
if (!IPAddress.TryParse(config.IP, out _))
{
return DetectorResult.Failure($"无效的 IP 地址:{config.IP} | Invalid IP address: {config.IP}");
}
// 验证端口范围 | Validate port range
if (config.Port < 1 || config.Port > 65535)
{
return DetectorResult.Failure($"无效的端口号:{config.Port},必须在 1-65535 之间 | Invalid port: {config.Port}, must be between 1-65535");
}
// 验证存储路径 | Validate save path
if (string.IsNullOrWhiteSpace(config.SavePath))
{
return DetectorResult.Failure("存储路径不能为空 | Save path cannot be empty");
}
try
{
// 尝试创建目录以验证路径有效性 | Try to create directory to validate path
if (!Directory.Exists(config.SavePath))
{
Directory.CreateDirectory(config.SavePath);
}
}
catch (Exception ex)
{
return DetectorResult.Failure($"无效的存储路径:{config.SavePath},错误:{ex.Message} | Invalid save path: {config.SavePath}, error: {ex.Message}");
}
return DetectorResult.Success("配置验证通过 | Configuration validation passed");
}
}
}
@@ -0,0 +1,138 @@
using System.Collections.Generic;
using XP.Hardware.Detector.Abstractions.Enums;
namespace XP.Hardware.Detector.Config
{
/// <summary>
/// 探测器通用配置基类 | Detector common configuration base class
/// 包含所有探测器的通用配置参数
/// </summary>
public class DetectorConfig
{
/// <summary>
/// 探测器类型 | Detector type
/// </summary>
public DetectorType Type { get; set; }
/// <summary>
/// IP 地址 | IP address
/// </summary>
public string IP { get; set; }
/// <summary>
/// 端口号 | Port number
/// </summary>
public int Port { get; set; }
/// <summary>
/// 图像存储路径 | Image save path
/// </summary>
public string SavePath { get; set; }
/// <summary>
/// 是否自动保存图像 | Whether to auto save images
/// </summary>
public bool AutoSave { get; set; }
/// <summary>
/// 获取支持的 Binning 选项(显示名称 → 索引)| Get supported binning options (display name → index)
/// 子类可重写以提供不同的选项列表
/// </summary>
public virtual List<BinningOption> GetSupportedBinnings()
{
return new List<BinningOption>
{
new BinningOption("1×1", 0),
new BinningOption("2×2", 1),
};
}
/// <summary>
/// 获取支持的 PGA(灵敏度)选项 | Get supported PGA (sensitivity) options
/// 子类可重写以提供不同的选项列表
/// </summary>
public virtual List<int> GetSupportedPgaValues()
{
return new List<int> { 2, 3, 4, 5, 6, 7 };
}
/// <summary>
/// 获取各 Binning 模式下的最大帧率 | Get max frame rate for each binning mode
/// 子类可重写以提供不同的限制
/// </summary>
public virtual decimal GetMaxFrameRate(int binningIndex)
{
return 15m;
}
/// <summary>
/// 获取指定 Binning 模式下的图像规格(像素尺寸、分辨率)| Get image spec for given binning mode
/// 子类可重写以提供不同的映射关系
/// </summary>
/// <param name="binningIndex">Binning 索引 | Binning index</param>
/// <returns>图像规格 | Image specification</returns>
public virtual BinningImageSpec GetImageSpec(int binningIndex)
{
return new BinningImageSpec(0.139, 0.139, 3072, 3060);
}
}
/// <summary>
/// Binning 模式下的图像规格 | Image specification for binning mode
/// 包含像素尺寸和图像分辨率,供重建 PC 使用
/// </summary>
public class BinningImageSpec
{
/// <summary>
/// X 方向像素尺寸(mm| Pixel size in X direction (mm)
/// </summary>
public double PixelX { get; }
/// <summary>
/// Y 方向像素尺寸(mm| Pixel size in Y direction (mm)
/// </summary>
public double PixelY { get; }
/// <summary>
/// 图像宽度(像素)| Image width (pixels)
/// </summary>
public int ImageWidth { get; }
/// <summary>
/// 图像高度(像素)| Image height (pixels)
/// </summary>
public int ImageHeight { get; }
public BinningImageSpec(double pixelX, double pixelY, int imageWidth, int imageHeight)
{
PixelX = pixelX;
PixelY = pixelY;
ImageWidth = imageWidth;
ImageHeight = imageHeight;
}
}
/// <summary>
/// Binning 选项模型 | Binning option model
/// </summary>
public class BinningOption
{
/// <summary>
/// 显示名称 | Display name
/// </summary>
public string DisplayName { get; }
/// <summary>
/// 索引值 | Index value
/// </summary>
public int Index { get; }
public BinningOption(string displayName, int index)
{
DisplayName = displayName;
Index = index;
}
public override string ToString() => DisplayName;
}
}
@@ -0,0 +1,56 @@
using System.Collections.Generic;
using XP.Hardware.Detector.Abstractions.Enums;
namespace XP.Hardware.Detector.Config
{
/// <summary>
/// iRay 探测器配置类 | iRay detector configuration class
/// 包含 iRay 专属的配置参数
/// </summary>
public class IRayDetectorConfig : DetectorConfig
{
/// <summary>
/// 默认增益值 | Default gain value
/// </summary>
public double DefaultGain { get; set; } = 1.0;
/// <summary>
/// 采集模式 | Acquisition mode
/// </summary>
public AcquisitionMode AcquisitionMode { get; set; } = AcquisitionMode.Continuous;
/// <summary>
/// iRay 支持 1×1、2×2、3×3、4×4 四种 Binning | iRay supports 1×1, 2×2, 3×3, 4×4 binning
/// </summary>
public override List<BinningOption> GetSupportedBinnings()
{
return new List<BinningOption>
{
new BinningOption("1×1", 0),
new BinningOption("2×2", 1),
new BinningOption("3×3", 2),
new BinningOption("4×4", 3),
};
}
/// <summary>
/// iRay PGA 范围 1-8 | iRay PGA range 1-8
/// </summary>
public override List<int> GetSupportedPgaValues()
{
return new List<int> { 1, 2, 3, 4, 5, 6, 7, 8 };
}
/// <summary>
/// iRay 各 Binning 模式最大帧率 | iRay max frame rate per binning mode
/// </summary>
public override decimal GetMaxFrameRate(int binningIndex) => binningIndex switch
{
0 => 15m, // 1×1
1 => 30m, // 2×2
2 => 45m, // 3×3
3 => 60m, // 4×4
_ => 15m
};
}
}
@@ -0,0 +1,94 @@
using System.Collections.Generic;
using XP.Hardware.Detector.Abstractions.Enums;
namespace XP.Hardware.Detector.Config
{
/// <summary>
/// Varex 探测器配置类 | Varex detector configuration class
/// 包含 Varex 专属的配置参数
/// </summary>
public class VarexDetectorConfig : DetectorConfig
{
/// <summary>
/// Binning 模式 | Binning mode
/// </summary>
public BinningMode BinningMode { get; set; } = BinningMode.Bin1x1;
/// <summary>
/// 增益模式 | Gain mode
/// </summary>
public GainMode GainMode { get; set; } = GainMode.Low;
/// <summary>
/// 曝光时间(毫秒)| Exposure time (milliseconds)
/// </summary>
public uint ExposureTime { get; set; } = 100;
/// <summary>
/// ROI 起始 X 坐标 | ROI start X coordinate
/// </summary>
public uint RoiX { get; set; } = 0;
/// <summary>
/// ROI 起始 Y 坐标 | ROI start Y coordinate
/// </summary>
public uint RoiY { get; set; } = 0;
/// <summary>
/// ROI 宽度 | ROI width
/// </summary>
public uint RoiWidth { get; set; } = 0;
/// <summary>
/// ROI 高度 | ROI height
/// </summary>
public uint RoiHeight { get; set; } = 0;
/// <summary>
/// Varex 支持 1×1、2×2、4×4 三种 Binning | Varex supports 1×1, 2×2, 4×4 binning
/// </summary>
public override List<BinningOption> GetSupportedBinnings()
{
return new List<BinningOption>
{
new BinningOption("1×1", 0),
new BinningOption("2×2", 1),
new BinningOption("3×3", 2),
new BinningOption("4×4", 3),
};
}
/// <summary>
/// Varex PGA 范围 2-7 | Varex PGA range 2-7
/// </summary>
public override List<int> GetSupportedPgaValues()
{
return new List<int> { 2, 3, 4, 5, 6, 7 };
}
/// <summary>
/// Varex 各 Binning 模式最大帧率 | Varex max frame rate per binning mode
/// </summary>
public override decimal GetMaxFrameRate(int binningIndex) => binningIndex switch
{
0 => 15m, // 1×1
1 => 30m, // 2×2
2 => 45m, // 3×3
3 => 60m, // 4×4
_ => 15m
};
/// <summary>
/// Varex 4343N 各 Binning 模式的图像规格 | Varex 4343N image spec per binning mode
/// 像素尺寸和分辨率映射关系,供重建 PC 使用
/// </summary>
public override BinningImageSpec GetImageSpec(int binningIndex) => binningIndex switch
{
0 => new BinningImageSpec(0.139, 0.139, 3072, 3060), // 1×1
1 => new BinningImageSpec(0.278, 0.278, 1536, 1530), // 2×2
2 => new BinningImageSpec(0.417, 0.417, 1024, 1020), // 3×3
3 => new BinningImageSpec(0.556, 0.556, 768, 765), // 4×4
_ => new BinningImageSpec(0.139, 0.139, 3072, 3060)
};
}
}
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!-- 探测器配置 | Detector Configuration -->
<!-- 探测器类型 | Detector Type -->
<!-- 可选值: Varex, IRay, Hamamatsu | Available values: Varex, IRay, Hamamatsu -->
<add key="Detector:Type" value="Varex" />
<!-- 通用配置 | Common Configuration -->
<add key="Detector:IP" value="192.168.1.200" />
<add key="Detector:Port" value="5000" />
<add key="Detector:SavePath" value="C:\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 -->
<add key="Detector:Varex:GainMode" value="High" />
<!-- 曝光时间(毫秒)| Exposure time (milliseconds) -->
<add key="Detector:Varex:ExposureTime" value="100" />
<!-- ROI 区域 | ROI Region -->
<add key="Detector:Varex:ROI_X" value="0" />
<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:SaveCorrectionData" value="true" />
<!-- 操作超时配置 | Operation Timeout Configuration -->
<add key="Detector:InitializationTimeout" value="30000" />
<add key="Detector:AcquisitionTimeout" value="10000" />
<add key="Detector:CorrectionTimeout" value="60000" />
</appSettings>
</configuration>
File diff suppressed because it is too large Load Diff
+356
View File
@@ -0,0 +1,356 @@
# XP.Hardware.Detector
工业探测器控制模块 | Industrial Detector Control Module
---
## 项目概述 | Project Overview
XP.Hardware.Detector 是 XplorePlane X 射线检测系统的探测器控制模块,负责与工业 X 射线探测器进行通讯和控制。该模块采用策略模式和工厂模式设计,支持多种探测器型号的统一管理,提供完整的图像采集和校正功能。
### 主要特性 | Key Features
- 支持多种探测器型号(Varex、iRay、Hamamatsu 等)
- 完整的探测器生命周期管理(初始化、采集、校正)
- 单帧和连续采集模式
- 自动校正功能(暗场、增益、坏像素)
- 实时状态监控
- 基于 Prism 事件聚合器的松耦合跨模块通讯
- 异步操作,不阻塞 UI 线程
- 完整的日志记录和异常处理
---
## 框架架构 | Architecture
```
XP.Hardware.Detector/
├── Abstractions/ # 抽象层 | Abstraction Layer
│ ├── IAreaDetector.cs # 面阵探测器接口
│ ├── AreaDetectorBase.cs # 抽象基类
│ ├── DetectorResult.cs # 结果封装类
│ ├── DetectorInfo.cs # 探测器信息
│ ├── Enums/ # 枚举定义
│ │ ├── DetectorStatus.cs
│ │ └── DetectorType.cs
│ └── Events/ # Prism 事件定义
│ ├── ImageCapturedEvent.cs
│ └── CorrectionCompletedEvent.cs
├── Implementations/ # 实现层 | Implementation Layer
│ ├── VarexDetector.cs # Varex 探测器实现
│ └── IRayDetector.cs # iRay 探测器实现
├── Factories/ # 工厂层 | Factory Layer
│ ├── IDetectorFactory.cs # 工厂接口
│ └── DetectorFactory.cs # 探测器工厂
├── Services/ # 业务服务层 | Service Layer
│ ├── IDetectorService.cs # 服务接口
│ └── DetectorService.cs # 服务实现(单例)
├── Config/ # 配置层 | Configuration Layer
│ ├── DetectorConfig.cs # 配置实体
│ ├── VarexDetectorConfig.cs
│ ├── IRayDetectorConfig.cs
│ └── ConfigLoader.cs # 配置加载器
├── Module/ # Prism 模块 | Prism Module
│ └── DetectorModule.cs # 模块注册
├── ViewModels/ # 视图模型 | View Models
└── Views/ # WPF 视图 | WPF Views
```
### 设计模式 | Design Patterns
- **策略模式**`IAreaDetector` 接口定义统一操作,支持多种设备实现
- **工厂模式**`IDetectorFactory` 根据设备类型动态创建实例
- **模板方法模式**`AreaDetectorBase` 提供基础实现框架
- **单例模式**`IDetectorService` 作为全局单例管理设备状态
- **依赖注入**:通过 Prism 容器管理服务生命周期
- **事件聚合器**:使用 Prism `IEventAggregator` 实现松耦合通讯
- **异步模式**:所有 I/O 操作采用 async/await
---
## 核心功能 | Core Features
### 1. 探测器初始化 | Detector Initialization
```csharp
// 初始化探测器
DetectorResult result = await _detectorService.InitializeAsync();
if (result.IsSuccess)
{
// 获取探测器信息
DetectorInfo info = _detectorService.GetInfo();
Console.WriteLine($"型号: {info.Model}, 分辨率: {info.MaxWidth}x{info.MaxHeight}");
}
```
### 2. 图像采集 | Image Acquisition
```csharp
// 单帧采集
await _detectorService.AcquireSingleFrameAsync();
// 启动连续采集
await _detectorService.StartAcquisitionAsync();
// 停止采集
await _detectorService.StopAcquisitionAsync();
```
### 3. 自动校正 | Auto Correction
```csharp
// 执行自动校正(暗场+增益+坏像素)
await _detectorService.AutoCorrectionAsync(frameCount: 10);
```
### 4. 状态监控 | Status Monitoring
```csharp
// 检查探测器状态
DetectorStatus status = _detectorService.Status;
// 获取错误信息
DetectorResult error = _detectorService.GetLastError();
```
### 5. 事件通讯 | Event Communication
```csharp
// 订阅图像采集事件
_eventAggregator.GetEvent<ImageCapturedEvent>()
.Subscribe(OnImageCaptured, ThreadOption.UIThread);
// 订阅校正完成事件
_eventAggregator.GetEvent<CorrectionCompletedEvent>()
.Subscribe(OnCorrectionCompleted, ThreadOption.UIThread);
```
---
## 技术要求 | Technical Requirements
### 运行环境 | Runtime Environment
- **.NET 8.0** (net8.0-windows7.0)
- **Windows 操作系统**WPF 依赖)
- **Visual Studio 2022** 或更高版本
### 核心依赖 | Core Dependencies
| 依赖库 | 版本 | 用途 |
|--------|------|------|
| **Prism.Wpf** | 9.0.537 | MVVM 框架和依赖注入 |
| **Serilog** | - | 结构化日志记录 |
| **System.Configuration.ConfigurationManager** | - | 配置文件管理 |
| **探测器 SDK** | - | 厂商提供的探测器驱动库 |
### 硬件要求 | Hardware Requirements
- 支持的探测器型号:
- Varex(当前支持)
- iRay(预留)
- Hamamatsu(预留)
- 网络连接:TCP/IP 或 USB
- 探测器驱动和 SDK
---
## 快速开始 | Quick Start
### 1. 配置文件设置
`App.config` 中添加:
```xml
<appSettings>
<!-- 探测器类型 -->
<add key="Detector:Type" value="Varex" />
<!-- 网络配置 -->
<add key="Detector:IP" value="192.168.1.200" />
<add key="Detector:Port" value="50000" />
<!-- 图像存储配置 -->
<add key="Detector:SavePath" value="D:\Images" />
<add key="Detector:AutoSave" value="true" />
</appSettings>
```
### 2. 注册模块
`App.xaml.cs` 中:
```csharp
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<DetectorModule>();
}
```
### 3. 使用服务
在 ViewModel 中注入并使用:
```csharp
public class YourViewModel : BindableBase
{
private readonly IDetectorService _detectorService;
private readonly IEventAggregator _eventAggregator;
public YourViewModel(
IDetectorService detectorService,
IEventAggregator eventAggregator)
{
_detectorService = detectorService;
_eventAggregator = eventAggregator;
SubscribeEvents();
}
public async Task InitializeAsync()
{
DetectorResult result = await _detectorService.InitializeAsync();
if (result.IsSuccess)
{
// 初始化成功
}
}
}
```
---
## 配置参数说明 | Configuration Parameters
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| Type | string | Varex | 探测器类型 |
| IP | string | 192.168.1.200 | 探测器 IP 地址 |
| Port | int | 50000 | 探测器端口号 |
| SavePath | string | D:\Images | 图像存储路径 |
| AutoSave | bool | true | 是否自动保存图像 |
---
## 探测器状态 | Detector Status
```csharp
public enum DetectorStatus
{
Uninitialized = 0, // 未初始化
Initializing = 1, // 初始化中
Ready = 2, // 就绪
Acquiring = 3, // 采集中
Correcting = 4, // 校正中
Error = 5 // 错误
}
```
---
## 异常处理 | Exception Handling
所有操作返回 `DetectorResult` 对象:
```csharp
DetectorResult result = await _detectorService.AcquireSingleFrameAsync();
if (result.IsSuccess)
{
// 操作成功
}
else
{
// 操作失败
string error = result.ErrorMessage;
int errorCode = result.ErrorCode;
Exception exception = result.Exception;
_logger.Error(exception, $"操作失败: {error}, 错误码: {errorCode}");
}
```
---
## 日志记录 | Logging
模块使用 Serilog 进行结构化日志记录:
- **Debug**:详细的调试信息(帧采集、状态变化等)
- **Info**:一般信息(初始化成功、采集开始等)
- **Warn**:警告信息(状态异常、参数警告等)
- **Error**:错误信息(操作失败、通讯异常等)
- **Fatal**:致命错误(系统崩溃等)
---
## 资源释放 | Resource Disposal
在应用退出时探测器会自动停止采集并释放资源:
```csharp
protected override void OnExit(ExitEventArgs e)
{
var detectorService = Container.Resolve<IDetectorService>();
// 服务会自动释放资源
base.OnExit(e);
}
```
---
## 扩展性 | Extensibility
### 添加新的探测器设备
1. 在 `Implementations/` 创建新类继承 `AreaDetectorBase`
2. 实现所有抽象方法
3. 在 `DetectorFactory.CreateDetector()` 添加新类型分支
4. 在配置文件中添加设备配置
```csharp
public class NewDetector : AreaDetectorBase
{
public override async Task<DetectorResult> InitializeAsync(CancellationToken cancellationToken = default)
{
// 实现初始化逻辑
}
// 实现其他抽象方法...
}
```
---
## 文档索引 | Documentation Index
- **[GUIDENCE.md](./GUIDENCE.md)** - 详细使用指南,包含完整代码示例
- **[README.md](./README.md)** - 本文档,项目概述和快速参考
- **[App.config.example](./App.config.example)** - 配置文件示例
---
## 故障排查 | Troubleshooting
### 初始化失败
- 检查探测器 IP 地址和端口配置
- 确认探测器网络连接正常
- 验证探测器驱动已正确安装
### 采集失败
- 确认已成功初始化
- 检查探测器状态是否为 `Ready`
- 执行自动校正
### 校正失败
- 确认探测器状态为 `Ready`
- 增益校正前确保射线源已开启
- 增加采集帧数(建议 10 帧以上)
详细故障排查请参考 [GUIDENCE.md](./GUIDENCE.md) 第 13 节。
---
**最后更新 | Last Updated**: 2026-03-11
@@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using Prism.Events;
using Prism.Ioc;
using XP.Hardware.Detector.Abstractions;
using XP.Hardware.Detector.Abstractions.Enums;
using XP.Hardware.Detector.Config;
using XP.Hardware.Detector.Implementations;
using XP.Common.Logging.Interfaces;
namespace XP.Hardware.Detector.Factories
{
/// <summary>
/// 探测器工厂实现 | Detector factory implementation
/// 配置驱动的探测器实例创建
/// </summary>
public class DetectorFactory : IDetectorFactory
{
private readonly IEventAggregator _eventAggregator;
private readonly IContainerProvider _containerProvider;
private readonly ILoggerService _logger;
/// <summary>
/// 构造函数 | Constructor
/// </summary>
/// <param name="eventAggregator">事件聚合器 | Event aggregator</param>
/// <param name="containerProvider">Prism 容器 | Prism container</param>
/// <param name="logger">日志服务 | Logger service</param>
public DetectorFactory(IEventAggregator eventAggregator, IContainerProvider containerProvider, ILoggerService logger = null)
{
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
_containerProvider = containerProvider ?? throw new ArgumentNullException(nameof(containerProvider));
_logger = logger?.ForModule("DetectorFactory");
}
/// <summary>
/// 创建探测器实例 | Create detector instance
/// </summary>
public DetectorResult<IAreaDetector> CreateDetector(DetectorConfig config)
{
if (config == null)
{
var errorMsg = "配置不能为空 | Configuration cannot be null";
_logger?.Error(null, errorMsg);
return DetectorResult<IAreaDetector>.Failure(errorMsg);
}
try
{
_logger?.Info($"开始创建探测器,类型:{config.Type} | Starting to create detector, type: {config.Type}");
IAreaDetector detector = config.Type switch
{
DetectorType.Varex => CreateVarexDetector(config),
//DetectorType.IRay => CreateIRayDetector(config),
DetectorType.Hamamatsu => throw new NotImplementedException("Hamamatsu 探测器尚未实现 | Hamamatsu detector not implemented yet"),
_ => throw new NotSupportedException($"不支持的探测器类型:{config.Type} | Unsupported detector type: {config.Type}")
};
// 注册到容器(通过 IContainerExtension 注册实例)| Register to container
var containerExtension = _containerProvider.Resolve<IContainerExtension>();
containerExtension.RegisterInstance<IAreaDetector>(detector);
_logger?.Debug("探测器实例已注册到容器 | Detector instance registered to container");
_logger?.Info($"探测器创建成功,类型:{config.Type} | Detector created successfully, type: {config.Type}");
return DetectorResult<IAreaDetector>.Success(detector, "探测器创建成功 | Detector created successfully");
}
catch (NotImplementedException ex)
{
var errorMsg = $"探测器类型尚未实现 | Detector type not implemented: {ex.Message}";
_logger?.Warn(errorMsg);
return DetectorResult<IAreaDetector>.Failure(errorMsg, ex, -2);
}
catch (NotSupportedException ex)
{
var errorMsg = $"不支持的探测器类型 | Unsupported detector type: {ex.Message}";
_logger?.Error(ex, errorMsg);
return DetectorResult<IAreaDetector>.Failure(errorMsg, ex, -3);
}
catch (Exception ex)
{
var errorMsg = $"创建探测器失败 | Failed to create detector: {ex.Message}";
_logger?.Error(ex, errorMsg);
return DetectorResult<IAreaDetector>.Failure(errorMsg, ex, -1);
}
}
/// <summary>
/// 创建 Varex 探测器 | Create Varex detector
/// </summary>
private IAreaDetector CreateVarexDetector(DetectorConfig config)
{
var varexConfig = config as VarexDetectorConfig
?? throw new ArgumentException("Varex 探测器需要 VarexDetectorConfig | Varex detector requires VarexDetectorConfig");
_logger?.Debug($"创建 Varex 探测器实例,Binning 模式:{varexConfig.BinningMode},增益模式:{varexConfig.GainMode} | Creating Varex detector instance, Binning mode: {varexConfig.BinningMode}, Gain mode: {varexConfig.GainMode}");
return new VarexDetector(varexConfig, _eventAggregator, _logger);
}
///// <summary>
///// 创建 iRay 探测器 | Create iRay detector
///// </summary>
//private IAreaDetector CreateIRayDetector(DetectorConfig config)
//{
// var irayConfig = config as IRayDetectorConfig
// ?? throw new ArgumentException("iRay 探测器需要 IRayDetectorConfig | iRay detector requires IRayDetectorConfig");
// return new IRayDetector(irayConfig, _eventAggregator);
//}
/// <summary>
/// 获取支持的探测器类型 | Get supported detector types
/// </summary>
public IEnumerable<DetectorType> GetSupportedTypes()
{
return new[] { DetectorType.Varex, DetectorType.IRay };
}
}
}
@@ -0,0 +1,27 @@
using System.Collections.Generic;
using XP.Hardware.Detector.Abstractions;
using XP.Hardware.Detector.Abstractions.Enums;
using XP.Hardware.Detector.Config;
namespace XP.Hardware.Detector.Factories
{
/// <summary>
/// 探测器工厂接口 | Detector factory interface
/// 根据配置创建探测器实例
/// </summary>
public interface IDetectorFactory
{
/// <summary>
/// 创建探测器实例 | Create detector instance
/// </summary>
/// <param name="config">探测器配置 | Detector configuration</param>
/// <returns>探测器实例或错误结果 | Detector instance or error result</returns>
DetectorResult<IAreaDetector> CreateDetector(DetectorConfig config);
/// <summary>
/// 获取支持的探测器类型 | Get supported detector types
/// </summary>
/// <returns>支持的类型列表 | List of supported types</returns>
IEnumerable<DetectorType> GetSupportedTypes();
}
}
@@ -0,0 +1,199 @@
using Prism.Events;
using System;
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.Implementations
{
/// <summary>
/// iRay 探测器实现 | iRay detector implementation
/// 继承抽象基类,实现 iRay 专属接口 | Inherits abstract base class, implements iRay specific interface
/// 注意:iRay 探测器实现待后续完成,当前为占位符 | Note: iRay detector implementation to be completed later, currently placeholder
/// </summary>
public class IRayDetector : AreaDetectorBase, IIRayDetector
{
private readonly IRayDetectorConfig _config;
private AcquisitionMode _acquisitionMode;
private double _gain;
/// <summary>
/// 探测器类型 | Detector type
/// </summary>
public override DetectorType Type => DetectorType.IRay;
/// <summary>
/// 构造函数 | Constructor
/// </summary>
/// <param name="config">iRay 探测器配置 | iRay detector configuration</param>
/// <param name="eventAggregator">事件聚合器 | Event aggregator</param>
public IRayDetector(IRayDetectorConfig config, IEventAggregator eventAggregator)
: base(eventAggregator)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
_acquisitionMode = config.AcquisitionMode;
_gain = config.DefaultGain;
}
#region IAreaDetector | IAreaDetector abstract method implementations
/// <summary>
/// 初始化探测器(内部实现)| Initialize detector (internal implementation)
/// </summary>
protected override Task<DetectorResult> InitializeInternalAsync(CancellationToken cancellationToken)
{
// TODO: 实现 iRay 探测器初始化逻辑 | Implement iRay detector initialization logic
throw new NotImplementedException("iRay 探测器初始化尚未实现 | iRay detector initialization not implemented yet");
}
/// <summary>
/// 启动连续采集(内部实现)| Start continuous acquisition (internal implementation)
/// </summary>
protected override Task<DetectorResult> StartAcquisitionInternalAsync(CancellationToken cancellationToken)
{
// TODO: 实现 iRay 探测器启动采集逻辑 | Implement iRay detector start acquisition logic
throw new NotImplementedException("iRay 探测器启动采集尚未实现 | iRay detector start acquisition not implemented yet");
}
/// <summary>
/// 停止采集(内部实现)| Stop acquisition (internal implementation)
/// </summary>
protected override Task<DetectorResult> StopAcquisitionInternalAsync(CancellationToken cancellationToken)
{
// TODO: 实现 iRay 探测器停止采集逻辑 | Implement iRay detector stop acquisition logic
throw new NotImplementedException("iRay 探测器停止采集尚未实现 | iRay detector stop acquisition not implemented yet");
}
/// <summary>
/// 单帧采集(内部实现)| Single frame acquisition (internal implementation)
/// </summary>
protected override Task<DetectorResult> AcquireSingleFrameInternalAsync(CancellationToken cancellationToken)
{
// TODO: 实现 iRay 探测器单帧采集逻辑 | Implement iRay detector single frame acquisition logic
throw new NotImplementedException("iRay 探测器单帧采集尚未实现 | iRay detector single frame acquisition not implemented yet");
}
/// <summary>
/// 暗场校正(内部实现)| Dark field correction (internal implementation)
/// </summary>
protected override Task<DetectorResult> DarkCorrectionInternalAsync(int frameCount, CancellationToken cancellationToken)
{
// TODO: 实现 iRay 探测器暗场校正逻辑 | Implement iRay detector dark correction logic
throw new NotImplementedException("iRay 探测器暗场校正尚未实现 | iRay detector dark correction not implemented yet");
}
/// <summary>
/// 增益校正(内部实现)| Gain correction (internal implementation)
/// </summary>
protected override Task<DetectorResult> GainCorrectionInternalAsync(int frameCount, CancellationToken cancellationToken)
{
// TODO: 实现 iRay 探测器增益校正逻辑 | Implement iRay detector gain correction logic
throw new NotImplementedException("iRay 探测器增益校正尚未实现 | iRay detector gain correction not implemented yet");
}
/// <summary>
/// 自动校正(内部实现)| Auto correction (internal implementation)
/// </summary>
protected override Task<DetectorResult> AutoCorrectionInternalAsync(int frameCount, CancellationToken cancellationToken)
{
// TODO: 实现 iRay 探测器自动校正逻辑 | Implement iRay detector auto correction logic
throw new NotImplementedException("iRay 探测器自动校正尚未实现 | iRay detector auto correction not implemented yet");
}
/// <summary>
/// 坏像素校正(内部实现)| Bad pixel correction (internal implementation)
/// </summary>
protected override Task<DetectorResult> BadPixelCorrectionInternalAsync(CancellationToken cancellationToken)
{
// TODO: 实现 iRay 探测器坏像素校正逻辑 | Implement iRay detector bad pixel correction logic
throw new NotImplementedException("iRay 探测器坏像素校正尚未实现 | iRay detector bad pixel correction not implemented yet");
}
/// <summary>
/// 获取探测器信息 | Get detector information
/// </summary>
public override DetectorInfo GetInfo()
{
// TODO: 实现 iRay 探测器信息查询逻辑 | Implement iRay detector info query logic
return new DetectorInfo
{
Type = DetectorType.IRay,
Model = "iRay (待实现 | To be implemented)",
SerialNumber = "N/A",
FirmwareVersion = "N/A",
MaxWidth = 0,
MaxHeight = 0,
PixelSize = 0.0,
BitDepth = 16
};
}
#endregion
#region IIRayDetector | IIRayDetector interface implementations
/// <summary>
/// 设置采集模式 | Set acquisition mode
/// </summary>
public Task<DetectorResult> SetAcquisitionModeAsync(AcquisitionMode mode)
{
// TODO: 实现 iRay 探测器设置采集模式逻辑 | Implement iRay detector set acquisition mode logic
_acquisitionMode = mode;
return Task.FromResult(DetectorResult.Success($"采集模式已设置为 {mode}(占位符实现)| Acquisition mode set to {mode} (placeholder implementation)"));
}
/// <summary>
/// 获取采集模式 | Get acquisition mode
/// </summary>
public AcquisitionMode GetAcquisitionMode()
{
return _acquisitionMode;
}
/// <summary>
/// 设置增益值 | Set gain value
/// </summary>
public Task<DetectorResult> SetGainAsync(double gain)
{
// TODO: 实现 iRay 探测器设置增益逻辑 | Implement iRay detector set gain logic
_gain = gain;
return Task.FromResult(DetectorResult.Success($"增益已设置为 {gain}(占位符实现)| Gain set to {gain} (placeholder implementation)"));
}
/// <summary>
/// 获取增益值 | Get gain value
/// </summary>
public double GetGain()
{
return _gain;
}
#endregion
#region | Resource disposal
/// <summary>
/// 释放资源 | Dispose resources
/// </summary>
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// TODO: 释放 iRay 探测器托管资源 | Release iRay detector managed resources
}
// TODO: 释放 iRay 探测器非托管资源 | Release iRay detector unmanaged resources
_disposed = true;
}
base.Dispose(disposing);
}
#endregion
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,289 @@
using System;
using System.Runtime.InteropServices;
namespace XP.Hardware.Detector.Implementations
{
/// <summary>
/// XISL API 互操作层 | XISL API interop layer
/// 封装 Varex 探测器的 xisl.dll 原厂 API
/// </summary>
public static class XISLApi
{
#region | Enum Definitions
/// <summary>
/// XISL API 返回码 | XISL API return codes
/// </summary>
public enum HIS_RETURN
{
HIS_ALL_OK = 0,
HIS_Init = -1,
HIS_ERROR_MEMORY = 1,
HIS_ERROR_BOARDINIT = 2,
HIS_ERROR_NOBOARD = 3,
HIS_ERROR_NOCAMERA = 4,
HIS_ERROR_TIMEOUT = 5,
HIS_ERROR_INVALIDPARAM = 6,
HIS_ERROR_ABORT = 7
}
#endregion
#region | Constant Definitions
/// <summary>
/// 连续采集模式 | Continuous acquisition mode
/// </summary>
public const int HIS_SEQ_CONTINUOUS = 0x100;
/// <summary>
/// 单帧采集模式(单缓冲区)| Single frame mode (one buffer)
/// </summary>
public const int HIS_SEQ_ONE_BUFFER = 0x2;
/// <summary>
/// 采集数据标志:连续采集 | Acquisition data flag: continuous
/// </summary>
public const uint ACQ_CONT = 1;
/// <summary>
/// 采集数据标志:暗场校正 | Acquisition data flag: offset correction
/// </summary>
public const uint ACQ_OFFSET = 2;
/// <summary>
/// 采集数据标志:增益校正 | Acquisition data flag: gain correction
/// </summary>
public const uint ACQ_GAIN = 4;
/// <summary>
/// 采集数据标志:单帧采集 | Acquisition data flag: snap (single frame)
/// </summary>
public const uint ACQ_SNAP = 8;
/// <summary>
/// 等待对象信号 | Wait object signaled
/// </summary>
public const uint WAIT_OBJECT_0 = 0x00000000;
/// <summary>
/// 无限等待 | Infinite wait
/// </summary>
public const uint INFINITE = 0xFFFFFFFF;
/// <summary>
/// 内部定时器同步模式 | Internal timer sync mode
/// </summary>
public const int HIS_SYNCMODE_INTERNAL_TIMER = 2;
#endregion
#region | Callback Delegates
/// <summary>
/// 帧结束回调函数委托 | End frame callback delegate
/// </summary>
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void EndFrameCallback(IntPtr hAcqDesc);
/// <summary>
/// 采集结束回调函数委托 | End acquisition callback delegate
/// </summary>
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void EndAcqCallback(IntPtr hAcqDesc);
#endregion
#region Win32 API | Win32 API
/// <summary>
/// 创建事件对象 | Create event object
/// </summary>
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);
/// <summary>
/// 设置事件信号 | Set event signal
/// </summary>
[DllImport("kernel32.dll")]
public static extern bool SetEvent(IntPtr hEvent);
/// <summary>
/// 等待单个对象 | Wait for single object
/// </summary>
[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
/// <summary>
/// 关闭句柄 | Close handle
/// </summary>
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);
#endregion
#region XISL API - | XISL API - Basic Functions
/// <summary>
/// 获取下一个探测器传感器 | Get next sensor
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_GetNextSensor", CallingConvention = CallingConvention.StdCall)]
public static extern HIS_RETURN Acquisition_GetNextSensor(ref IntPtr pAcqDesc, ref IntPtr hWnd);
/// <summary>
/// 获取探测器配置信息 | Get detector configuration
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_GetConfiguration", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_GetConfiguration(
IntPtr hAcqDesc, out uint dwFrames, out uint dwRows, out uint dwColumns,
out uint dwDataType, out uint dwSortFlags, out int iIRQFlags,
out uint dwAcqType, out uint dwSystemID, out uint dwSyncMode, out uint dwHwAccess);
/// <summary>
/// 获取采集数据指针 | Get acquisition data pointer
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_GetAcqData", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_GetAcqData(IntPtr hAcqDesc, out IntPtr vpAcqData);
/// <summary>
/// 设置采集数据标志 | Set acquisition data flags
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_SetAcqData", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_SetAcqData(IntPtr hAcqDesc, ref uint dwAcqData);
/// <summary>
/// 定义目标缓冲区 | Define destination buffers
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_DefineDestBuffers", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_DefineDestBuffers(IntPtr hAcqDesc, IntPtr pBuffer, int iFrames, uint dwRows, uint dwColumns);
/// <summary>
/// 设置回调函数和消息 | Set callbacks and messages
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_SetCallbacksAndMessages", CallingConvention = CallingConvention.StdCall)]
public static extern HIS_RETURN Acquisition_SetCallbacksAndMessages(
IntPtr pAcqDesc,
IntPtr hWnd,
uint dwErrorMsg,
uint dwLoosingFramesMsg,
EndFrameCallback lpfnEndFrameCallback,
EndAcqCallback lpfnEndAcqCallback);
/// <summary>
/// 关闭所有连接 | Close all connections
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_CloseAll", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_CloseAll();
#endregion
#region XISL API - | XISL API - Parameter Settings
/// <summary>
/// 设置 Binning 模式 | Set binning mode
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_SetCameraBinningMode", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_SetCameraBinningMode(IntPtr hAcqDesc, uint wMode);
/// <summary>
/// 设置增益模式 | Set gain mode
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_SetCameraGain", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_SetCameraGain(IntPtr hAcqDesc, uint wMode);
/// <summary>
/// 设置定时器同步(曝光时间)| Set timer sync (exposure time)
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_SetTimerSync", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_SetTimerSync(IntPtr hAcqDesc, ref uint wMode);
/// <summary>
/// 设置帧同步模式 | Set frame sync mode
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_SetFrameSyncMode", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_SetFrameSyncMode(IntPtr hAcqDesc, uint dwMode);
/// <summary>
/// 设置相机触发模式 | Set camera trigger mode
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_SetCameraTriggerMode", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_SetCameraTriggerMode(IntPtr hAcqDesc, uint dwMode);
#endregion
#region XISL API - | XISL API - Image Acquisition
/// <summary>
/// 采集图像 | Acquire image
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_Acquire_Image", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_Acquire_Image(
IntPtr hAcqDesc,
uint dwFrames,
uint dwSkipFrms,
uint dwOpt,
IntPtr pwOffsetData,
IntPtr pdwGainData,
IntPtr pdwPxlCorrList);
/// <summary>
/// 停止采集 | Abort acquisition
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_Abort", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_Abort(IntPtr hAcqDesc);
/// <summary>
/// 获取当前帧号 | Get current frame number
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_GetActFrame", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_GetActFrame(IntPtr hAcqDesc, out uint dwActFrame, out uint dwSecFrame);
#endregion
#region XISL API - | XISL API - Correction Functions
/// <summary>
/// 采集暗场图像 | Acquire offset image
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_Acquire_OffsetImage", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_Acquire_OffsetImage(IntPtr hAcqDesc, IntPtr pOffsetData, uint nRows, uint nCols, uint nFrames);
/// <summary>
/// 采集增益图像 | Acquire gain image
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_Acquire_GainImage", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_Acquire_GainImage(IntPtr hAcqDesc, IntPtr pOffsetData, IntPtr pGainData, uint nRows, uint nCols, uint nFrames);
/// <summary>
/// 创建增益映射 | Create gain map
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_CreateGainMap", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_CreateGainMap(IntPtr pGainData, IntPtr pGainAVG, int nCount, int nFrame);
/// <summary>
/// 创建坏像素列表 | Create pixel map
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_CreatePixelMap", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_CreatePixelMap(IntPtr pData, int nDataRows, int nDataColumns, IntPtr pCorrList, ref int nCorrListSize);
/// <summary>
/// 重置板载选项 | Reset onboard options
/// </summary>
[DllImport("xisl.dll", EntryPoint = "Acquisition_Reset_OnboardOptions", CallingConvention = CallingConvention.Cdecl)]
public static extern HIS_RETURN Acquisition_Reset_OnboardOptions(IntPtr hAcqDesc);
#endregion
#region VarexDetDll API - Varex | VarexDetDll API - Varex Detector Initialization
/// <summary>
/// 探测器底层初始化 | Detector low-level initialization
/// 由 VarexDetDll.dll 提供,内部完成枚举、连接等底层操作
/// 返回 0 表示成功 | Returns 0 on success
/// </summary>
[DllImport("VarexDetDll.dll", EntryPoint = "DoDetInit", CallingConvention = CallingConvention.StdCall)]
public static extern int DoDetInit(ref IntPtr hAcqDesc);
#endregion
}
}
@@ -0,0 +1,54 @@
using Prism.Ioc;
using Prism.Modularity;
using System.Resources;
using XP.Common.Localization;
using XP.Common.Localization.Interfaces;
using XP.Hardware.Detector.Factories;
using XP.Hardware.Detector.Services;
namespace XP.Hardware.Detector.Module
{
/// <summary>
/// 探测器模块 | Detector Module
/// Prism 模块入口,注册服务/工厂/事件到 Unity 容器 | Prism module entry, register services/factories/events to Unity container
/// </summary>
[Module(ModuleName = "DetectorModule")]
public class DetectorModule : IModule
{
/// <summary>
/// 模块初始化 | Module initialization
/// </summary>
public void OnInitialized(IContainerProvider containerProvider)
{
// 注册模块级多语言资源到 Fallback Chain | Register module-level localization resources to Fallback Chain
var localizationService = containerProvider.Resolve<ILocalizationService>();
var resourceManager = new ResourceManager(
"XP.Hardware.Detector.Resources.Resources",
typeof(DetectorModule).Assembly);
localizationService.RegisterResourceSource("XP.Hardware.Detector", resourceManager);
// 初始化 LocalizationHelper,使其通过 ILocalizationService 获取字符串(支持 Fallback Chain
// Initialize LocalizationHelper to use ILocalizationService for string lookup (supports Fallback Chain)
LocalizationHelper.Initialize(localizationService);
System.Console.WriteLine("[DetectorModule] 模块已初始化 | Module initialized");
}
/// <summary>
/// 注册类型到 DI 容器 | Register types to DI container
/// </summary>
public void RegisterTypes(IContainerRegistry containerRegistry)
{
// 注册工厂层(Transient| Register factory layer (Transient)
containerRegistry.Register<IDetectorFactory, DetectorFactory>();
// 注册服务层(Singleton| Register service layer (Singleton)
containerRegistry.RegisterSingleton<IDetectorService, DetectorService>();
// 注册图像服务(Singleton| Register image service (Singleton)
containerRegistry.RegisterSingleton<IImageService, ImageService>();
System.Console.WriteLine("[DetectorModule] 类型注册完成 | Type registration completed");
}
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
+315
View File
@@ -0,0 +1,315 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
//
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace XP.Hardware.Detector.Resources {
using System;
/// <summary>
/// 一个强类型的资源类,用于查找本地化的字符串等。
/// </summary>
// 此类是由 StronglyTypedResourceBuilder
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// 返回此类使用的缓存的 ResourceManager 实例。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("XP.Hardware.Detector.Resources.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// 重写当前线程的 CurrentUICulture 属性,对
/// 使用此强类型资源类的所有资源查找执行重写。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// 查找类似 应用参数 的本地化字符串。
/// </summary>
internal static string Detector_ApplyParametersButton {
get {
return ResourceManager.GetString("Detector_ApplyParametersButton", resourceCulture);
}
}
/// <summary>
/// 查找类似 帧合并 的本地化字符串。
/// </summary>
internal static string Detector_AvgFramesLabel {
get {
return ResourceManager.GetString("Detector_AvgFramesLabel", resourceCulture);
}
}
/// <summary>
/// 查找类似 坏像素校正 的本地化字符串。
/// </summary>
internal static string Detector_BadPixelCorrectionButton {
get {
return ResourceManager.GetString("Detector_BadPixelCorrectionButton", resourceCulture);
}
}
/// <summary>
/// 查找类似 像素合并 的本地化字符串。
/// </summary>
internal static string Detector_BinningLabel {
get {
return ResourceManager.GetString("Detector_BinningLabel", resourceCulture);
}
}
/// <summary>
/// 查找类似 探测器配置 的本地化字符串。
/// </summary>
internal static string Detector_ConfigWindowTitle {
get {
return ResourceManager.GetString("Detector_ConfigWindowTitle", resourceCulture);
}
}
/// <summary>
/// 查找类似 连接探测器 的本地化字符串。
/// </summary>
internal static string Detector_ConnectButton {
get {
return ResourceManager.GetString("Detector_ConnectButton", resourceCulture);
}
}
/// <summary>
/// 查找类似 已连接 的本地化字符串。
/// </summary>
internal static string Detector_Connected {
get {
return ResourceManager.GetString("Detector_Connected", resourceCulture);
}
}
/// <summary>
/// 查找类似 探测器连接失败 的本地化字符串。
/// </summary>
internal static string Detector_ConnectFailed {
get {
return ResourceManager.GetString("Detector_ConnectFailed", resourceCulture);
}
}
/// <summary>
/// 查找类似 探测器连接成功 的本地化字符串。
/// </summary>
internal static string Detector_ConnectSuccess {
get {
return ResourceManager.GetString("Detector_ConnectSuccess", resourceCulture);
}
}
/// <summary>
/// 查找类似 暗场校正 的本地化字符串。
/// </summary>
internal static string Detector_DarkCorrectionButton {
get {
return ResourceManager.GetString("Detector_DarkCorrectionButton", resourceCulture);
}
}
/// <summary>
/// 查找类似 断开探测器 的本地化字符串。
/// </summary>
internal static string Detector_DisconnectButton {
get {
return ResourceManager.GetString("Detector_DisconnectButton", resourceCulture);
}
}
/// <summary>
/// 查找类似 未连接 的本地化字符串。
/// </summary>
internal static string Detector_Disconnected {
get {
return ResourceManager.GetString("Detector_Disconnected", resourceCulture);
}
}
/// <summary>
/// 查找类似 探测器断开成功 的本地化字符串。
/// </summary>
internal static string Detector_DisconnectSuccess {
get {
return ResourceManager.GetString("Detector_DisconnectSuccess", resourceCulture);
}
}
/// <summary>
/// 查找类似 帧率 的本地化字符串。
/// </summary>
internal static string Detector_FrameRateLabel {
get {
return ResourceManager.GetString("Detector_FrameRateLabel", resourceCulture);
}
}
/// <summary>
/// 查找类似 探测器图像 的本地化字符串。
/// </summary>
internal static string Detector_ImageWindowTitle {
get {
return ResourceManager.GetString("Detector_ImageWindowTitle", resourceCulture);
}
}
/// <summary>
/// 查找类似 亮场校正 的本地化字符串。
/// </summary>
internal static string Detector_LightCorrectionButton {
get {
return ResourceManager.GetString("Detector_LightCorrectionButton", resourceCulture);
}
}
/// <summary>
/// 查找类似 型号: 的本地化字符串。
/// </summary>
internal static string Detector_ModelLabel {
get {
return ResourceManager.GetString("Detector_ModelLabel", resourceCulture);
}
}
/// <summary>
/// 查找类似 像素尺寸: 的本地化字符串。
/// </summary>
internal static string Detector_PixelSizeLabel {
get {
return ResourceManager.GetString("Detector_PixelSizeLabel", resourceCulture);
}
}
/// <summary>
/// 查找类似 分辨率: 的本地化字符串。
/// </summary>
internal static string Detector_ResolutionLabel {
get {
return ResourceManager.GetString("Detector_ResolutionLabel", resourceCulture);
}
}
/// <summary>
/// 查找类似 灵敏度 的本地化字符串。
/// </summary>
internal static string Detector_SensitivityLabel {
get {
return ResourceManager.GetString("Detector_SensitivityLabel", resourceCulture);
}
}
/// <summary>
/// 查找类似 单帧采集 的本地化字符串。
/// </summary>
internal static string Detector_SingleFrameButton {
get {
return ResourceManager.GetString("Detector_SingleFrameButton", resourceCulture);
}
}
/// <summary>
/// 查找类似 开始采集 的本地化字符串。
/// </summary>
internal static string Detector_StartAcquisitionButton {
get {
return ResourceManager.GetString("Detector_StartAcquisitionButton", resourceCulture);
}
}
/// <summary>
/// 查找类似 采集中 的本地化字符串。
/// </summary>
internal static string Detector_Status_Acquiring {
get {
return ResourceManager.GetString("Detector_Status_Acquiring", resourceCulture);
}
}
/// <summary>
/// 查找类似 空闲 的本地化字符串。
/// </summary>
internal static string Detector_StatusIdle {
get {
return ResourceManager.GetString("Detector_StatusIdle", resourceCulture);
}
}
/// <summary>
/// 查找类似 状态: 的本地化字符串。
/// </summary>
internal static string Detector_StatusLabel {
get {
return ResourceManager.GetString("Detector_StatusLabel", resourceCulture);
}
}
/// <summary>
/// 查找类似 停止采集 的本地化字符串。
/// </summary>
internal static string Detector_StopAcquisitionButton {
get {
return ResourceManager.GetString("Detector_StopAcquisitionButton", resourceCulture);
}
}
/// <summary>
/// 查找类似 探测器控制 的本地化字符串。
/// </summary>
internal static string Detector_Title {
get {
return ResourceManager.GetString("Detector_Title", resourceCulture);
}
}
/// <summary>
/// 查找类似 类型: 的本地化字符串。
/// </summary>
internal static string Detector_TypeLabel {
get {
return ResourceManager.GetString("Detector_TypeLabel", resourceCulture);
}
}
}
}
@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Detector_Title" xml:space="preserve">
<value>Detector Control</value>
</data>
<data name="Detector_ConnectSuccess" xml:space="preserve">
<value>Detector connected successfully</value>
</data>
<data name="Detector_ConnectFailed" xml:space="preserve">
<value>Detector connection failed</value>
</data>
<data name="Detector_DisconnectSuccess" xml:space="preserve">
<value>Detector disconnected successfully</value>
</data>
<data name="Detector_Status_Acquiring" xml:space="preserve">
<value>Acquiring</value>
</data>
<data name="Detector_SensitivityLabel" xml:space="preserve">
<value>Sensitivity</value>
</data>
<data name="Detector_FrameRateLabel" xml:space="preserve">
<value>Frame Rate</value>
</data>
<data name="Detector_BinningLabel" xml:space="preserve">
<value>Binning</value>
</data>
<data name="Detector_AvgFramesLabel" xml:space="preserve">
<value>Avg Frames</value>
</data>
<data name="Detector_ConfigWindowTitle" xml:space="preserve">
<value>Detector Configuration</value>
</data>
<data name="Detector_DarkCorrectionButton" xml:space="preserve">
<value>Dark Correction</value>
</data>
<data name="Detector_LightCorrectionButton" xml:space="preserve">
<value>Light Correction</value>
</data>
<data name="Detector_BadPixelCorrectionButton" xml:space="preserve">
<value>Bad Pixel Correction</value>
</data>
<data name="Detector_ApplyParametersButton" xml:space="preserve">
<value>Apply Parameters</value>
</data>
<data name="Detector_ImageWindowTitle" xml:space="preserve">
<value>Detector Image</value>
</data>
<data name="Detector_StartAcquisitionButton" xml:space="preserve">
<value>Start</value>
</data>
<data name="Detector_StopAcquisitionButton" xml:space="preserve">
<value>Stop</value>
</data>
<data name="Detector_SingleFrameButton" xml:space="preserve">
<value>Single Frame</value>
</data>
<data name="Detector_SaveImageButton" xml:space="preserve">
<value>Save Image</value>
</data>
<data name="Detector_StatusIdle" xml:space="preserve">
<value>Idle</value>
</data>
<data name="Detector_ConnectButton" xml:space="preserve">
<value>Connect</value>
</data>
<data name="Detector_DisconnectButton" xml:space="preserve">
<value>Disconnect</value>
</data>
<data name="Detector_Connected" xml:space="preserve">
<value>Connected</value>
</data>
<data name="Detector_Disconnected" xml:space="preserve">
<value>Disconnected</value>
</data>
<data name="Detector_ModelLabel" xml:space="preserve">
<value>Model:</value>
</data>
<data name="Detector_TypeLabel" xml:space="preserve">
<value>Type:</value>
</data>
<data name="Detector_ResolutionLabel" xml:space="preserve">
<value>Resolution:</value>
</data>
<data name="Detector_PixelSizeLabel" xml:space="preserve">
<value>Pixel Size:</value>
</data>
<data name="Detector_StatusLabel" xml:space="preserve">
<value>Status:</value>
</data>
</root>
@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Detector_Title" xml:space="preserve">
<value>探测器控制</value>
</data>
<data name="Detector_ConnectSuccess" xml:space="preserve">
<value>探测器连接成功</value>
</data>
<data name="Detector_ConnectFailed" xml:space="preserve">
<value>探测器连接失败</value>
</data>
<data name="Detector_DisconnectSuccess" xml:space="preserve">
<value>探测器断开成功</value>
</data>
<data name="Detector_Status_Acquiring" xml:space="preserve">
<value>采集中</value>
</data>
<data name="Detector_SensitivityLabel" xml:space="preserve">
<value>灵敏度</value>
<comment>DetectorConfigView - 灵敏度(PGA)标签 | Sensitivity (PGA) label</comment>
</data>
<data name="Detector_FrameRateLabel" xml:space="preserve">
<value>帧率</value>
<comment>DetectorConfigView - 帧率标签 | Frame rate label</comment>
</data>
<data name="Detector_BinningLabel" xml:space="preserve">
<value>像素合并</value>
<comment>DetectorConfigView - 像素合并(Binning)标签 | Pixel binning label</comment>
</data>
<data name="Detector_AvgFramesLabel" xml:space="preserve">
<value>帧合并</value>
<comment>DetectorConfigView - 帧合并标签 | Average frames label</comment>
</data>
<data name="Detector_ConfigWindowTitle" xml:space="preserve">
<value>探测器配置</value>
<comment>DetectorConfigWindow - 窗口标题 | Window title</comment>
</data>
<data name="Detector_DarkCorrectionButton" xml:space="preserve">
<value>暗场校正</value>
<comment>DetectorConfigView - 暗场校正按钮 | Dark correction button</comment>
</data>
<data name="Detector_LightCorrectionButton" xml:space="preserve">
<value>亮场校正</value>
<comment>DetectorConfigView - 亮场校正按钮 | Light (gain) correction button</comment>
</data>
<data name="Detector_BadPixelCorrectionButton" xml:space="preserve">
<value>坏像素校正</value>
<comment>DetectorConfigView - 坏像素校正按钮 | Bad pixel correction button</comment>
</data>
<data name="Detector_ApplyParametersButton" xml:space="preserve">
<value>应用参数</value>
<comment>DetectorConfigView - 应用参数按钮 | Apply parameters button</comment>
</data>
<data name="Detector_ImageWindowTitle" xml:space="preserve">
<value>探测器图像</value>
<comment>DetectorImageWindow - 窗口标题 | Window title</comment>
</data>
<data name="Detector_StartAcquisitionButton" xml:space="preserve">
<value>开始采集</value>
<comment>DetectorImageWindow - 开始采集按钮 | Start acquisition button</comment>
</data>
<data name="Detector_StopAcquisitionButton" xml:space="preserve">
<value>停止采集</value>
<comment>DetectorImageWindow - 停止采集按钮 | Stop acquisition button</comment>
</data>
<data name="Detector_SingleFrameButton" xml:space="preserve">
<value>单帧采集</value>
<comment>DetectorImageWindow - 单帧采集按钮 | Single frame button</comment>
</data>
<data name="Detector_SaveImageButton" xml:space="preserve">
<value>保存图像</value>
<comment>DetectorImageWindow - 保存图像按钮 | Save image button</comment>
</data>
<data name="Detector_StatusIdle" xml:space="preserve">
<value>空闲</value>
<comment>DetectorImageWindow - 空闲状态 | Idle status</comment>
</data>
<data name="Detector_ConnectButton" xml:space="preserve">
<value>连接探测器</value>
<comment>DetectorImageWindow - 连接探测器按钮 | Connect detector button</comment>
</data>
<data name="Detector_DisconnectButton" xml:space="preserve">
<value>断开探测器</value>
<comment>DetectorImageWindow - 断开探测器按钮 | Disconnect detector button</comment>
</data>
<data name="Detector_Connected" xml:space="preserve">
<value>已连接</value>
<comment>DetectorImageWindow - 已连接状态 | Connected status</comment>
</data>
<data name="Detector_Disconnected" xml:space="preserve">
<value>未连接</value>
<comment>DetectorImageWindow - 未连接状态 | Disconnected status</comment>
</data>
<data name="Detector_ModelLabel" xml:space="preserve">
<value>型号:</value>
<comment>DetectorImageWindow - 探测器型号标签 | Detector model label</comment>
</data>
<data name="Detector_TypeLabel" xml:space="preserve">
<value>类型:</value>
<comment>DetectorImageWindow - 探测器类型标签 | Detector type label</comment>
</data>
<data name="Detector_ResolutionLabel" xml:space="preserve">
<value>分辨率:</value>
<comment>DetectorImageWindow - 分辨率标签 | Resolution label</comment>
</data>
<data name="Detector_PixelSizeLabel" xml:space="preserve">
<value>像素尺寸:</value>
<comment>DetectorImageWindow - 像素尺寸标签 | Pixel size label</comment>
</data>
<data name="Detector_StatusLabel" xml:space="preserve">
<value>状态:</value>
<comment>DetectorImageWindow - 探测器状态标签 | Detector status label</comment>
</data>
</root>
@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Detector_Title" xml:space="preserve">
<value>探测器控制</value>
</data>
<data name="Detector_ConnectSuccess" xml:space="preserve">
<value>探测器连接成功</value>
</data>
<data name="Detector_ConnectFailed" xml:space="preserve">
<value>探测器连接失败</value>
</data>
<data name="Detector_DisconnectSuccess" xml:space="preserve">
<value>探测器断开成功</value>
</data>
<data name="Detector_Status_Acquiring" xml:space="preserve">
<value>采集中</value>
</data>
<data name="Detector_SensitivityLabel" xml:space="preserve">
<value>灵敏度</value>
</data>
<data name="Detector_FrameRateLabel" xml:space="preserve">
<value>帧率</value>
</data>
<data name="Detector_BinningLabel" xml:space="preserve">
<value>像素合并</value>
</data>
<data name="Detector_AvgFramesLabel" xml:space="preserve">
<value>帧合并</value>
</data>
<data name="Detector_ConfigWindowTitle" xml:space="preserve">
<value>探测器配置</value>
</data>
<data name="Detector_DarkCorrectionButton" xml:space="preserve">
<value>暗场校正</value>
</data>
<data name="Detector_LightCorrectionButton" xml:space="preserve">
<value>亮场校正</value>
</data>
<data name="Detector_BadPixelCorrectionButton" xml:space="preserve">
<value>坏像素校正</value>
</data>
<data name="Detector_ApplyParametersButton" xml:space="preserve">
<value>应用参数</value>
</data>
<data name="Detector_ImageWindowTitle" xml:space="preserve">
<value>探测器图像</value>
</data>
<data name="Detector_StartAcquisitionButton" xml:space="preserve">
<value>开始采集</value>
</data>
<data name="Detector_StopAcquisitionButton" xml:space="preserve">
<value>停止采集</value>
</data>
<data name="Detector_SingleFrameButton" xml:space="preserve">
<value>单帧采集</value>
</data>
<data name="Detector_SaveImageButton" xml:space="preserve">
<value>保存图像</value>
</data>
<data name="Detector_StatusIdle" xml:space="preserve">
<value>空闲</value>
</data>
<data name="Detector_ConnectButton" xml:space="preserve">
<value>连接探测器</value>
</data>
<data name="Detector_DisconnectButton" xml:space="preserve">
<value>断开探测器</value>
</data>
<data name="Detector_Connected" xml:space="preserve">
<value>已连接</value>
</data>
<data name="Detector_Disconnected" xml:space="preserve">
<value>未连接</value>
</data>
<data name="Detector_ModelLabel" xml:space="preserve">
<value>型号:</value>
</data>
<data name="Detector_TypeLabel" xml:space="preserve">
<value>类型:</value>
</data>
<data name="Detector_ResolutionLabel" xml:space="preserve">
<value>分辨率:</value>
</data>
<data name="Detector_PixelSizeLabel" xml:space="preserve">
<value>像素尺寸:</value>
</data>
<data name="Detector_StatusLabel" xml:space="preserve">
<value>状态:</value>
</data>
</root>
@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Detector_Title" xml:space="preserve">
<value>探測器控制</value>
</data>
<data name="Detector_ConnectSuccess" xml:space="preserve">
<value>探測器連線成功</value>
</data>
<data name="Detector_ConnectFailed" xml:space="preserve">
<value>探測器連線失敗</value>
</data>
<data name="Detector_DisconnectSuccess" xml:space="preserve">
<value>探測器中斷連線成功</value>
</data>
<data name="Detector_Status_Acquiring" xml:space="preserve">
<value>採集中</value>
</data>
<data name="Detector_SensitivityLabel" xml:space="preserve">
<value>靈敏度</value>
</data>
<data name="Detector_FrameRateLabel" xml:space="preserve">
<value>幀率</value>
</data>
<data name="Detector_BinningLabel" xml:space="preserve">
<value>像素合併</value>
</data>
<data name="Detector_AvgFramesLabel" xml:space="preserve">
<value>幀合併</value>
</data>
<data name="Detector_ConfigWindowTitle" xml:space="preserve">
<value>探測器配置</value>
</data>
<data name="Detector_DarkCorrectionButton" xml:space="preserve">
<value>暗場校正</value>
</data>
<data name="Detector_LightCorrectionButton" xml:space="preserve">
<value>亮場校正</value>
</data>
<data name="Detector_BadPixelCorrectionButton" xml:space="preserve">
<value>壞像素校正</value>
</data>
<data name="Detector_ApplyParametersButton" xml:space="preserve">
<value>套用參數</value>
</data>
<data name="Detector_ImageWindowTitle" xml:space="preserve">
<value>探測器影像</value>
</data>
<data name="Detector_StartAcquisitionButton" xml:space="preserve">
<value>開始採集</value>
</data>
<data name="Detector_StopAcquisitionButton" xml:space="preserve">
<value>停止採集</value>
</data>
<data name="Detector_SingleFrameButton" xml:space="preserve">
<value>單幀採集</value>
</data>
<data name="Detector_SaveImageButton" xml:space="preserve">
<value>儲存影像</value>
</data>
<data name="Detector_StatusIdle" xml:space="preserve">
<value>閒置</value>
</data>
<data name="Detector_ConnectButton" xml:space="preserve">
<value>連接探測器</value>
</data>
<data name="Detector_DisconnectButton" xml:space="preserve">
<value>斷開探測器</value>
</data>
<data name="Detector_Connected" xml:space="preserve">
<value>已連接</value>
</data>
<data name="Detector_Disconnected" xml:space="preserve">
<value>未連接</value>
</data>
<data name="Detector_ModelLabel" xml:space="preserve">
<value>型號:</value>
</data>
<data name="Detector_TypeLabel" xml:space="preserve">
<value>類型:</value>
</data>
<data name="Detector_ResolutionLabel" xml:space="preserve">
<value>解析度:</value>
</data>
<data name="Detector_PixelSizeLabel" xml:space="preserve">
<value>像素尺寸:</value>
</data>
<data name="Detector_StatusLabel" xml:space="preserve">
<value>狀態:</value>
</data>
</root>
@@ -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");
}
}
}
@@ -0,0 +1,670 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using XP.Common.GeneralForm.Views;
using XP.Common.Logging.Interfaces;
using XP.Hardware.Detector.Abstractions.Events;
using XP.Hardware.Detector.Abstractions.Enums;
using XP.Hardware.Detector.Config;
using XP.Hardware.Detector.Services;
namespace XP.Hardware.Detector.ViewModels
{
/// <summary>
/// 面阵探测器配置 ViewModel | Area detector configuration ViewModel
/// 根据探测器类型动态加载参数选项,包含校正参数一致性校验和扫描期间 UI 锁定
/// </summary>
public class DetectorConfigViewModel : BindableBase
{
private readonly IDetectorService _detectorService;
private readonly IEventAggregator _eventAggregator;
private readonly ILoggerService _logger;
private DetectorConfig _config;
#region | Connection status
private bool _isConnected;
/// <summary>
/// 探测器是否已连接 | Whether detector is connected
/// </summary>
public bool IsConnected
{
get => _isConnected;
private set
{
if (SetProperty(ref _isConnected, value))
RaiseAllCommandsCanExecuteChanged();
}
}
#endregion
#region | Parameter snapshot at dark correction time
private int _darkCorrectionBinningIndex = -1;
private int _darkCorrectionPga = -1;
private decimal _darkCorrectionFrameRate = -1m;
#endregion
#region PGA| Sensitivity (PGA)
/// <summary>
/// PGA 可选项列表 | PGA selectable items
/// </summary>
public ObservableCollection<int> PgaItems { get; } = new ObservableCollection<int>();
private int _selectedPga;
/// <summary>
/// 当前选中的 PGA 值 | Currently selected PGA value
/// </summary>
public int SelectedPga
{
get => _selectedPga;
set => SetProperty(ref _selectedPga, value);
}
#endregion
#region Binning| Pixel binning
/// <summary>
/// Binning 可选项列表 | Binning selectable items
/// </summary>
public ObservableCollection<BinningOption> BinningItems { get; } = new ObservableCollection<BinningOption>();
private int _selectedBinningIndex;
/// <summary>
/// 当前选中的 Binning 索引 | Currently selected binning index
/// </summary>
public int SelectedBinningIndex
{
get => _selectedBinningIndex;
set
{
if (SetProperty(ref _selectedBinningIndex, value))
{
ClampFrameRate();
UpdateImageSpec();
}
}
}
#endregion
#region | Frame rate
private decimal _frameRate = 5m;
/// <summary>
/// 帧率 | Frame rate
/// </summary>
public decimal FrameRate
{
get => _frameRate;
set
{
if (value < FrameRateMinimum)
value = FrameRateMinimum;
var max = GetMaxFrameRate(_selectedBinningIndex);
if (value > max)
{
_logger?.Warn("帧率超出当前 Binning 模式上限 {Max},已自动修正 | Frame rate exceeds max {Max} for current binning, auto corrected", max);
value = max;
}
SetProperty(ref _frameRate, value);
}
}
/// <summary>
/// 帧率最小值 | Frame rate minimum
/// </summary>
public decimal FrameRateMinimum => 0.1m;
private decimal _frameRateMaximum = 15m;
/// <summary>
/// 帧率最大值(随 Binning 变化)| Frame rate maximum (varies with binning)
/// </summary>
public decimal FrameRateMaximum
{
get => _frameRateMaximum;
private set => SetProperty(ref _frameRateMaximum, value);
}
#endregion
#region | Average frames
private int _avgFrames = 1;
/// <summary>
/// 帧合并数 | Average frame count
/// </summary>
public int AvgFrames
{
get => _avgFrames;
set
{
if (value < 1) value = 1;
SetProperty(ref _avgFrames, value);
}
}
#endregion
#region Binning | Image spec (read-only, varies with binning)
private string _imageSpecText = "";
/// <summary>
/// 当前 Binning 模式下的图像规格文本 | Image spec text for current binning mode
/// </summary>
public string ImageSpecText
{
get => _imageSpecText;
private set => SetProperty(ref _imageSpecText, value);
}
#endregion
#region | Commands
public DelegateCommand DarkCorrectionCommand { get; }
public DelegateCommand LightCorrectionCommand { get; }
public DelegateCommand BadPixelCorrectionCommand { get; }
public DelegateCommand ApplyParametersCommand { get; }
#endregion
#region | Status
private bool _isBusy;
/// <summary>
/// 是否正在执行校正操作 | Whether a correction operation is in progress
/// </summary>
public bool IsBusy
{
get => _isBusy;
private set
{
SetProperty(ref _isBusy, value);
RaiseAllCommandsCanExecuteChanged();
}
}
private bool _darkCorrectionDone;
/// <summary>
/// 暗场校正是否已完成 | Whether dark correction is done
/// </summary>
public bool DarkCorrectionDone
{
get => _darkCorrectionDone;
private set
{
SetProperty(ref _darkCorrectionDone, value);
LightCorrectionCommand.RaiseCanExecuteChanged();
}
}
private bool _isParametersLocked;
/// <summary>
/// 参数是否被锁定(扫描采集期间禁止修改)| Whether parameters are locked (during acquisition)
/// </summary>
public bool IsParametersLocked
{
get => _isParametersLocked;
private set
{
SetProperty(ref _isParametersLocked, value);
RaisePropertyChanged(nameof(IsParametersEditable));
RaiseAllCommandsCanExecuteChanged();
}
}
/// <summary>
/// 参数是否可编辑(已连接、未锁定且不忙)| Whether parameters are editable
/// </summary>
public bool IsParametersEditable => _isConnected && !_isParametersLocked && !_isBusy;
#endregion
#region | Constructor
public DetectorConfigViewModel(
IDetectorService detectorService,
IEventAggregator eventAggregator,
ILoggerService logger)
{
_detectorService = detectorService ?? throw new ArgumentNullException(nameof(detectorService));
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
_logger = logger?.ForModule<DetectorConfigViewModel>() ?? throw new ArgumentNullException(nameof(logger));
DarkCorrectionCommand = new DelegateCommand(ExecuteDarkCorrectionAsync, CanExecuteCorrection);
LightCorrectionCommand = new DelegateCommand(ExecuteLightCorrectionAsync, CanExecuteLightCorrection);
BadPixelCorrectionCommand = new DelegateCommand(ExecuteBadPixelCorrectionAsync, CanExecuteCorrection);
ApplyParametersCommand = new DelegateCommand(ExecuteApplyParametersAsync, CanExecuteCorrection);
// 订阅探测器状态变更事件,用于扫描期间锁定参数 | Subscribe to status changed event for parameter locking
_eventAggregator.GetEvent<StatusChangedEvent>()
.Subscribe(OnDetectorStatusChanged, ThreadOption.UIThread);
// 从配置加载 UI 选项 | Load UI options from config
LoadOptionsFromConfig();
}
#endregion
#region | Public methods
/// <summary>
/// 锁定参数(外部调用,如扫描开始时)| Lock parameters (called externally, e.g. when scan starts)
/// </summary>
public void LockParameters()
{
IsParametersLocked = true;
_logger?.Info("探测器参数已锁定 | Detector parameters locked");
}
/// <summary>
/// 解锁参数(外部调用,如扫描结束时)| Unlock parameters (called externally, e.g. when scan ends)
/// </summary>
public void UnlockParameters()
{
IsParametersLocked = false;
_logger?.Info("探测器参数已解锁 | Detector parameters unlocked");
}
/// <summary>
/// 获取当前参数的图像规格(供扫描配置导出)| Get current image spec for scan config export
/// </summary>
public BinningImageSpec GetCurrentImageSpec()
{
return _config?.GetImageSpec(_selectedBinningIndex);
}
/// <summary>
/// 导出当前探测器参数为字典(供扫描配置保存)| Export current parameters as dictionary
/// </summary>
public System.Collections.Generic.Dictionary<string, string> ExportParameters()
{
var dict = new System.Collections.Generic.Dictionary<string, string>();
var spec = GetCurrentImageSpec();
var binningName = _selectedBinningIndex < BinningItems.Count
? BinningItems[_selectedBinningIndex].DisplayName : "1×1";
dict["Det_Binning"] = binningName;
dict["Det_PGA"] = _selectedPga.ToString();
dict["Det_Frame_rate"] = _frameRate.ToString(System.Globalization.CultureInfo.InvariantCulture);
dict["Det_Avg_Frames"] = _avgFrames.ToString();
if (spec != null)
{
dict["Pixel_X"] = spec.PixelX.ToString(System.Globalization.CultureInfo.InvariantCulture);
dict["Pixel_Y"] = spec.PixelY.ToString(System.Globalization.CultureInfo.InvariantCulture);
dict["Image_Size_Width"] = spec.ImageWidth.ToString();
dict["Image_Size_Height"] = spec.ImageHeight.ToString();
}
return dict;
}
#endregion
#region | Private methods
/// <summary>
/// 从探测器配置加载下拉框选项 | Load combo box options from detector config
/// </summary>
private void LoadOptionsFromConfig()
{
_config = _detectorService.GetCurrentConfig();
// 加载 Binning 选项 | Load binning options
BinningItems.Clear();
var binnings = _config?.GetSupportedBinnings();
if (binnings != null && binnings.Count > 0)
{
foreach (var b in binnings) BinningItems.Add(b);
}
else
{
BinningItems.Add(new BinningOption("1×1", 0));
BinningItems.Add(new BinningOption("2×2", 1));
}
// 加载 PGA 选项 | Load PGA options
PgaItems.Clear();
var pgas = _config?.GetSupportedPgaValues();
if (pgas != null && pgas.Count > 0)
{
foreach (var p in pgas) PgaItems.Add(p);
}
else
{
foreach (var p in new[] { 2, 3, 4, 5, 6, 7 }) PgaItems.Add(p);
}
// 尝试从持久化配置恢复参数 | Try to restore parameters from saved config
var saved = ConfigLoader.LoadSavedParameters();
if (saved.HasValue)
{
var (binIdx, pga, fr, avg) = saved.Value;
SelectedBinningIndex = binIdx < BinningItems.Count ? binIdx : 0;
SelectedPga = PgaItems.Contains(pga) ? pga : PgaItems.FirstOrDefault();
FrameRate = fr > 0 ? fr : 5m;
AvgFrames = avg > 0 ? avg : 1;
_logger?.Info("从持久化配置恢复参数,Binning={Binning}PGA={PGA},帧率={FrameRate},帧合并={AvgFrames} | Restored parameters from saved config",
binIdx, pga, fr, avg);
}
else
{
SelectedBinningIndex = 0;
SelectedPga = PgaItems.FirstOrDefault();
}
// 初始化帧率上限 | Initialize frame rate maximum
FrameRateMaximum = GetMaxFrameRate(_selectedBinningIndex);
UpdateImageSpec();
_logger?.Info("探测器配置选项已加载,类型={Type}Binning选项数={BinCount}PGA选项数={PgaCount} | Detector config options loaded",
_config?.Type.ToString() ?? "未知", BinningItems.Count, PgaItems.Count);
}
/// <summary>
/// 获取最大帧率(优先从配置获取)| Get max frame rate (prefer from config)
/// </summary>
private decimal GetMaxFrameRate(int binningIndex)
{
return _config?.GetMaxFrameRate(binningIndex) ?? 15m;
}
private void ClampFrameRate()
{
var max = GetMaxFrameRate(_selectedBinningIndex);
FrameRateMaximum = max;
if (_frameRate > max)
{
_logger?.Warn("Binning 变更,帧率已从 {Old} 调整为上限 {Max} | Binning changed, frame rate adjusted from {Old} to max {Max}", _frameRate, max);
FrameRate = max;
}
}
/// <summary>
/// 更新图像规格显示文本 | Update image spec display text
/// </summary>
private void UpdateImageSpec()
{
var spec = _config?.GetImageSpec(_selectedBinningIndex);
if (spec != null)
{
ImageSpecText = $"{spec.ImageWidth}×{spec.ImageHeight} 像素尺寸 {spec.PixelX}×{spec.PixelY} mm";
}
else
{
ImageSpecText = "";
}
}
/// <summary>
/// 探测器状态变更回调,用于扫描期间自动锁定/解锁参数 | Detector status changed callback
/// </summary>
private void OnDetectorStatusChanged(DetectorStatus status)
{
// 同步连接状态:非 Uninitialized 即视为已连接 | Sync connection status: connected if not Uninitialized
IsConnected = status != DetectorStatus.Uninitialized;
if (status == DetectorStatus.Acquiring)
{
IsParametersLocked = true;
_logger?.Debug("探测器进入采集状态,参数已自动锁定 | Detector acquiring, parameters auto-locked");
}
else if (status == DetectorStatus.Ready)
{
IsParametersLocked = false;
_logger?.Debug("探测器就绪,参数已自动解锁 | Detector ready, parameters auto-unlocked");
}
}
/// <summary>
/// 校验亮场校正前参数是否与暗场校正时一致 | Validate parameters consistency before light correction
/// </summary>
/// <returns>参数是否一致 | Whether parameters are consistent</returns>
private bool ValidateCorrectionParametersConsistency()
{
if (_darkCorrectionBinningIndex < 0)
{
_logger?.Warn("未执行暗场校正,无法进行亮场校正 | Dark correction not done, cannot perform light correction");
return false;
}
if (_selectedBinningIndex != _darkCorrectionBinningIndex ||
_selectedPga != _darkCorrectionPga ||
_frameRate != _darkCorrectionFrameRate)
{
_logger?.Warn("暗场校正与亮场校正参数不一致:暗场 Binning={DarkBin}PGA={DarkPga},帧率={DarkFr};当前 Binning={CurBin}PGA={CurPga},帧率={CurFr} | Parameter mismatch between dark and light correction",
_darkCorrectionBinningIndex, _darkCorrectionPga, _darkCorrectionFrameRate,
_selectedBinningIndex, _selectedPga, _frameRate);
return false;
}
return true;
}
/// <summary>
/// 记录暗场校正时的参数快照 | Record parameter snapshot at dark correction time
/// </summary>
private void RecordDarkCorrectionParameters()
{
_darkCorrectionBinningIndex = _selectedBinningIndex;
_darkCorrectionPga = _selectedPga;
_darkCorrectionFrameRate = _frameRate;
}
#endregion
#region | Command execution
private bool CanExecuteCorrection() => _isConnected && !_isBusy && !_isParametersLocked;
private bool CanExecuteLightCorrection() => _isConnected && !_isBusy && !_isParametersLocked && _darkCorrectionDone;
private void RaiseAllCommandsCanExecuteChanged()
{
DarkCorrectionCommand.RaiseCanExecuteChanged();
LightCorrectionCommand.RaiseCanExecuteChanged();
BadPixelCorrectionCommand.RaiseCanExecuteChanged();
ApplyParametersCommand.RaiseCanExecuteChanged();
RaisePropertyChanged(nameof(IsParametersEditable));
}
/// <summary>
/// 执行暗场校正 | Execute dark correction
/// </summary>
private async void ExecuteDarkCorrectionAsync()
{
var binningName = _selectedBinningIndex < BinningItems.Count ? BinningItems[_selectedBinningIndex].DisplayName : "?";
_logger?.Info("开始暗场校正,Binning={Binning}PGA={PGA},帧率={FrameRate} | Starting dark correction",
binningName, _selectedPga, _frameRate);
// 显示进度条窗口 | Show progress window
var progressWindow = new ProgressWindow(
title: "暗场校正 | Dark Correction",
message: "正在应用参数... | Applying parameters...",
isCancelable: false,
logger: _logger);
progressWindow.Show();
IsBusy = true;
try
{
// 1. 应用参数到硬件 | Apply parameters to hardware
progressWindow.UpdateProgress("正在应用参数... | Applying parameters...", 10);
var applyResult = await _detectorService.ApplyParametersAsync(_selectedBinningIndex, _selectedPga, _frameRate);
if (!applyResult.IsSuccess)
{
_logger?.Error(applyResult.Exception, "应用参数失败,暗场校正中止:{Message} | Apply parameters failed, dark correction aborted: {Message}", applyResult.ErrorMessage);
return;
}
// 2. 执行暗场校正 | Execute dark correction
progressWindow.UpdateProgress("正在采集暗场数据... | Acquiring dark field data...", 30);
var result = await _detectorService.DarkCorrectionAsync(_avgFrames);
if (result.IsSuccess)
{
progressWindow.UpdateProgress("暗场校正完成 | Dark correction completed", 100);
RecordDarkCorrectionParameters();
DarkCorrectionDone = true;
_logger?.Info("暗场校正完成 | Dark correction completed");
_detectorService.SaveParameters(_selectedBinningIndex, _selectedPga, _frameRate, _avgFrames);
}
else
{
_logger?.Error(result.Exception, "暗场校正失败:{Message} | Dark correction failed: {Message}", result.ErrorMessage);
}
}
catch (Exception ex)
{
_logger?.Error(ex, "暗场校正异常:{Message} | Dark correction exception: {Message}", ex.Message);
}
finally
{
IsBusy = false;
progressWindow.Close();
}
}
/// <summary>
/// 执行亮场校正(含参数一致性校验)| Execute light correction (with parameter consistency check)
/// </summary>
private async void ExecuteLightCorrectionAsync()
{
// 校验参数一致性 | Validate parameter consistency
if (!ValidateCorrectionParametersConsistency())
{
_logger?.Warn("暗场校正与亮场校正参数不一致,请重新进行暗场校正 | Parameter mismatch, please redo dark correction");
DarkCorrectionDone = false;
return;
}
var binningName = _selectedBinningIndex < BinningItems.Count ? BinningItems[_selectedBinningIndex].DisplayName : "?";
_logger?.Info("开始亮场校正,Binning={Binning}PGA={PGA},帧率={FrameRate} | Starting light correction",
binningName, _selectedPga, _frameRate);
// 显示进度条窗口 | Show progress window
var progressWindow = new ProgressWindow(
title: "亮场校正 | Light Correction",
message: "正在采集亮场数据... | Acquiring light field data...",
isCancelable: false,
logger: _logger);
progressWindow.Show();
IsBusy = true;
try
{
progressWindow.UpdateProgress("正在采集亮场数据... | Acquiring light field data...", 30);
var result = await _detectorService.GainCorrectionAsync(_avgFrames);
if (result.IsSuccess)
{
progressWindow.UpdateProgress("亮场校正完成 | Light correction completed", 100);
_logger?.Info("亮场校正完成 | Light correction completed");
}
else
{
_logger?.Error(result.Exception, "亮场校正失败:{Message} | Light correction failed: {Message}", result.ErrorMessage);
}
}
catch (Exception ex)
{
_logger?.Error(ex, "亮场校正异常:{Message} | Light correction exception: {Message}", ex.Message);
}
finally
{
IsBusy = false;
progressWindow.Close();
}
}
/// <summary>
/// 执行坏像素校正 | Execute bad pixel correction
/// </summary>
private async void ExecuteBadPixelCorrectionAsync()
{
_logger?.Info("开始坏像素校正 | Starting bad pixel correction");
// 显示进度条窗口 | Show progress window
var progressWindow = new ProgressWindow(
title: "坏像素校正 | Bad Pixel Correction",
message: "正在检测坏像素... | Detecting bad pixels...",
isCancelable: false,
logger: _logger);
progressWindow.Show();
IsBusy = true;
try
{
progressWindow.UpdateProgress("正在检测坏像素... | Detecting bad pixels...", 30);
var result = await _detectorService.BadPixelCorrectionAsync();
if (result.IsSuccess)
{
progressWindow.UpdateProgress("坏像素校正完成 | Bad pixel correction completed", 100);
_logger?.Info("坏像素校正完成 | Bad pixel correction completed");
}
else
{
_logger?.Error(result.Exception, "坏像素校正失败:{Message} | Bad pixel correction failed: {Message}", result.ErrorMessage);
}
}
catch (Exception ex)
{
_logger?.Error(ex, "坏像素校正异常:{Message} | Bad pixel correction exception: {Message}", ex.Message);
}
finally
{
IsBusy = false;
progressWindow.Close();
}
}
/// <summary>
/// 应用参数到探测器硬件 | Apply parameters to detector hardware
/// </summary>
private async void ExecuteApplyParametersAsync()
{
var binningName = _selectedBinningIndex < BinningItems.Count ? BinningItems[_selectedBinningIndex].DisplayName : "?";
_logger?.Info("应用探测器参数,Binning={Binning}PGA={PGA},帧率={FrameRate} | Applying detector parameters",
binningName, _selectedPga, _frameRate);
IsBusy = true;
try
{
var result = await _detectorService.ApplyParametersAsync(_selectedBinningIndex, _selectedPga, _frameRate);
if (result.IsSuccess)
{
_logger?.Info("参数应用成功 | Parameters applied successfully");
// 持久化参数 | Persist parameters
_detectorService.SaveParameters(_selectedBinningIndex, _selectedPga, _frameRate, _avgFrames);
}
else
{
_logger?.Error(result.Exception, "参数应用失败:{Message} | Parameters apply failed: {Message}", result.ErrorMessage);
}
}
catch (Exception ex)
{
_logger?.Error(ex, "参数应用异常:{Message} | Parameters apply exception: {Message}", ex.Message);
}
finally
{
IsBusy = false;
}
}
#endregion
}
}
@@ -0,0 +1,657 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using XP.Common.Logging.Interfaces;
using XP.Hardware.Detector.Abstractions;
using XP.Hardware.Detector.Abstractions.Enums;
using XP.Hardware.Detector.Abstractions.Events;
using XP.Hardware.Detector.Services;
namespace XP.Hardware.Detector.ViewModels
{
/// <summary>
/// 探测器操作 ViewModel | Detector operation ViewModel
/// 负责连接/断开、连续采集、单帧采集、图像显示及状态跟踪
/// </summary>
public class DetectorImageWindowViewModel : BindableBase
{
private readonly IDetectorService _detectorService;
private readonly IEventAggregator _eventAggregator;
private readonly IImageService _imageSaveService;
private readonly ILoggerService _logger;
/// <summary>
/// 帧处理节流标志,防止后台线程堆积 | Frame processing throttle flag to prevent thread pile-up
/// 0 = 空闲,1 = 正在处理
/// </summary>
private int _isProcessingFrame;
#region | Connection status
private bool _isConnected;
/// <summary>
/// 探测器是否已连接 | Whether detector is connected
/// </summary>
public bool IsConnected
{
get => _isConnected;
private set
{
if (SetProperty(ref _isConnected, value))
{
RaiseAllCommandsCanExecuteChanged();
RaisePropertyChanged(nameof(ConnectionStatusText));
RaisePropertyChanged(nameof(ConnectionStatusColor));
}
}
}
/// <summary>
/// 连接状态文本 | Connection status text
/// </summary>
public string ConnectionStatusText => _isConnected ? "已连接 | Connected" : "未连接 | Disconnected";
/// <summary>
/// 连接状态指示颜色 | Connection status indicator color
/// </summary>
public Brush ConnectionStatusColor => _isConnected
? new SolidColorBrush(Color.FromRgb(0x4C, 0xAF, 0x50)) // 绿色 | Green
: new SolidColorBrush(Color.FromRgb(0xF4, 0x43, 0x36)); // 红色 | Red
#endregion
#region | Detector status
private DetectorStatus _detectorStatus = DetectorStatus.Uninitialized;
/// <summary>
/// 当前探测器状态 | Current detector status
/// </summary>
public DetectorStatus DetectorStatus
{
get => _detectorStatus;
private set
{
if (SetProperty(ref _detectorStatus, value))
{
RaisePropertyChanged(nameof(DetectorStatusText));
RaiseAllCommandsCanExecuteChanged();
}
}
}
/// <summary>
/// 探测器状态文本 | Detector status text
/// </summary>
public string DetectorStatusText => _detectorStatus switch
{
Abstractions.Enums.DetectorStatus.Uninitialized => "未初始化 | Uninitialized",
Abstractions.Enums.DetectorStatus.Initializing => "初始化中 | Initializing",
Abstractions.Enums.DetectorStatus.Ready => "就绪 | Ready",
Abstractions.Enums.DetectorStatus.Acquiring => "采集中 | Acquiring",
Abstractions.Enums.DetectorStatus.Correcting => "校正中 | Correcting",
Abstractions.Enums.DetectorStatus.Error => "错误 | Error",
_ => "未知 | Unknown"
};
#endregion
#region | Detector info
private string _detectorModel = "--";
/// <summary>
/// 探测器型号 | Detector model
/// </summary>
public string DetectorModel
{
get => _detectorModel;
private set => SetProperty(ref _detectorModel, value);
}
private string _detectorResolution = "--";
/// <summary>
/// 探测器分辨率 | Detector resolution
/// </summary>
public string DetectorResolution
{
get => _detectorResolution;
private set => SetProperty(ref _detectorResolution, value);
}
private string _detectorPixelSize = "--";
/// <summary>
/// 探测器像素尺寸 | Detector pixel size
/// </summary>
public string DetectorPixelSize
{
get => _detectorPixelSize;
private set => SetProperty(ref _detectorPixelSize, value);
}
private string _detectorType = "--";
/// <summary>
/// 探测器类型 | Detector type
/// </summary>
public string DetectorTypeText
{
get => _detectorType;
private set => SetProperty(ref _detectorType, value);
}
#endregion
#region | Image properties
private BitmapSource _imageSource;
/// <summary>
/// 当前显示的图像 | Currently displayed image
/// </summary>
public BitmapSource ImageSource
{
get => _imageSource;
private set => SetProperty(ref _imageSource, value);
}
private int _frameNumber;
/// <summary>
/// 当前帧号 | Current frame number
/// </summary>
public int FrameNumber
{
get => _frameNumber;
private set => SetProperty(ref _frameNumber, value);
}
private string _imageInfo = "等待采集... | Waiting for acquisition...";
/// <summary>
/// 图像信息文本 | Image info text
/// </summary>
public string ImageInfo
{
get => _imageInfo;
private set => SetProperty(ref _imageInfo, value);
}
private bool _isAcquiring;
/// <summary>
/// 是否正在连续采集 | Whether continuous acquisition is in progress
/// </summary>
public bool IsAcquiring
{
get => _isAcquiring;
private set
{
if (SetProperty(ref _isAcquiring, value))
RaiseAllCommandsCanExecuteChanged();
}
}
#endregion
#region | Operation busy state
private bool _isBusy;
/// <summary>
/// 是否正在执行连接/断开操作 | Whether connect/disconnect is in progress
/// </summary>
public bool IsBusy
{
get => _isBusy;
private set
{
if (SetProperty(ref _isBusy, value))
RaiseAllCommandsCanExecuteChanged();
}
}
private string _statusMessage = "";
/// <summary>
/// 操作状态消息 | Operation status message
/// </summary>
public string StatusMessage
{
get => _statusMessage;
private set => SetProperty(ref _statusMessage, value);
}
#endregion
#region | Commands
/// <summary>
/// 连接探测器命令 | Connect detector command
/// </summary>
public DelegateCommand ConnectCommand { get; }
/// <summary>
/// 断开探测器命令 | Disconnect detector command
/// </summary>
public DelegateCommand DisconnectCommand { get; }
/// <summary>
/// 启动连续采集命令 | Start continuous acquisition command
/// </summary>
public DelegateCommand StartAcquisitionCommand { get; }
/// <summary>
/// 停止采集命令 | Stop acquisition command
/// </summary>
public DelegateCommand StopAcquisitionCommand { get; }
/// <summary>
/// 单帧采集命令 | Single frame acquisition command
/// </summary>
public DelegateCommand SingleFrameCommand { get; }
/// <summary>
/// 保存图像命令 | Save image command
/// </summary>
public DelegateCommand SaveImageCommand { get; }
#endregion
#region | Constructor
public DetectorImageWindowViewModel(
IDetectorService detectorService,
IEventAggregator eventAggregator,
IImageService imageSaveService,
ILoggerService logger)
{
_detectorService = detectorService ?? throw new ArgumentNullException(nameof(detectorService));
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
_imageSaveService = imageSaveService ?? throw new ArgumentNullException(nameof(imageSaveService));
_logger = logger?.ForModule<DetectorImageWindowViewModel>() ?? throw new ArgumentNullException(nameof(logger));
ConnectCommand = new DelegateCommand(ExecuteConnectAsync, CanExecuteConnect);
DisconnectCommand = new DelegateCommand(ExecuteDisconnectAsync, CanExecuteDisconnect);
StartAcquisitionCommand = new DelegateCommand(ExecuteStartAcquisitionAsync, CanExecuteStartAcquisition);
StopAcquisitionCommand = new DelegateCommand(ExecuteStopAcquisitionAsync, CanExecuteStopAcquisition);
SingleFrameCommand = new DelegateCommand(ExecuteSingleFrameAsync, CanExecuteSingleFrame);
SaveImageCommand = new DelegateCommand(ExecuteSaveImageAsync, CanExecuteSaveImage);
// 订阅图像采集事件(后台线程)| Subscribe to image captured event (background thread)
// 图像转换在后台线程执行,避免阻塞 UI | Image conversion runs on background thread to avoid blocking UI
_eventAggregator.GetEvent<ImageCapturedEvent>()
.Subscribe(OnImageCaptured, ThreadOption.BackgroundThread);
// 订阅探测器状态变更事件 | Subscribe to detector status changed event
_eventAggregator.GetEvent<StatusChangedEvent>()
.Subscribe(OnDetectorStatusChanged, ThreadOption.UIThread);
// 同步初始状态 | Sync initial state
SyncStatusFromService();
}
#endregion
#region CanExecute
private bool CanExecuteConnect() => !_isBusy && !_isConnected;
private bool CanExecuteDisconnect() => !_isBusy && _isConnected && !_isAcquiring;
private bool CanExecuteStartAcquisition() => !_isBusy && _isConnected &&
_detectorStatus == Abstractions.Enums.DetectorStatus.Ready &&
!_isAcquiring;
private bool CanExecuteStopAcquisition() => _isConnected && _isAcquiring;
private bool CanExecuteSingleFrame() => !_isBusy && _isConnected &&
_detectorStatus == Abstractions.Enums.DetectorStatus.Ready &&
!_isAcquiring;
private bool CanExecuteSaveImage() => _isConnected && _imageSaveService.LatestFrame != null;
private void RaiseAllCommandsCanExecuteChanged()
{
ConnectCommand.RaiseCanExecuteChanged();
DisconnectCommand.RaiseCanExecuteChanged();
StartAcquisitionCommand.RaiseCanExecuteChanged();
StopAcquisitionCommand.RaiseCanExecuteChanged();
SingleFrameCommand.RaiseCanExecuteChanged();
SaveImageCommand.RaiseCanExecuteChanged();
}
#endregion
#region | Command execution
/// <summary>
/// 连接探测器 | Connect detector
/// </summary>
private async void ExecuteConnectAsync()
{
_logger?.Info("开始连接探测器 | Connecting detector");
IsBusy = true;
StatusMessage = "正在连接探测器... | Connecting...";
try
{
var result = await _detectorService.InitializeAsync();
if (result.IsSuccess)
{
IsConnected = true;
StatusMessage = "探测器连接成功,正在应用参数... | Detector connected, applying parameters...";
_logger?.Info("探测器连接成功 | Detector connected successfully");
RefreshDetectorInfo();
// 连接成功后自动应用持久化的探测器参数 | Auto apply saved detector parameters after connection
var saved = Config.ConfigLoader.LoadSavedParameters();
if (saved.HasValue)
{
var (binIdx, pga, fr, _) = saved.Value;
var applyResult = await _detectorService.ApplyParametersAsync(binIdx, pga, fr);
if (applyResult.IsSuccess)
{
StatusMessage = "探测器就绪 | Detector ready";
_logger?.Info("探测器参数已自动应用,Binning={Binning}PGA={PGA},帧率={FrameRate} | Detector parameters auto-applied", binIdx, pga, fr);
}
else
{
StatusMessage = $"参数应用失败:{applyResult.ErrorMessage}";
_logger?.Warn("自动应用参数失败:{Message} | Auto apply parameters failed: {Message}", applyResult.ErrorMessage);
}
}
else
{
StatusMessage = "探测器就绪(使用默认参数)| Detector ready (default parameters)";
_logger?.Info("未找到持久化参数,使用默认配置 | No saved parameters found, using default config");
}
}
else
{
StatusMessage = $"连接失败:{result.ErrorMessage}";
_logger?.Error(result.Exception, "探测器连接失败:{Message} | Detector connection failed: {Message}", result.ErrorMessage);
}
}
catch (Exception ex)
{
StatusMessage = $"连接异常:{ex.Message}";
_logger?.Error(ex, "探测器连接异常:{Message} | Detector connection exception: {Message}", ex.Message);
}
finally
{
IsBusy = false;
}
}
/// <summary>
/// 断开探测器 | Disconnect detector
/// </summary>
private async void ExecuteDisconnectAsync()
{
_logger?.Info("开始断开探测器 | Disconnecting detector");
IsBusy = true;
StatusMessage = "正在断开探测器... | Disconnecting...";
try
{
var result = await _detectorService.DisconnectAsync();
IsConnected = false;
IsAcquiring = false;
ClearDetectorInfo();
ImageInfo = "等待采集... | Waiting for acquisition...";
if (result.IsSuccess)
{
StatusMessage = "探测器已断开 | Detector disconnected";
_logger?.Info("探测器已断开 | Detector disconnected");
}
else
{
StatusMessage = $"断开时发生错误:{result.ErrorMessage}";
_logger?.Warn("断开探测器时发生错误:{Message} | Error during disconnect: {Message}", result.ErrorMessage);
}
}
catch (Exception ex)
{
IsConnected = false;
IsAcquiring = false;
ClearDetectorInfo();
StatusMessage = $"断开异常:{ex.Message}";
_logger?.Error(ex, "断开探测器异常:{Message} | Disconnect exception: {Message}", ex.Message);
}
finally
{
IsBusy = false;
}
}
/// <summary>
/// 启动连续采集 | Start continuous acquisition
/// </summary>
private async void ExecuteStartAcquisitionAsync()
{
_logger?.Info("启动连续采集 | Starting continuous acquisition");
IsAcquiring = true;
StatusMessage = "采集中... | Acquiring...";
try
{
var result = await _detectorService.StartAcquisitionAsync();
if (!result.IsSuccess)
{
IsAcquiring = false;
StatusMessage = $"启动采集失败:{result.ErrorMessage}";
_logger?.Error(result.Exception, "启动采集失败:{Message} | Start acquisition failed: {Message}", result.ErrorMessage);
}
}
catch (Exception ex)
{
IsAcquiring = false;
StatusMessage = $"启动采集异常:{ex.Message}";
_logger?.Error(ex, "启动采集异常:{Message} | Start acquisition exception: {Message}", ex.Message);
}
}
/// <summary>
/// 停止采集 | Stop acquisition
/// </summary>
private async void ExecuteStopAcquisitionAsync()
{
_logger?.Info("停止采集 | Stopping acquisition");
StatusMessage = "正在停止采集... | Stopping...";
try
{
var result = await _detectorService.StopAcquisitionAsync();
IsAcquiring = false;
StatusMessage = result.IsSuccess ? "采集已停止 | Acquisition stopped" : $"停止采集失败:{result.ErrorMessage}";
if (!result.IsSuccess)
_logger?.Error(result.Exception, "停止采集失败:{Message} | Stop acquisition failed: {Message}", result.ErrorMessage);
}
catch (Exception ex)
{
IsAcquiring = false;
StatusMessage = $"停止采集异常:{ex.Message}";
_logger?.Error(ex, "停止采集异常:{Message} | Stop acquisition exception: {Message}", ex.Message);
}
}
/// <summary>
/// 单帧采集 | Single frame acquisition
/// </summary>
private async void ExecuteSingleFrameAsync()
{
_logger?.Info("单帧采集 | Single frame acquisition");
IsBusy = true;
StatusMessage = "单帧采集中... | Acquiring single frame...";
try
{
var result = await _detectorService.AcquireSingleFrameAsync();
StatusMessage = result.IsSuccess ? "单帧采集完成 | Single frame acquired" : $"单帧采集失败:{result.ErrorMessage}";
if (!result.IsSuccess)
_logger?.Error(result.Exception, "单帧采集失败:{Message} | Single frame failed: {Message}", result.ErrorMessage);
}
catch (Exception ex)
{
StatusMessage = $"单帧采集异常:{ex.Message}";
_logger?.Error(ex, "单帧采集异常:{Message} | Single frame exception: {Message}", ex.Message);
}
finally
{
IsBusy = false;
}
}
/// <summary>
/// 保存当前图像为 16 位 TIFF | Save current image as 16-bit TIFF
/// </summary>
private async void ExecuteSaveImageAsync()
{
var frame = _imageSaveService.LatestFrame;
if (frame?.ImageData == null)
{
StatusMessage = "无可保存的图像 | No image to save";
return;
}
_logger?.Info("保存图像,帧号:{FrameNumber} | Saving image, frame: {FrameNumber}", frame.FrameNumber);
StatusMessage = "正在保存图像... | Saving image...";
try
{
var saveDir = _imageSaveService.GetDefaultSaveDirectory();
var result = await _imageSaveService.SaveLatestFrameAsync(saveDir, "IMG");
StatusMessage = result.IsSuccess
? $"图像已保存 | Image saved"
: $"保存失败:{result.ErrorMessage}";
}
catch (Exception ex)
{
StatusMessage = $"保存异常:{ex.Message}";
_logger?.Error(ex, "保存图像异常:{Message} | Save image exception: {Message}", ex.Message);
}
}
#endregion
#region | Event callbacks
/// <summary>
/// 图像采集回调,将 16 位原始数据归一化为 8 位灰度图 | Image captured callback
/// </summary>
private void OnImageCaptured(ImageCapturedEventArgs args)
{
if (args?.ImageData == null || args.Width == 0 || args.Height == 0) return;
// 帧节流:上一帧尚未被 UI 线程消费完毕时,跳过当前帧
// Frame throttle: skip current frame if previous frame hasn't been consumed by UI thread yet
if (Interlocked.CompareExchange(ref _isProcessingFrame, 1, 0) != 0) return;
try
{
// 在后台线程执行图像转换 | Perform image conversion on background thread
var bitmap = ConvertToBitmapSource(args.ImageData, (int)args.Width, (int)args.Height);
bitmap.Freeze(); // Freeze 后可跨线程访问 | Freeze allows cross-thread access
var frameNumber = args.FrameNumber;
var imageInfo = $"{args.Width}×{args.Height} 帧#{args.FrameNumber} {args.CaptureTime:HH:mm:ss.fff}";
// 将属性赋值调度到 UI 线程,完成后才释放节流标志
// Dispatch property assignment to UI thread; release throttle flag only after UI completes
Application.Current?.Dispatcher?.BeginInvoke(new Action(() =>
{
try
{
ImageSource = bitmap;
FrameNumber = frameNumber;
ImageInfo = imageInfo;
}
finally
{
// UI 线程完成渲染后才允许下一帧进入 | Allow next frame only after UI thread finishes rendering
Interlocked.Exchange(ref _isProcessingFrame, 0);
}
}));
}
catch (Exception ex)
{
// 异常时释放标志,避免永久锁死 | Release flag on exception to avoid permanent lock
Interlocked.Exchange(ref _isProcessingFrame, 0);
_logger?.Error(ex, "图像转换失败:{Message} | Image conversion failed: {Message}", ex.Message);
}
}
/// <summary>
/// 探测器状态变更回调 | Detector status changed callback
/// </summary>
private void OnDetectorStatusChanged(DetectorStatus status)
{
DetectorStatus = status;
// 根据状态同步 IsAcquiring | Sync IsAcquiring from status
if (status == Abstractions.Enums.DetectorStatus.Acquiring)
IsAcquiring = true;
else if (status == Abstractions.Enums.DetectorStatus.Ready ||
status == Abstractions.Enums.DetectorStatus.Error)
IsAcquiring = false;
// 探测器错误时同步连接状态 | Sync connection state on error
if (status == Abstractions.Enums.DetectorStatus.Uninitialized)
{
IsConnected = false;
ClearDetectorInfo();
}
}
#endregion
#region | Private helpers
/// <summary>
/// 从服务层同步当前状态(ViewModel 初始化时调用)| Sync state from service (called on init)
/// </summary>
private void SyncStatusFromService()
{
IsConnected = _detectorService.IsConnected;
DetectorStatus = _detectorService.Status;
if (IsConnected)
RefreshDetectorInfo();
}
/// <summary>
/// 刷新探测器信息显示 | Refresh detector info display
/// </summary>
private void RefreshDetectorInfo()
{
try
{
var info = _detectorService.GetInfo();
if (info == null) return;
DetectorModel = string.IsNullOrEmpty(info.Model) ? "--" : info.Model;
DetectorResolution = (info.MaxWidth > 0 && info.MaxHeight > 0)
? $"{info.MaxWidth}×{info.MaxHeight}"
: "--";
DetectorPixelSize = info.PixelSize > 0
? $"{info.PixelSize:F3} mm"
: "--";
DetectorTypeText = _detectorService.Type?.ToString() ?? "--";
}
catch (Exception ex)
{
_logger?.Warn("获取探测器信息失败:{Message} | Failed to get detector info: {Message}", ex.Message);
}
}
/// <summary>
/// 清空探测器信息 | Clear detector info
/// </summary>
private void ClearDetectorInfo()
{
DetectorModel = "--";
DetectorResolution = "--";
DetectorPixelSize = "--";
DetectorTypeText = "--";
}
/// <summary>
/// 将 16 位灰度数据转换为 8 位 BitmapSource(委托给 XP.Common 通用转换器)
/// Convert 16-bit grayscale data to 8-bit BitmapSource (delegates to XP.Common common converter)
/// </summary>
private static BitmapSource ConvertToBitmapSource(ushort[] data, int width, int height)
{
return XP.Common.Converters.ImageConverter.ConvertGray16ToBitmapSource(data, width, height);
}
#endregion
}
}
@@ -0,0 +1,140 @@
<UserControl x:Class="XP.Hardware.Detector.Views.DetectorConfigView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:loc="clr-namespace:XP.Common.Localization.Extensions;assembly=XP.Common"
mc:Ignorable="d"
d:DesignWidth="420" d:DesignHeight="210"
prism:ViewModelLocator.AutoWireViewModel="True"
Background="White">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 第1行:灵敏度 + 帧率 | Row 1: Sensitivity (PGA) + Frame rate -->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{loc:Localization Detector_SensitivityLabel}"
FontSize="12" Foreground="#616161" VerticalAlignment="Center" Width="60"/>
<telerik:RadComboBox Grid.Column="1"
ItemsSource="{Binding PgaItems}"
SelectedItem="{Binding SelectedPga, Mode=TwoWay}"
Height="26" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal"/>
<TextBlock Grid.Column="3" Text="{loc:Localization Detector_FrameRateLabel}"
FontSize="12" Foreground="#616161" VerticalAlignment="Center" Width="60"/>
<telerik:RadNumericUpDown Grid.Column="4"
Value="{Binding FrameRate, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
Minimum="{Binding FrameRateMinimum}"
Maximum="{Binding FrameRateMaximum}"
NumberDecimalDigits="2"
SmallChange="0.1"
Height="26" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal"/>
</Grid>
<!-- 第2行:像素合并 + 帧合并 | Row 2: Binning + Average frames -->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{loc:Localization Detector_BinningLabel}"
FontSize="12" Foreground="#616161" VerticalAlignment="Center" Width="60"/>
<telerik:RadComboBox Grid.Column="1"
ItemsSource="{Binding BinningItems}"
DisplayMemberPath="DisplayName"
SelectedIndex="{Binding SelectedBinningIndex, Mode=TwoWay}"
Height="26" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal"/>
<TextBlock Grid.Column="3" Text="{loc:Localization Detector_AvgFramesLabel}"
FontSize="12" Foreground="#616161" VerticalAlignment="Center" Width="60"/>
<telerik:RadNumericUpDown Grid.Column="4"
Value="{Binding AvgFrames, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
Minimum="1"
Maximum="100"
NumberDecimalDigits="0"
SmallChange="1"
Height="26" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal"/>
</Grid>
<!-- 第3行:应用参数按钮(右对齐占一半)| Row 3: Apply parameters button (right-aligned, half width) -->
<Grid Grid.Row="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<telerik:RadButton Grid.Column="1"
Content="{loc:Localization Detector_ApplyParametersButton}"
Command="{Binding ApplyParametersCommand}"
Height="32" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal" Margin="4 0 0 0"/>
</Grid>
<!-- 第4行:分隔线 | Row 4: Separator -->
<Separator Grid.Row="6" Margin="0 0 0 0"/>
<!-- 第5行:暗场校正 + 亮场校正按钮 | Row 5: Dark + Light correction buttons -->
<Grid Grid.Row="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 暗场校正 | Dark correction -->
<telerik:RadButton Grid.Column="0"
Content="{loc:Localization Detector_DarkCorrectionButton}"
Command="{Binding DarkCorrectionCommand}"
Height="32" Margin="0 0 4 0" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal">
<telerik:RadButton.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="White"/>
<GradientStop Color="#FF62B8E0" Offset="1"/>
</LinearGradientBrush>
</telerik:RadButton.Background>
</telerik:RadButton>
<!-- 亮场校正 | Light correction -->
<telerik:RadButton Grid.Column="1"
Content="{loc:Localization Detector_LightCorrectionButton}"
Command="{Binding LightCorrectionCommand}"
Height="32" Margin="4 0 0 0" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal">
<telerik:RadButton.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="White"/>
<GradientStop Color="#FF77E062" Offset="1"/>
</LinearGradientBrush>
</telerik:RadButton.Background>
</telerik:RadButton>
</Grid>
</Grid>
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace XP.Hardware.Detector.Views
{
/// <summary>
/// 面阵探测器配置视图 | Area detector configuration view
/// </summary>
public partial class DetectorConfigView : UserControl
{
public DetectorConfigView()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,14 @@
<Window x:Class="XP.Hardware.Detector.Views.DetectorConfigWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:XP.Common.Localization.Extensions;assembly=XP.Common"
xmlns:detectorViews="clr-namespace:XP.Hardware.Detector.Views"
Title="{loc:Localization Detector_ConfigWindowTitle}"
Height="230" Width="400"
MinHeight="230" MinWidth="360"
WindowStartupLocation="CenterOwner"
ResizeMode="CanResize">
<Grid>
<detectorViews:DetectorConfigView />
</Grid>
</Window>
@@ -0,0 +1,24 @@
using System.Windows;
namespace XP.Hardware.Detector.Views
{
/// <summary>
/// 探测器配置窗口 | Detector config window
/// </summary>
public partial class DetectorConfigWindow : Window
{
/// <summary>
/// 构造函数 | Constructor
/// </summary>
public DetectorConfigWindow()
{
InitializeComponent();
// 继承主窗口图标 | Inherit main window icon
if (Application.Current?.MainWindow != null)
{
Icon = Application.Current.MainWindow.Icon;
}
}
}
}
@@ -0,0 +1,268 @@
<Window x:Class="XP.Hardware.Detector.Views.DetectorImageWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:loc="clr-namespace:XP.Common.Localization.Extensions;assembly=XP.Common"
mc:Ignorable="d"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="{loc:Localization Detector_ImageWindowTitle}"
Height="750" Width="850"
MinHeight="550" MinWidth="650"
WindowStartupLocation="CenterOwner"
Background="White">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<!-- 左侧:图像区域 | Left: Image area -->
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="8"/>
<!-- 右侧:控制面板 | Right: Control panel -->
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<!-- ========== 左侧主区域 ========== -->
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="8"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 图像显示区域 | Image display area -->
<Border Grid.Row="0" BorderBrush="#E0E0E0" BorderThickness="1" Background="#1A1A1A">
<Viewbox Stretch="Uniform">
<Image Source="{Binding ImageSource}"
RenderOptions.BitmapScalingMode="NearestNeighbor"
RenderOptions.EdgeMode="Aliased"
Stretch="Uniform"/>
</Viewbox>
</Border>
<!-- 底部状态栏 | Bottom status bar -->
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="12"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- 采集状态指示灯 | Acquisition status indicator -->
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Ellipse Width="10" Height="10" Margin="0 0 6 0" VerticalAlignment="Center">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="#9E9E9E"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsAcquiring}" Value="True">
<Setter Property="Fill" Value="#4CAF50"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<TextBlock FontSize="12" Foreground="#616161" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{loc:Localization Detector_StatusIdle}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsAcquiring}" Value="True">
<Setter Property="Text" Value="{loc:Localization Detector_Status_Acquiring}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
<!-- 操作状态消息 | Operation status message -->
<TextBlock Grid.Column="2" Text="{Binding StatusMessage}"
FontSize="11" Foreground="#9E9E9E" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
<!-- 图像信息 | Image info -->
<TextBlock Grid.Column="3" Text="{Binding ImageInfo}"
FontSize="12" Foreground="#616161" VerticalAlignment="Center"/>
</Grid>
</Grid>
<!-- ========== 右侧控制面板 ========== -->
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<!-- 连接控制 | Connection control -->
<RowDefinition Height="Auto"/>
<RowDefinition Height="12"/>
<!-- 探测器信息 | Detector info -->
<RowDefinition Height="Auto"/>
<RowDefinition Height="12"/>
<!-- 采集控制 | Acquisition control -->
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 连接控制区 | Connection control section -->
<Border Grid.Row="0" BorderBrush="#E0E0E0" BorderThickness="1" CornerRadius="4" Padding="10">
<StackPanel>
<!-- 连接状态指示 | Connection status indicator -->
<StackPanel Orientation="Horizontal" Margin="0 0 0 8">
<Ellipse Width="10" Height="10" Margin="0 0 6 0" VerticalAlignment="Center"
Fill="{Binding ConnectionStatusColor}"/>
<TextBlock FontSize="12" Foreground="#424242" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{loc:Localization Detector_Disconnected}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Text" Value="{loc:Localization Detector_Connected}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
<!-- 连接/断开按钮 | Connect/Disconnect buttons -->
<telerik:RadButton Content="{loc:Localization Detector_ConnectButton}"
Command="{Binding ConnectCommand}"
Height="32" Margin="0 0 0 4" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal">
<telerik:RadButton.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="White"/>
<GradientStop Color="#FF77E062" Offset="1"/>
</LinearGradientBrush>
</telerik:RadButton.Background>
</telerik:RadButton>
<telerik:RadButton Content="{loc:Localization Detector_DisconnectButton}"
Command="{Binding DisconnectCommand}"
Height="32" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal">
<telerik:RadButton.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="White"/>
<GradientStop Color="#FFFFA1A1" Offset="1"/>
</LinearGradientBrush>
</telerik:RadButton.Background>
</telerik:RadButton>
</StackPanel>
</Border>
<!-- 探测器信息区 | Detector info section -->
<Border Grid.Row="2" BorderBrush="#E0E0E0" BorderThickness="1" CornerRadius="4" Padding="10">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="4"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="4"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="4"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="4"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 类型 | Type -->
<TextBlock Grid.Row="0" Grid.Column="0"
Text="{loc:Localization Detector_TypeLabel}"
FontSize="12" Foreground="#757575" VerticalAlignment="Center"/>
<TextBlock Grid.Row="0" Grid.Column="1"
Text="{Binding DetectorTypeText}"
FontSize="12" Foreground="#212121" VerticalAlignment="Center"
HorizontalAlignment="Right"/>
<!-- 型号 | Model -->
<TextBlock Grid.Row="2" Grid.Column="0"
Text="{loc:Localization Detector_ModelLabel}"
FontSize="12" Foreground="#757575" VerticalAlignment="Center"/>
<TextBlock Grid.Row="2" Grid.Column="1"
Text="{Binding DetectorModel}"
FontSize="12" Foreground="#212121" VerticalAlignment="Center"
HorizontalAlignment="Right" TextTrimming="CharacterEllipsis"/>
<!-- 分辨率 | Resolution -->
<TextBlock Grid.Row="4" Grid.Column="0"
Text="{loc:Localization Detector_ResolutionLabel}"
FontSize="12" Foreground="#757575" VerticalAlignment="Center"/>
<TextBlock Grid.Row="4" Grid.Column="1"
Text="{Binding DetectorResolution}"
FontSize="12" Foreground="#212121" VerticalAlignment="Center"
HorizontalAlignment="Right"/>
<!-- 像素尺寸 | Pixel size -->
<TextBlock Grid.Row="6" Grid.Column="0"
Text="{loc:Localization Detector_PixelSizeLabel}"
FontSize="12" Foreground="#757575" VerticalAlignment="Center"/>
<TextBlock Grid.Row="6" Grid.Column="1"
Text="{Binding DetectorPixelSize}"
FontSize="12" Foreground="#212121" VerticalAlignment="Center"
HorizontalAlignment="Right"/>
<!-- 状态 | Status -->
<TextBlock Grid.Row="8" Grid.Column="0"
Text="{loc:Localization Detector_StatusLabel}"
FontSize="12" Foreground="#757575" VerticalAlignment="Center"/>
<TextBlock Grid.Row="8" Grid.Column="1"
Text="{Binding DetectorStatusText}"
FontSize="12" Foreground="#212121" VerticalAlignment="Center"
HorizontalAlignment="Right"/>
</Grid>
</Border>
<!-- 采集控制区 | Acquisition control section -->
<Border Grid.Row="4" BorderBrush="#E0E0E0" BorderThickness="1" CornerRadius="4" Padding="10">
<StackPanel>
<telerik:RadButton Content="{loc:Localization Detector_StartAcquisitionButton}"
Command="{Binding StartAcquisitionCommand}"
Height="32" Margin="0 0 0 4" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal">
<telerik:RadButton.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="White"/>
<GradientStop Color="#FF77E062" Offset="1"/>
</LinearGradientBrush>
</telerik:RadButton.Background>
</telerik:RadButton>
<telerik:RadButton Content="{loc:Localization Detector_StopAcquisitionButton}"
Command="{Binding StopAcquisitionCommand}"
Height="32" Margin="0 0 0 4" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal">
<telerik:RadButton.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="White"/>
<GradientStop Color="#FFFFA1A1" Offset="1"/>
</LinearGradientBrush>
</telerik:RadButton.Background>
</telerik:RadButton>
<telerik:RadButton Content="{loc:Localization Detector_SingleFrameButton}"
Command="{Binding SingleFrameCommand}"
Height="32" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal">
<telerik:RadButton.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="White"/>
<GradientStop Color="#FF62B8E0" Offset="1"/>
</LinearGradientBrush>
</telerik:RadButton.Background>
</telerik:RadButton>
<Separator Margin="0,8,0,4"/>
<telerik:RadButton Content="{loc:Localization Detector_SaveImageButton}"
Command="{Binding SaveImageCommand}"
Height="32" Margin="0 4 0 0" HorizontalAlignment="Stretch"
telerik:StyleManager.Theme="Crystal"/>
</StackPanel>
</Border>
</Grid>
</Grid>
</Window>
@@ -0,0 +1,24 @@
using System.Windows;
namespace XP.Hardware.Detector.Views
{
/// <summary>
/// 探测器图像显示窗口 | Detector image display window
/// </summary>
public partial class DetectorImageWindow : Window
{
/// <summary>
/// 构造函数 | Constructor
/// </summary>
public DetectorImageWindow()
{
InitializeComponent();
// 继承主窗口图标 | Inherit main window icon
if (Application.Current?.MainWindow != null)
{
Icon = Application.Current.MainWindow.Icon;
}
}
}
}
@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows7.0</TargetFramework>
<UseWPF>true</UseWPF>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Prism.Wpf" Version="9.0.537" />
<PackageReference Include="Telerik.UI.for.Wpf.NetCore.Xaml" Version="2024.1.408" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\XP.Common\XP.Common.csproj" />
</ItemGroup>
<ItemGroup>
<!-- 确保 xisl.dll 复制到输出目录 | Ensure xisl.dll is copied to output directory -->
<None Include="bin\Debug\net8.0-windows7.0\xisl.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>