diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml
index 89b4871f8..a348a926f 100644
--- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml
+++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml
@@ -16274,6 +16274,66 @@
访视数据模型
+
+
+ 患者ID (0010,0020)
+
+
+
+
+ 患者姓名 (0010,0010)
+
+
+
+
+ 研究实例UID (0020,000D)
+
+
+
+
+ 序列实例UID (0020,000E)
+
+
+
+
+ SOP实例UID (0008,0018)
+
+
+
+
+ 序列号 (0020,0011)
+
+
+
+
+ 实例号 (0020,0013)
+
+
+
+
+ 序列描述 (0008,103E)
+
+
+
+
+ 像素间距 (0028,0030)
+
+
+
+
+ 生成随机噪声图
+
+
+
+
+ 噪声矩形
+
+
+
+
+ 批量生成Series
+
+
遮挡影像
diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs
index 70f40183a..b8eb0580c 100644
--- a/IRaCIS.Core.Application/TestService.cs
+++ b/IRaCIS.Core.Application/TestService.cs
@@ -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 replaceRules)
{
foreach (var rule in replaceRules)
@@ -431,6 +435,439 @@ namespace IRaCIS.Core.Application.Service
}
+
+ public async Task 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(DicomTag.Rows);
+ var cols = ds.GetSingleValue(DicomTag.Columns);
+ var bitsAllocated = ds.GetSingleValue(DicomTag.BitsAllocated);
+ var bitsStored = ds.GetSingleValueOrDefault(DicomTag.BitsStored, bitsAllocated);
+ var samplesPerPixel = ds.GetSingleValue(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();
+
+
+ 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
+ {
+ ///
+ /// 患者ID (0010,0020)
+ ///
+ public string PatientId { get; set; }
+
+ ///
+ /// 患者姓名 (0010,0010)
+ ///
+ public string PatientName { get; set; }
+
+ ///
+ /// 研究实例UID (0020,000D)
+ ///
+ public string StudyInstanceUID { get; set; }
+
+ ///
+ /// 序列实例UID (0020,000E)
+ ///
+ public string SeriesInstanceUID { get; set; }
+
+ ///
+ /// SOP实例UID (0008,0018)
+ ///
+ public string SOPInstanceUID { get; set; }
+
+ ///
+ /// 序列号 (0020,0011)
+ ///
+ public int SeriesNumber { get; set; }
+
+ ///
+ /// 实例号 (0020,0013)
+ ///
+ public int InstanceNumber { get; set; }
+
+ ///
+ /// 序列描述 (0008,103E)
+ ///
+ 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; }
+
+ ///
+ /// 像素间距 (0028,0030)
+ ///
+ public string PixelSpacing { get; set; }
+
+ }
+
+ ///
+ /// 生成随机噪声图
+ ///
+ private MaskRegion GenerateNoisePixel(
+ DicomDataset ds,
+ string modality)
+ {
+ Random random = new Random(); // 创建实例
+
+ int rows = ds.GetSingleValue(DicomTag.Rows);
+
+ int cols = ds.GetSingleValue(DicomTag.Columns);
+
+ int bits = ds.GetSingleValue(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);
+
+ }
+
+ ///
+ /// 噪声矩形
+ ///
+ 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);
+
+ }
+
+ }
+
+
+ }
+
+ ///
+ /// 批量生成Series
+ ///
+ public async Task GenerateAsync(
+ string template,
+ string outputDir,
+ string modality,
+ int count, List 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
+
+
///
/// 遮挡影像
///