增加扫描模式配置参数功能
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace XP.Scan.Attributes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 标记 INI Key 名称(可选,默认使用属性名)| Marks the INI key name (optional, defaults to property name)
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public class IniKeyAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string KeyName { get; }
|
||||||
|
|
||||||
|
public IniKeyAttribute(string keyName)
|
||||||
|
{
|
||||||
|
KeyName = keyName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace XP.Scan.Attributes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 标记 INI Section 名称 | Marks the INI section name
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class IniSectionAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string SectionName { get; }
|
||||||
|
|
||||||
|
public IniSectionAttribute(string sectionName)
|
||||||
|
{
|
||||||
|
SectionName = sectionName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,366 @@
|
|||||||
|
# XP.Scan 扫描配置文件设计文档
|
||||||
|
|
||||||
|
## 1. 需求概述
|
||||||
|
|
||||||
|
在平面CT扫描采集过程中,需要将当前扫描的所有参数信息(项目信息、射线源参数、探测器参数、运动控制参数、扫描配置、校正配置)打包为一个配置对象,最终序列化为 INI 格式文件传递给重构电脑。
|
||||||
|
|
||||||
|
INI 文件结构与立式CT采集配置兼容,包含 6 个 Section:
|
||||||
|
- `[Project_Information]` — 项目基本信息
|
||||||
|
- `[XRay]` — 射线源参数
|
||||||
|
- `[Detector]` — 探测器参数
|
||||||
|
- `[Move_Control]` — 运动控制轴位置
|
||||||
|
- `[Scan_Config]` — 扫描配置
|
||||||
|
- `[Correction_Config]` — 校正参数
|
||||||
|
|
||||||
|
## 2. 设计方案对比
|
||||||
|
|
||||||
|
| 方案 | 优点 | 缺点 | 推荐 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| A. 单个大类 `ScanConfig` | 简单直接 | 字段太多,职责不清 | ✗ |
|
||||||
|
| B. 分组模型 + 序列化服务 | 职责清晰,可测试,可扩展 | 类稍多 | ✓ |
|
||||||
|
| C. Dictionary + 手写序列化 | 灵活 | 无类型安全,易出错 | ✗ |
|
||||||
|
|
||||||
|
**选择方案 B**:用分组模型类映射 INI 的每个 Section,加一个序列化服务负责读写 INI 文件。
|
||||||
|
|
||||||
|
## 3. 数据模型设计
|
||||||
|
|
||||||
|
### 3.1 整体结构
|
||||||
|
|
||||||
|
```
|
||||||
|
ScanConfigData (顶层聚合)
|
||||||
|
├── ProjectInfo → [Project_Information]
|
||||||
|
├── XRayConfig → [XRay]
|
||||||
|
├── DetectorConfig → [Detector]
|
||||||
|
├── MoveControlConfig → [Move_Control]
|
||||||
|
├── ScanSettings → [Scan_Config]
|
||||||
|
└── CorrectionConfig → [Correction_Config]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 类定义
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
namespace XP.Scan.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 扫描配置数据(顶层聚合,对应完整 INI 文件)
|
||||||
|
/// </summary>
|
||||||
|
public class ScanConfigData
|
||||||
|
{
|
||||||
|
public ProjectInfo ProjectInfo { get; set; } = new();
|
||||||
|
public XRayConfig XRay { get; set; } = new();
|
||||||
|
public DetectorConfig Detector { get; set; } = new();
|
||||||
|
public MoveControlConfig MoveControl { get; set; } = new();
|
||||||
|
public ScanSettings ScanSettings { get; set; } = new();
|
||||||
|
public CorrectionConfig Correction { get; set; } = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### [Project_Information]
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class ProjectInfo
|
||||||
|
{
|
||||||
|
/// <summary>图像保存路径</summary>
|
||||||
|
public string FileSave { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>滤波片1</summary>
|
||||||
|
public string Filter1 { get; set; } = "None";
|
||||||
|
|
||||||
|
/// <summary>滤波片2</summary>
|
||||||
|
public string Filter2 { get; set; } = "None";
|
||||||
|
|
||||||
|
/// <summary>项目名称</summary>
|
||||||
|
public string Project { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>样品编号</summary>
|
||||||
|
public string SampleNo { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>扫描模式名称</summary>
|
||||||
|
public string ScanMode { get; set; } = "QuickScan";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### [XRay]
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class XRayConfig
|
||||||
|
{
|
||||||
|
/// <summary>管电流 (μA)</summary>
|
||||||
|
public int Current_uA { get; set; }
|
||||||
|
|
||||||
|
/// <summary>焦点尺寸</summary>
|
||||||
|
public string Focus { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>管电压 (kV)</summary>
|
||||||
|
public int Voltage_kV { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### [Detector]
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class DetectorConfig
|
||||||
|
{
|
||||||
|
/// <summary>帧合并数</summary>
|
||||||
|
public int Det_Avg_Frames { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>Binning 模式</summary>
|
||||||
|
public string Det_Binning { get; set; } = "1*1";
|
||||||
|
|
||||||
|
/// <summary>帧率</summary>
|
||||||
|
public int Det_Frame_rate { get; set; } = 2;
|
||||||
|
|
||||||
|
/// <summary>增益 (PGA)</summary>
|
||||||
|
public int Det_PGA { get; set; } = 6;
|
||||||
|
|
||||||
|
// ROI 参数
|
||||||
|
public int Image_ROI_Height { get; set; }
|
||||||
|
public int Image_ROI_Width { get; set; }
|
||||||
|
public int Image_ROI_xStart { get; set; }
|
||||||
|
public int Image_ROI_xEnd { get; set; }
|
||||||
|
public int Image_ROI_yStart { get; set; }
|
||||||
|
public int Image_ROI_yEnd { get; set; }
|
||||||
|
public int Image_ROI_zStart { get; set; }
|
||||||
|
public int Image_ROI_zEnd { get; set; }
|
||||||
|
|
||||||
|
// 图像尺寸
|
||||||
|
public int Image_Size_Height { get; set; }
|
||||||
|
public int Image_Size_Width { get; set; }
|
||||||
|
|
||||||
|
// 物理尺寸 (mm)
|
||||||
|
public double Physical_Size_X { get; set; }
|
||||||
|
public double Physical_Size_Y { get; set; }
|
||||||
|
|
||||||
|
// 像素尺寸 (mm)
|
||||||
|
public double Pixel_X { get; set; }
|
||||||
|
public double Pixel_Y { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### [Move_Control]
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class MoveControlConfig
|
||||||
|
{
|
||||||
|
/// <summary>探测器 X 位置 (mm)</summary>
|
||||||
|
public double DetX { get; set; }
|
||||||
|
|
||||||
|
/// <summary>探测器 Y 位置 (mm)</summary>
|
||||||
|
public double DetY { get; set; }
|
||||||
|
|
||||||
|
/// <summary>探测器 Z 位置 (mm)</summary>
|
||||||
|
public double DetZ { get; set; }
|
||||||
|
|
||||||
|
/// <summary>旋转台角度 (°)</summary>
|
||||||
|
public double Rotation { get; set; }
|
||||||
|
|
||||||
|
/// <summary>样品台 X 位置 (mm) — 即 SOD</summary>
|
||||||
|
public double X { get; set; }
|
||||||
|
|
||||||
|
/// <summary>射线源 Z 位置 (mm)</summary>
|
||||||
|
public double XRAYZ { get; set; }
|
||||||
|
|
||||||
|
/// <summary>样品台 Y 位置 (mm)</summary>
|
||||||
|
public double Y { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### [Scan_Config]
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class ScanSettings
|
||||||
|
{
|
||||||
|
/// <summary>采集张数</summary>
|
||||||
|
public int AcquiresNums { get; set; }
|
||||||
|
|
||||||
|
/// <summary>旋转角度 (°)</summary>
|
||||||
|
public double RotateDegree { get; set; }
|
||||||
|
|
||||||
|
/// <summary>扫描模式描述</summary>
|
||||||
|
public string ScanMode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>SDD — 射线源到探测器距离 (mm)</summary>
|
||||||
|
public double SDD { get; set; }
|
||||||
|
|
||||||
|
/// <summary>SOD — 射线源到样品距离 (mm)</summary>
|
||||||
|
public double SOD { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### [Correction_Config]
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class CorrectionConfig
|
||||||
|
{
|
||||||
|
/// <summary>探测器水平偏移 (mm)</summary>
|
||||||
|
public double Detector_Horizontal_Offset { get; set; }
|
||||||
|
|
||||||
|
/// <summary>探测器旋转偏移 (°)</summary>
|
||||||
|
public double Detector_Rotation_Offset { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. INI 序列化服务设计
|
||||||
|
|
||||||
|
### 4.1 接口
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
namespace XP.Scan.Services
|
||||||
|
{
|
||||||
|
public interface IScanConfigSerializer
|
||||||
|
{
|
||||||
|
/// <summary>将配置数据序列化为 INI 格式字符串</summary>
|
||||||
|
string Serialize(ScanConfigData config);
|
||||||
|
|
||||||
|
/// <summary>将配置数据写入 INI 文件</summary>
|
||||||
|
void SaveToFile(ScanConfigData config, string filePath);
|
||||||
|
|
||||||
|
/// <summary>从 INI 文件读取配置数据</summary>
|
||||||
|
ScanConfigData LoadFromFile(string filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 实现方案
|
||||||
|
|
||||||
|
不引入第三方 INI 库,手写轻量级序列化,原因:
|
||||||
|
- INI 结构简单固定(6 个 Section,字段已知)
|
||||||
|
- 避免额外 NuGet 依赖
|
||||||
|
- 完全可控,格式与立式CT兼容
|
||||||
|
|
||||||
|
核心思路:用 `[IniSection("Section_Name")]` 和 `[IniKey("Key_Name")]` 特性标注模型属性,序列化时通过反射自动生成 INI 内容。
|
||||||
|
|
||||||
|
### 4.3 特性定义
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
/// <summary>标记 INI Section 名称</summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class IniSectionAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string SectionName { get; }
|
||||||
|
public IniSectionAttribute(string sectionName) => SectionName = sectionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>标记 INI Key 名称(可选,默认用属性名)</summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public class IniKeyAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string KeyName { get; }
|
||||||
|
public IniKeyAttribute(string keyName) => KeyName = keyName;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 模型标注示例
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
[IniSection("Project_Information")]
|
||||||
|
public class ProjectInfo
|
||||||
|
{
|
||||||
|
[IniKey("fileSave")]
|
||||||
|
public string FileSave { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[IniKey("filter1")]
|
||||||
|
public string Filter1 { get; set; } = "None";
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
[IniSection("XRay")]
|
||||||
|
public class XRayConfig
|
||||||
|
{
|
||||||
|
[IniKey("Current_uA")]
|
||||||
|
public int Current_uA { get; set; }
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.5 序列化输出示例
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Project_Information]
|
||||||
|
fileSave=D:\HexagonCTData\Test_2026-04-21\Image
|
||||||
|
filter1=Cu 0.2mm
|
||||||
|
filter2=None
|
||||||
|
Project=Test
|
||||||
|
SampleNo=
|
||||||
|
ScanMode=QuickScan
|
||||||
|
|
||||||
|
[XRay]
|
||||||
|
Current_uA=1000
|
||||||
|
Focus=450um
|
||||||
|
Voltage_kV=450
|
||||||
|
|
||||||
|
[Detector]
|
||||||
|
Det_Avg_Frames=1
|
||||||
|
Det_Binning=1*1
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 文件结构规划
|
||||||
|
|
||||||
|
```
|
||||||
|
XP.Scan/
|
||||||
|
├── Models/
|
||||||
|
│ ├── ScanConfigData.cs # 顶层聚合类
|
||||||
|
│ ├── ProjectInfo.cs # [Project_Information]
|
||||||
|
│ ├── XRayConfig.cs # [XRay]
|
||||||
|
│ ├── DetectorConfig.cs # [Detector]
|
||||||
|
│ ├── MoveControlConfig.cs # [Move_Control]
|
||||||
|
│ ├── ScanSettings.cs # [Scan_Config]
|
||||||
|
│ └── CorrectionConfig.cs # [Correction_Config]
|
||||||
|
│
|
||||||
|
├── Attributes/
|
||||||
|
│ ├── IniSectionAttribute.cs # Section 特性
|
||||||
|
│ └── IniKeyAttribute.cs # Key 特性
|
||||||
|
│
|
||||||
|
├── Services/
|
||||||
|
│ ├── IScanConfigSerializer.cs # 序列化接口
|
||||||
|
│ └── ScanConfigSerializer.cs # 序列化实现(反射 + 手写 INI)
|
||||||
|
│
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 使用流程
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 扫描开始前,从各硬件服务收集参数 → 填充 ScanConfigData
|
||||||
|
|
||||||
|
var config = new ScanConfigData();
|
||||||
|
config.XRay.Voltage_kV = raySourceService.CurrentVoltage;
|
||||||
|
config.XRay.Current_uA = raySourceService.CurrentCurrent;
|
||||||
|
config.Detector.Det_Avg_Frames = detectorService.AvgFrames;
|
||||||
|
config.MoveControl.X = motionService.GetPosition(AxisId.StageX);
|
||||||
|
config.ScanSettings.AcquiresNums = acquisitionCount;
|
||||||
|
// ...
|
||||||
|
|
||||||
|
2. 序列化为 INI 文件
|
||||||
|
|
||||||
|
var serializer = new ScanConfigSerializer();
|
||||||
|
serializer.SaveToFile(config, @"D:\HexagonCTData\Test\ScanConfig.ini");
|
||||||
|
|
||||||
|
3. 传递给重构电脑(文件拷贝或网络传输)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 设计决策
|
||||||
|
|
||||||
|
| 决策 | 选择 | 理由 |
|
||||||
|
|------|------|------|
|
||||||
|
| 数据模型 | 分组类(每个 Section 一个类) | 职责清晰,属性有类型安全 |
|
||||||
|
| 序列化方式 | 自定义特性 + 反射 | 轻量,无第三方依赖,格式完全可控 |
|
||||||
|
| INI Key 映射 | `[IniKey]` 特性 | 属性名可以用 C# 命名规范,INI Key 保持与立式CT兼容 |
|
||||||
|
| 数值格式 | `InvariantCulture` | 避免不同系统区域设置导致小数点格式不一致 |
|
||||||
|
| 文件编码 | UTF-8 无 BOM | 兼容性最好 |
|
||||||
|
|
||||||
|
## 8. 扩展性
|
||||||
|
|
||||||
|
- 新增 Section:创建新模型类 + 标注 `[IniSection]` + 在 `ScanConfigData` 中添加属性
|
||||||
|
- 新增字段:在对应模型类中添加属性 + 标注 `[IniKey]`
|
||||||
|
- 反序列化:`LoadFromFile` 支持从 INI 文件回读配置(用于加载历史扫描参数)
|
||||||
|
- 验证:可在模型类中添加 `Validate()` 方法,检查参数范围合法性
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**版本:** 1.0
|
||||||
|
**最后更新:** 2026-04-21
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using XP.Scan.Attributes;
|
||||||
|
|
||||||
|
namespace XP.Scan.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 校正配置 | Correction configuration
|
||||||
|
/// 对应 INI [Correction_Config] Section
|
||||||
|
/// </summary>
|
||||||
|
[IniSection("Correction_Config")]
|
||||||
|
public class CorrectionConfig
|
||||||
|
{
|
||||||
|
/// <summary>探测器水平偏移 (mm) | Detector horizontal offset (mm)</summary>
|
||||||
|
[IniKey("Detector_Horizontal_Offset")]
|
||||||
|
public double DetectorHorizontalOffset { get; set; }
|
||||||
|
|
||||||
|
/// <summary>探测器旋转偏移 (°) | Detector rotation offset (°)</summary>
|
||||||
|
[IniKey("Detector_Rotation_Offset")]
|
||||||
|
public double DetectorRotationOffset { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using XP.Scan.Attributes;
|
||||||
|
|
||||||
|
namespace XP.Scan.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 探测器配置 | Detector configuration
|
||||||
|
/// 对应 INI [Detector] Section
|
||||||
|
/// </summary>
|
||||||
|
[IniSection("Detector")]
|
||||||
|
public class DetectorConfig
|
||||||
|
{
|
||||||
|
/// <summary>帧合并数 | Average frames</summary>
|
||||||
|
[IniKey("Det_Avg_Frames")]
|
||||||
|
public int DetAvgFrames { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>Binning 模式 | Binning mode</summary>
|
||||||
|
[IniKey("Det_Binning")]
|
||||||
|
public string DetBinning { get; set; } = "1*1";
|
||||||
|
|
||||||
|
/// <summary>帧率 | Frame rate</summary>
|
||||||
|
[IniKey("Det_Frame_rate")]
|
||||||
|
public int DetFrameRate { get; set; } = 2;
|
||||||
|
|
||||||
|
/// <summary>增益 (PGA) | Gain (PGA)</summary>
|
||||||
|
[IniKey("Det_PGA")]
|
||||||
|
public int DetPGA { get; set; } = 6;
|
||||||
|
|
||||||
|
/// <summary>ROI 高度 | ROI height</summary>
|
||||||
|
[IniKey("Image_ROI_Height")]
|
||||||
|
public int ImageROIHeight { get; set; }
|
||||||
|
|
||||||
|
/// <summary>ROI 宽度 | ROI width</summary>
|
||||||
|
[IniKey("Image_ROI_Width")]
|
||||||
|
public int ImageROIWidth { get; set; }
|
||||||
|
|
||||||
|
/// <summary>ROI X 起始 | ROI X start</summary>
|
||||||
|
[IniKey("Image_ROI_xStart")]
|
||||||
|
public int ImageROIxStart { get; set; }
|
||||||
|
|
||||||
|
/// <summary>ROI X 结束 | ROI X end</summary>
|
||||||
|
[IniKey("Image_ROI_xEnd")]
|
||||||
|
public int ImageROIxEnd { get; set; }
|
||||||
|
|
||||||
|
/// <summary>ROI Y 起始 | ROI Y start</summary>
|
||||||
|
[IniKey("Image_ROI_yStart")]
|
||||||
|
public int ImageROIyStart { get; set; }
|
||||||
|
|
||||||
|
/// <summary>ROI Y 结束 | ROI Y end</summary>
|
||||||
|
[IniKey("Image_ROI_yEnd")]
|
||||||
|
public int ImageROIyEnd { get; set; }
|
||||||
|
|
||||||
|
/// <summary>ROI Z 起始 | ROI Z start</summary>
|
||||||
|
[IniKey("Image_ROI_zStart")]
|
||||||
|
public int ImageROIzStart { get; set; }
|
||||||
|
|
||||||
|
/// <summary>ROI Z 结束 | ROI Z end</summary>
|
||||||
|
[IniKey("Image_ROI_zEnd")]
|
||||||
|
public int ImageROIzEnd { get; set; }
|
||||||
|
|
||||||
|
/// <summary>图像高度 | Image height</summary>
|
||||||
|
[IniKey("Image_Size_Height")]
|
||||||
|
public int ImageSizeHeight { get; set; }
|
||||||
|
|
||||||
|
/// <summary>图像宽度 | Image width</summary>
|
||||||
|
[IniKey("Image_Size_Width")]
|
||||||
|
public int ImageSizeWidth { get; set; }
|
||||||
|
|
||||||
|
/// <summary>物理尺寸 X (mm) | Physical size X (mm)</summary>
|
||||||
|
[IniKey("Physical_Size_X")]
|
||||||
|
public double PhysicalSizeX { get; set; }
|
||||||
|
|
||||||
|
/// <summary>物理尺寸 Y (mm) | Physical size Y (mm)</summary>
|
||||||
|
[IniKey("Physical_Size_Y")]
|
||||||
|
public double PhysicalSizeY { get; set; }
|
||||||
|
|
||||||
|
/// <summary>像素尺寸 X (mm) | Pixel size X (mm)</summary>
|
||||||
|
[IniKey("Pixel_X")]
|
||||||
|
public double PixelX { get; set; }
|
||||||
|
|
||||||
|
/// <summary>像素尺寸 Y (mm) | Pixel size Y (mm)</summary>
|
||||||
|
[IniKey("Pixel_Y")]
|
||||||
|
public double PixelY { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using XP.Scan.Attributes;
|
||||||
|
|
||||||
|
namespace XP.Scan.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 运动控制配置 | Motion control configuration
|
||||||
|
/// 对应 INI [Move_Control] Section
|
||||||
|
/// </summary>
|
||||||
|
[IniSection("Move_Control")]
|
||||||
|
public class MoveControlConfig
|
||||||
|
{
|
||||||
|
/// <summary>探测器 X 位置 (mm) | Detector X position (mm)</summary>
|
||||||
|
[IniKey("DetX")]
|
||||||
|
public double DetX { get; set; }
|
||||||
|
|
||||||
|
/// <summary>探测器 Y 位置 (mm) | Detector Y position (mm)</summary>
|
||||||
|
[IniKey("DetY")]
|
||||||
|
public double DetY { get; set; }
|
||||||
|
|
||||||
|
/// <summary>探测器 Z 位置 (mm) | Detector Z position (mm)</summary>
|
||||||
|
[IniKey("DetZ")]
|
||||||
|
public double DetZ { get; set; }
|
||||||
|
|
||||||
|
/// <summary>旋转台角度 (°) | Rotation angle (°)</summary>
|
||||||
|
[IniKey("Rotation")]
|
||||||
|
public double Rotation { get; set; }
|
||||||
|
|
||||||
|
/// <summary>样品台 X 位置 (mm),即 SOD | Stage X position (mm), i.e. SOD</summary>
|
||||||
|
[IniKey("X")]
|
||||||
|
public double X { get; set; }
|
||||||
|
|
||||||
|
/// <summary>射线源 Z 位置 (mm) | X-Ray source Z position (mm)</summary>
|
||||||
|
[IniKey("XRAYZ")]
|
||||||
|
public double XRAYZ { get; set; }
|
||||||
|
|
||||||
|
/// <summary>样品台 Y 位置 (mm) | Stage Y position (mm)</summary>
|
||||||
|
[IniKey("Y")]
|
||||||
|
public double Y { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using XP.Scan.Attributes;
|
||||||
|
|
||||||
|
namespace XP.Scan.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 项目信息 | Project information
|
||||||
|
/// 对应 INI [Project_Information] Section
|
||||||
|
/// </summary>
|
||||||
|
[IniSection("Project_Information")]
|
||||||
|
public class ProjectInfo
|
||||||
|
{
|
||||||
|
/// <summary>图像保存路径 | Image save path</summary>
|
||||||
|
[IniKey("fileSave")]
|
||||||
|
public string FileSave { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>滤波片1 | Filter 1</summary>
|
||||||
|
[IniKey("filter1")]
|
||||||
|
public string Filter1 { get; set; } = "None";
|
||||||
|
|
||||||
|
/// <summary>滤波片2 | Filter 2</summary>
|
||||||
|
[IniKey("filter2")]
|
||||||
|
public string Filter2 { get; set; } = "None";
|
||||||
|
|
||||||
|
/// <summary>项目名称 | Project name</summary>
|
||||||
|
[IniKey("Project")]
|
||||||
|
public string Project { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>样品编号 | Sample number</summary>
|
||||||
|
[IniKey("SampleNo")]
|
||||||
|
public string SampleNo { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>扫描模式名称 | Scan mode name</summary>
|
||||||
|
[IniKey("ScanMode")]
|
||||||
|
public string ScanMode { get; set; } = "QuickScan";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
namespace XP.Scan.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 扫描配置数据(顶层聚合,对应完整 INI 文件)
|
||||||
|
/// Scan configuration data (top-level aggregate, corresponds to complete INI file)
|
||||||
|
/// </summary>
|
||||||
|
public class ScanConfigData
|
||||||
|
{
|
||||||
|
/// <summary>项目信息 → [Project_Information]</summary>
|
||||||
|
public ProjectInfo ProjectInfo { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>射线源配置 → [XRay]</summary>
|
||||||
|
public XRayConfig XRay { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>探测器配置 → [Detector]</summary>
|
||||||
|
public DetectorConfig Detector { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>运动控制配置 → [Move_Control]</summary>
|
||||||
|
public MoveControlConfig MoveControl { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>扫描配置 → [Scan_Config]</summary>
|
||||||
|
public ScanSettings ScanSettings { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>校正配置 → [Correction_Config]</summary>
|
||||||
|
public CorrectionConfig Correction { get; set; } = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using XP.Scan.Attributes;
|
||||||
|
|
||||||
|
namespace XP.Scan.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 扫描配置 | Scan configuration
|
||||||
|
/// 对应 INI [Scan_Config] Section
|
||||||
|
/// </summary>
|
||||||
|
[IniSection("Scan_Config")]
|
||||||
|
public class ScanSettings
|
||||||
|
{
|
||||||
|
/// <summary>采集张数 | Number of acquisitions</summary>
|
||||||
|
[IniKey("AcquiresNums")]
|
||||||
|
public int AcquiresNums { get; set; }
|
||||||
|
|
||||||
|
/// <summary>旋转角度 (°) | Rotation degree (°)</summary>
|
||||||
|
[IniKey("RotateDegree")]
|
||||||
|
public double RotateDegree { get; set; }
|
||||||
|
|
||||||
|
/// <summary>扫描模式描述 | Scan mode description</summary>
|
||||||
|
[IniKey("ScanMode")]
|
||||||
|
public string ScanMode { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>SDD — 射线源到探测器距离 (mm) | Source to detector distance (mm)</summary>
|
||||||
|
[IniKey("SDD")]
|
||||||
|
public double SDD { get; set; }
|
||||||
|
|
||||||
|
/// <summary>SOD — 射线源到样品距离 (mm) | Source to object distance (mm)</summary>
|
||||||
|
[IniKey("SOD")]
|
||||||
|
public double SOD { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using XP.Scan.Attributes;
|
||||||
|
|
||||||
|
namespace XP.Scan.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 射线源配置 | X-Ray source configuration
|
||||||
|
/// 对应 INI [XRay] Section
|
||||||
|
/// </summary>
|
||||||
|
[IniSection("XRay")]
|
||||||
|
public class XRayConfig
|
||||||
|
{
|
||||||
|
/// <summary>管电流 (μA) | Tube current (μA)</summary>
|
||||||
|
[IniKey("Current_uA")]
|
||||||
|
public int CurrentUA { get; set; }
|
||||||
|
|
||||||
|
/// <summary>焦点尺寸 | Focus size</summary>
|
||||||
|
[IniKey("Focus")]
|
||||||
|
public string Focus { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>管电压 (kV) | Tube voltage (kV)</summary>
|
||||||
|
[IniKey("Voltage_kV")]
|
||||||
|
public int VoltageKV { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using XP.Scan.Models;
|
||||||
|
|
||||||
|
namespace XP.Scan.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 扫描配置 INI 序列化接口 | Scan config INI serialization interface
|
||||||
|
/// </summary>
|
||||||
|
public interface IScanConfigSerializer
|
||||||
|
{
|
||||||
|
/// <summary>将配置数据序列化为 INI 格式字符串 | Serialize config to INI string</summary>
|
||||||
|
string Serialize(ScanConfigData config);
|
||||||
|
|
||||||
|
/// <summary>将配置数据写入 INI 文件 | Save config to INI file</summary>
|
||||||
|
void SaveToFile(ScanConfigData config, string filePath);
|
||||||
|
|
||||||
|
/// <summary>从 INI 文件读取配置数据 | Load config from INI file</summary>
|
||||||
|
ScanConfigData LoadFromFile(string filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using XP.Scan.Attributes;
|
||||||
|
using XP.Scan.Models;
|
||||||
|
|
||||||
|
namespace XP.Scan.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 扫描配置 INI 序列化实现 | Scan config INI serialization implementation
|
||||||
|
/// 通过反射 + 自定义特性自动生成/解析 INI 内容
|
||||||
|
/// </summary>
|
||||||
|
public class ScanConfigSerializer : IScanConfigSerializer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 将配置数据序列化为 INI 格式字符串
|
||||||
|
/// </summary>
|
||||||
|
public string Serialize(ScanConfigData config)
|
||||||
|
{
|
||||||
|
if (config == null) throw new ArgumentNullException(nameof(config));
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var sections = GetSectionObjects(config);
|
||||||
|
|
||||||
|
foreach (var (sectionName, sectionObj) in sections)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"[{sectionName}]");
|
||||||
|
SerializeSection(sb, sectionObj);
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString().TrimEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将配置数据写入 INI 文件
|
||||||
|
/// </summary>
|
||||||
|
public void SaveToFile(ScanConfigData config, string filePath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException(nameof(filePath));
|
||||||
|
|
||||||
|
var directory = Path.GetDirectoryName(filePath);
|
||||||
|
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = Serialize(config);
|
||||||
|
File.WriteAllText(filePath, content, new UTF8Encoding(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 INI 文件读取配置数据
|
||||||
|
/// </summary>
|
||||||
|
public ScanConfigData LoadFromFile(string filePath)
|
||||||
|
{
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
throw new FileNotFoundException($"INI file not found: {filePath}", filePath);
|
||||||
|
|
||||||
|
var lines = File.ReadAllLines(filePath, Encoding.UTF8);
|
||||||
|
var iniData = ParseIniLines(lines);
|
||||||
|
|
||||||
|
var config = new ScanConfigData();
|
||||||
|
DeserializeSection(iniData, config.ProjectInfo);
|
||||||
|
DeserializeSection(iniData, config.XRay);
|
||||||
|
DeserializeSection(iniData, config.Detector);
|
||||||
|
DeserializeSection(iniData, config.MoveControl);
|
||||||
|
DeserializeSection(iniData, config.ScanSettings);
|
||||||
|
DeserializeSection(iniData, config.Correction);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 序列化辅助方法
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 ScanConfigData 中所有标注了 [IniSection] 的子对象
|
||||||
|
/// </summary>
|
||||||
|
private List<(string SectionName, object SectionObj)> GetSectionObjects(ScanConfigData config)
|
||||||
|
{
|
||||||
|
var result = new List<(string, object)>();
|
||||||
|
|
||||||
|
foreach (var prop in typeof(ScanConfigData).GetProperties())
|
||||||
|
{
|
||||||
|
var obj = prop.GetValue(config);
|
||||||
|
if (obj == null) continue;
|
||||||
|
|
||||||
|
var sectionAttr = obj.GetType().GetCustomAttribute<IniSectionAttribute>();
|
||||||
|
if (sectionAttr != null)
|
||||||
|
{
|
||||||
|
result.Add((sectionAttr.SectionName, obj));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 序列化单个 Section 的所有属性为 key=value 行
|
||||||
|
/// </summary>
|
||||||
|
private void SerializeSection(StringBuilder sb, object sectionObj)
|
||||||
|
{
|
||||||
|
foreach (var prop in sectionObj.GetType().GetProperties())
|
||||||
|
{
|
||||||
|
var keyAttr = prop.GetCustomAttribute<IniKeyAttribute>();
|
||||||
|
var keyName = keyAttr?.KeyName ?? prop.Name;
|
||||||
|
var value = prop.GetValue(sectionObj);
|
||||||
|
var valueStr = FormatValue(value);
|
||||||
|
|
||||||
|
sb.AppendLine($"{keyName}={valueStr}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 格式化属性值为 INI 字符串(使用 InvariantCulture)
|
||||||
|
/// </summary>
|
||||||
|
private string FormatValue(object? value)
|
||||||
|
{
|
||||||
|
if (value == null) return string.Empty;
|
||||||
|
|
||||||
|
return value switch
|
||||||
|
{
|
||||||
|
double d => d.ToString(CultureInfo.InvariantCulture),
|
||||||
|
float f => f.ToString(CultureInfo.InvariantCulture),
|
||||||
|
_ => value.ToString() ?? string.Empty
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 反序列化辅助方法
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析 INI 文件行为 Section → Key/Value 字典
|
||||||
|
/// </summary>
|
||||||
|
private Dictionary<string, Dictionary<string, string>> ParseIniLines(string[] lines)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
string currentSection = string.Empty;
|
||||||
|
|
||||||
|
foreach (var rawLine in lines)
|
||||||
|
{
|
||||||
|
var line = rawLine.Trim();
|
||||||
|
|
||||||
|
// 跳过空行和注释
|
||||||
|
if (string.IsNullOrEmpty(line) || line.StartsWith(";") || line.StartsWith("#"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Section 头
|
||||||
|
if (line.StartsWith("[") && line.EndsWith("]"))
|
||||||
|
{
|
||||||
|
currentSection = line.Substring(1, line.Length - 2).Trim();
|
||||||
|
if (!result.ContainsKey(currentSection))
|
||||||
|
{
|
||||||
|
result[currentSection] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key=Value
|
||||||
|
var eqIndex = line.IndexOf('=');
|
||||||
|
if (eqIndex > 0 && !string.IsNullOrEmpty(currentSection))
|
||||||
|
{
|
||||||
|
var key = line.Substring(0, eqIndex).Trim();
|
||||||
|
var val = line.Substring(eqIndex + 1).Trim();
|
||||||
|
result[currentSection][key] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 INI 数据反序列化到模型对象
|
||||||
|
/// </summary>
|
||||||
|
private void DeserializeSection(Dictionary<string, Dictionary<string, string>> iniData, object sectionObj)
|
||||||
|
{
|
||||||
|
var sectionAttr = sectionObj.GetType().GetCustomAttribute<IniSectionAttribute>();
|
||||||
|
if (sectionAttr == null) return;
|
||||||
|
|
||||||
|
if (!iniData.TryGetValue(sectionAttr.SectionName, out var sectionData))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var prop in sectionObj.GetType().GetProperties())
|
||||||
|
{
|
||||||
|
var keyAttr = prop.GetCustomAttribute<IniKeyAttribute>();
|
||||||
|
var keyName = keyAttr?.KeyName ?? prop.Name;
|
||||||
|
|
||||||
|
if (!sectionData.TryGetValue(keyName, out var valueStr))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var convertedValue = ConvertValue(valueStr, prop.PropertyType);
|
||||||
|
if (convertedValue != null)
|
||||||
|
{
|
||||||
|
prop.SetValue(sectionObj, convertedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 转换失败时保留默认值
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将字符串值转换为目标类型
|
||||||
|
/// </summary>
|
||||||
|
private object? ConvertValue(string value, Type targetType)
|
||||||
|
{
|
||||||
|
if (targetType == typeof(string))
|
||||||
|
return value;
|
||||||
|
|
||||||
|
if (targetType == typeof(int))
|
||||||
|
return int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var i) ? i : null;
|
||||||
|
|
||||||
|
if (targetType == typeof(double))
|
||||||
|
return double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var d) ? d : null;
|
||||||
|
|
||||||
|
if (targetType == typeof(float))
|
||||||
|
return float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var f) ? f : null;
|
||||||
|
|
||||||
|
if (targetType == typeof(bool))
|
||||||
|
return bool.TryParse(value, out var b) ? b : null;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user