生成测试图像代码提交

Test_IRC_Net10
hang 2026-08-07 14:56:35 +08:00
parent 581ee62a5b
commit 540de91485
2 changed files with 497 additions and 0 deletions

View File

@ -16274,6 +16274,66 @@
访视数据模型 访视数据模型
</summary> </summary>
</member> </member>
<member name="P:IRaCIS.Core.Application.Service.TestService.GenerateIamgeInfo.PatientId">
<summary>
患者ID (0010,0020)
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.TestService.GenerateIamgeInfo.PatientName">
<summary>
患者姓名 (0010,0010)
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.TestService.GenerateIamgeInfo.StudyInstanceUID">
<summary>
研究实例UID (0020,000D)
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.TestService.GenerateIamgeInfo.SeriesInstanceUID">
<summary>
序列实例UID (0020,000E)
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.TestService.GenerateIamgeInfo.SOPInstanceUID">
<summary>
SOP实例UID (0008,0018)
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.TestService.GenerateIamgeInfo.SeriesNumber">
<summary>
序列号 (0020,0011)
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.TestService.GenerateIamgeInfo.InstanceNumber">
<summary>
实例号 (0020,0013)
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.TestService.GenerateIamgeInfo.SeriesDescription">
<summary>
序列描述 (0008,103E)
</summary>
</member>
<member name="P:IRaCIS.Core.Application.Service.TestService.GenerateIamgeInfo.PixelSpacing">
<summary>
像素间距 (0028,0030)
</summary>
</member>
<member name="M:IRaCIS.Core.Application.Service.TestService.GenerateNoisePixel(FellowOakDicom.DicomDataset,System.String)">
<summary>
生成随机噪声图
</summary>
</member>
<member name="M:IRaCIS.Core.Application.Service.TestService.FillNoiseRectangle(System.UInt16[],System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32,System.Int32)">
<summary>
噪声矩形
</summary>
</member>
<member name="M:IRaCIS.Core.Application.Service.TestService.GenerateAsync(System.String,System.String,System.String,System.Int32,System.Collections.Generic.List{IRaCIS.Core.Application.Service.TestService.GenerateIamgeInfo})">
<summary>
批量生成Series
</summary>
</member>
<member name="M:IRaCIS.Core.Application.Service.TestService.MaskImage"> <member name="M:IRaCIS.Core.Application.Service.TestService.MaskImage">
<summary> <summary>
遮挡影像 遮挡影像

View File

@ -2,6 +2,7 @@
using ClosedXML.Excel; using ClosedXML.Excel;
using FellowOakDicom; using FellowOakDicom;
using FellowOakDicom.Imaging; using FellowOakDicom.Imaging;
using FellowOakDicom.IO.Buffer;
using IRaCIS.Application.Contracts; using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Contracts; using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.Helper;
@ -57,6 +58,9 @@ namespace IRaCIS.Core.Application.Service
{ {
public static int IntValue = 100; public static int IntValue = 100;
private readonly Random random = new();
static string ReplaceText(string text, Dictionary<string, string> replaceRules) static string ReplaceText(string text, Dictionary<string, string> replaceRules)
{ {
foreach (var rule in replaceRules) foreach (var rule in replaceRules)
@ -431,6 +435,439 @@ namespace IRaCIS.Core.Application.Service
} }
public async Task<IResponseOutput> GenerateImage()
{
var sourceDir = @"C:\work\测量图像";
var targetDir = @"C:\work\测量图像\纯黑色";
Directory.CreateDirectory(targetDir);
foreach (var inputPath in Directory.EnumerateFiles(sourceDir, "*", SearchOption.TopDirectoryOnly))
{
var relativePath = Path.GetRelativePath(sourceDir, inputPath);
var outputPath = Path.Combine(targetDir, relativePath);
var outputFolder = Path.GetDirectoryName(outputPath);
if (!string.IsNullOrEmpty(outputFolder))
{
Directory.CreateDirectory(outputFolder);
}
await using var input = File.OpenRead(inputPath);
await using var output = File.Create(outputPath);
try
{
Console.WriteLine($"开始处理: {relativePath}");
var dicomFile = await DicomFile.OpenAsync(input, FileReadOption.ReadAll).ConfigureAwait(false);
var ds = dicomFile.Dataset;
var rows = ds.GetSingleValue<int>(DicomTag.Rows);
var cols = ds.GetSingleValue<int>(DicomTag.Columns);
var bitsAllocated = ds.GetSingleValue<int>(DicomTag.BitsAllocated);
var bitsStored = ds.GetSingleValueOrDefault(DicomTag.BitsStored, bitsAllocated);
var samplesPerPixel = ds.GetSingleValue<int>(DicomTag.SamplesPerPixel);
var pixelRepresentation = ds.GetSingleValueOrDefault(DicomTag.PixelRepresentation, (ushort)0);
//Console.WriteLine($"Working Rows={rows}, Cols={cols}, BitsAllocated={bitsAllocated}, BitsStored={bitsStored}, SamplesPerPixel={samplesPerPixel}, PixelRepresentation={pixelRepresentation}");
//遮盖成黑色
var regions = new[] { new MaskRegion(0, 0, rows, cols) };
await DicomPixelMasker.MaskAsync(input, output, regions, new DicomMaskOptions() { GrayscaleMaskValue = 0 });
}
catch (Exception ex)
{
// 跳过该文件
Console.WriteLine();
Console.WriteLine($"error: {relativePath} —————— {ex.Message}");
Console.WriteLine();
}
Console.WriteLine($"处理结束: {relativePath}");
}
var list = new List<GenerateIamgeInfo>();
await GenerateAsync(@"C:\work\测量图像\纯黑色\CT.dcm", @"C:\work\测量图像\output", "CT", 20, list);
await GenerateAsync(@"C:\work\测量图像\纯黑色\MR.dcm", @"C:\work\测量图像\output", "MR", 20, list);
await GenerateAsync(@"C:\work\测量图像\纯黑色\PT.dcm", @"C:\work\测量图像\output", "PT", 10, list);
string exportPath = @$"C:\work\测量图像\outPut.xlsx";
MiniExcel.SaveAs(exportPath, list,overwriteFile:true);
return ResponseOutput.Ok();
}
#region 噪声
public class GenerateIamgeInfo
{
/// <summary>
/// 患者ID (0010,0020)
/// </summary>
public string PatientId { get; set; }
/// <summary>
/// 患者姓名 (0010,0010)
/// </summary>
public string PatientName { get; set; }
/// <summary>
/// 研究实例UID (0020,000D)
/// </summary>
public string StudyInstanceUID { get; set; }
/// <summary>
/// 序列实例UID (0020,000E)
/// </summary>
public string SeriesInstanceUID { get; set; }
/// <summary>
/// SOP实例UID (0008,0018)
/// </summary>
public string SOPInstanceUID { get; set; }
/// <summary>
/// 序列号 (0020,0011)
/// </summary>
public int SeriesNumber { get; set; }
/// <summary>
/// 实例号 (0020,0013)
/// </summary>
public int InstanceNumber { get; set; }
/// <summary>
/// 序列描述 (0008,103E)
/// </summary>
public string SeriesDescription { get; set; }
public int WidthMM => Width / 2;
public int HeightMM => Height / 2;
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
/// <summary>
/// 像素间距 (0028,0030)
/// </summary>
public string PixelSpacing { get; set; }
}
/// <summary>
/// 生成随机噪声图
/// </summary>
private MaskRegion GenerateNoisePixel(
DicomDataset ds,
string modality)
{
Random random = new Random(); // 创建实例
int rows = ds.GetSingleValue<int>(DicomTag.Rows);
int cols = ds.GetSingleValue<int>(DicomTag.Columns);
int bits = ds.GetSingleValue<int>(DicomTag.BitsAllocated);
if (bits != 16)
{
throw new Exception(
"只处理16bit CT/MR/PT");
}
ushort[] pixels = new ushort[rows * cols];
//
// 黑底
//
for (int i = 0; i < pixels.Length; i++)
{
pixels[i] = 0;
}
// PixelSpacing
double pixelSpacing = 0.5;
// 生成10cm-20cm矩形
double widthMm = random.Next(100, 200);
double heightMm = random.Next(100, 200);
int width = (int)(widthMm / pixelSpacing);
int height = (int)(heightMm / pixelSpacing);
int x = random.Next(0, cols - width);
int y = random.Next(0, rows - height);
int min;
int max;
if (modality == "PT")
{
min = 0;
max = 16000;
}
else if (modality == "CT")
{
min = 5;
max = 2000;
}
else
{
// MR
min = 5;
max = 2000;
}
FillNoiseRectangle(pixels, cols, x, y, width, height, min, max);
//
// ushort转byte
//
byte[] bytes = new byte[pixels.Length * 2];
Buffer.BlockCopy(pixels, 0, bytes, 0, bytes.Length);
ds.Remove(DicomTag.PixelData);
//ds.Add(DicomTag.PixelData,new DicomOtherByte(DicomTag.PixelData, bytes));
var pixelData = DicomPixelData.Create(ds, true);
pixelData.AddFrame(new MemoryByteBuffer(bytes));
return new MaskRegion(x, y, width, height);
}
/// <summary>
/// 噪声矩形
/// </summary>
private void FillNoiseRectangle(
ushort[] pixels,
int cols,
int x,
int y,
int width,
int height,
int min,
int max)
{
Random random = new Random(); // 创建实例
for (int row = y;
row < y + height;
row++)
{
for (int col = x;
col < x + width;
col++)
{
int index =
row * cols + col;
//
// 每个像素不同
//
pixels[index] = (ushort)random.Next(min, max);
}
}
}
/// <summary>
/// 批量生成Series
/// </summary>
public async Task GenerateAsync(
string template,
string outputDir,
string modality,
int count, List<GenerateIamgeInfo> generateIamgeInfos)
{
Directory.CreateDirectory(outputDir);
//
// Series UID
//
string seriesUid = DicomUIDGenerator.GenerateDerivedFromUUID().UID;
//
// SeriesNumber
//
int seriesNumber = random.Next(1, 101);
for (int i = 1; i <= count; i++)
{
var file = await DicomFile.OpenAsync(template);
var ds = file.Dataset;
//
// Series信息
//
ds.AddOrUpdate(DicomTag.SeriesInstanceUID, seriesUid);
ds.AddOrUpdate(DicomTag.SeriesNumber, seriesNumber);
ds.AddOrUpdate(DicomTag.InstanceNumber, i);
if (modality == "CT")
{
ds.AddOrUpdate(DicomTag.SliceLocation, (i * 5).ToString());
ds.AddOrUpdate(DicomTag.RescaleSlope, "1");
ds.AddOrUpdate(DicomTag.RescaleIntercept, "-1024");
}
if (modality == "PT")
{
// 生成随机身高 (1.60 - 1.80 米)
Random rand = new Random();
double height = Math.Round(rand.NextDouble() * 0.20 + 1.60, 2);
// 根据身高生成符合BMI的体重 (BMI范围 18.5 - 24.9)
double bmi = Math.Round(rand.NextDouble() * 6.4 + 18.5, 1);
double weight = Math.Round(bmi * height * height, 1);
ds.AddOrUpdate(DicomTag.PatientSize, height.ToString("F2"));
ds.AddOrUpdate(DicomTag.PatientWeight, weight.ToString("F1"));
}
var seriesDescription = $"{modality} Data";
ds.AddOrUpdate(DicomTag.SeriesDescription, seriesDescription);
var sopUid = DicomUIDGenerator.GenerateDerivedFromUUID().UID;
//
// SOP Instance UID
//
ds.AddOrUpdate(DicomTag.SOPInstanceUID, sopUid);
//
// Modality
//
ds.AddOrUpdate(DicomTag.Modality, modality);
// ============================
// 2. 修改像素间距
// ============================
ds.AddOrUpdate(DicomTag.PixelSpacing, "0.5\\0.5");
//
// 生成麻子PixelData
//
var region = GenerateNoisePixel(ds, modality);
//
// 文件名
//
string fileName = Path.Combine(outputDir, $"{modality}_{i:D3}.dcm");
await file.SaveAsync(fileName);
var studyUid = ds.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty);
var pid = ds.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
var pName = ds.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty);
generateIamgeInfos.Add(new GenerateIamgeInfo()
{
// 初始化默认值
PatientId = pid,
PatientName = pName,
StudyInstanceUID = studyUid,
SeriesInstanceUID = seriesUid,
SOPInstanceUID = sopUid,
SeriesDescription = seriesDescription,
SeriesNumber = seriesNumber,
InstanceNumber = i,
X = region.X,
Y = region.Y,
Width = region.Width,
Height = region.Height,
PixelSpacing = "0.5\\0.5",
});
}
}
#endregion
/// <summary> /// <summary>
/// 遮挡影像 /// 遮挡影像
/// </summary> /// </summary>