生成测试图像代码提交
parent
581ee62a5b
commit
540de91485
|
|
@ -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>
|
||||||
遮挡影像
|
遮挡影像
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue