生成测试图像代码提交
parent
581ee62a5b
commit
540de91485
|
|
@ -16274,6 +16274,66 @@
|
|||
访视数据模型
|
||||
</summary>
|
||||
</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">
|
||||
<summary>
|
||||
遮挡影像
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using ClosedXML.Excel;
|
||||
using FellowOakDicom;
|
||||
using FellowOakDicom.Imaging;
|
||||
using FellowOakDicom.IO.Buffer;
|
||||
using IRaCIS.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Contracts;
|
||||
using IRaCIS.Core.Application.Helper;
|
||||
|
|
@ -57,6 +58,9 @@ namespace IRaCIS.Core.Application.Service
|
|||
{
|
||||
public static int IntValue = 100;
|
||||
|
||||
private readonly Random random = new();
|
||||
|
||||
|
||||
static string ReplaceText(string text, Dictionary<string, string> 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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue