From 540de914853ff47db3a1bd0ec14d3e364bd0720e Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Fri, 7 Aug 2026 14:56:35 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=9F=E6=88=90=E6=B5=8B=E8=AF=95=E5=9B=BE?= =?UTF-8?q?=E5=83=8F=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IRaCIS.Core.Application.xml | 60 +++ IRaCIS.Core.Application/TestService.cs | 437 ++++++++++++++++++ 2 files changed, 497 insertions(+) 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 + + /// /// 遮挡影像 ///