From 32a06be96dc126cbe9b28dd0e0cca78f29862a5d Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Thu, 7 May 2026 11:28:32 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=A4=9A=E5=B8=A7=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E6=8B=86=E5=88=86=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/ImageAndDoc/StudyService.cs | 211 ++++++++++++++---- 1 file changed, 172 insertions(+), 39 deletions(-) diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs index 4cc69c749..10c7fc48e 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs @@ -17,6 +17,7 @@ using Microsoft.Extensions.Hosting; using NetTopologySuite.Algorithm; using SharpCompress.Common; using SkiaSharp; +using System.Data; using System.Drawing; using ZiggyCreatures.Caching.Fusion; @@ -77,9 +78,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc } - private static async Task TryWriteMergedDicomAsync( -Func> sourceFactory, -Stream output) + private static async Task TryWriteMergedDicomAsync(Func> sourceFactory, Stream output) { try { @@ -125,6 +124,166 @@ Stream output) } } + private static async Task TryWriteSplitDicomAsync(Stream input) + { + var dicomFile = await DicomFile.OpenAsync(input); + + var numberOfFrames = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1); + + //多帧处理逻辑 + if (numberOfFrames > 1) + { + //一定要有像素数据才处理 + var pixelData = DicomPixelData.Create(dicomFile.Dataset); + + if (pixelData != null) + { + try + { + + + var syntax = pixelData.Syntax; + + // 每个 fragment 固定大小 (64KB 示例,可以自己调整) + int fragmentSize = 20 * 1024; + + + + var frag = dicomFile.Dataset.GetDicomItem(DicomTag.PixelData); + + int fragmentCount = frag?.Fragments?.Count() ?? 0; + + var originOffsetTable = frag?.OffsetTable; //有可能没有表,需要自己重建 + + var bot = new List(); + + uint botOffset = 0; + + //需要拆成固定片段的 + if (syntax.IsEncapsulated && fragmentCount == pixelData.NumberOfFrames && numberOfFrames > 1) + { + + var newFragments = new DicomOtherByteFragment(DicomTag.PixelData); + + + #region 最终使用 + + for (int n = 0; n < pixelData.NumberOfFrames; n++) + { + var frameData = pixelData.GetFrame(n); // 获取完整一帧 + var data = frameData.Data; + int offset = 0; + + bot.Add(botOffset); + + botOffset += (uint)data.Length; + + + while (offset < data.Length) + { + botOffset += 8; + + + int size = Math.Min(fragmentSize, data.Length - offset); + var buffer = new byte[size]; + Buffer.BlockCopy(data, offset, buffer, 0, size); + + newFragments.Fragments.Add(new MemoryByteBuffer(buffer)); + + offset += size; + + } + } + + //保留原始偏移表 + + if (originOffsetTable.Count == pixelData.NumberOfFrames) + { + newFragments.OffsetTable.AddRange(originOffsetTable.ToArray()); + + } + else + { + newFragments.OffsetTable.AddRange(bot.ToArray()); + + } + + #endregion + + + dicomFile.Dataset.AddOrUpdate(newFragments); + + + } + //传递过来的就是拆分的,但是是没有偏移表的,我需要自己创建偏移表,不然生成缩略图失败 + else if (syntax.IsEncapsulated && fragmentCount > pixelData.NumberOfFrames && originOffsetTable.Count == 0) + { + + + + uint offset = 0; + + bot.Add(offset); + + var fistSize = frag.Fragments.FirstOrDefault()?.Size ?? 0; + + //和上一个大小不一样 + var isDiffrentBefore = false; + + uint count = 0; + + // 假设你知道每帧对应的 fragment 数量 + foreach (var frameFragments in frag.Fragments) + { + count++; + + if (frameFragments.Size == fistSize) + { + isDiffrentBefore = false; + // 累加这一帧所有 fragment 的大小 + offset += (uint)frameFragments.Size; + continue; + } + else + { + offset += (uint)frameFragments.Size; + isDiffrentBefore = true; + + } + + if (isDiffrentBefore) + { + //每个Fragment 也占用字节 + offset += 8 * count; + bot.Add(offset); + count = 0; + + } + + } + + bot.RemoveAt(bot.Count - 1); + // 设置到新的 PixelData + frag.OffsetTable.AddRange(bot.ToArray()); + + + + } + + } + catch (Exception ex) + { + // 只记录,不传播 + Log.Logger.Warning($"拆分 failed: {ex.Message}"); + } + } + + } + + return dicomFile; + + } + /// /// 标注遮盖影像 路径后面加了.MaskImage 就是遮盖的新路径 /// @@ -164,7 +323,7 @@ Stream output) await using var inputStream = new MemoryStream(); - if (item.IsEncapsulated /*&& item.NumberOfFrames > 1*/) + if (item.IsEncapsulated) { var ok = await TryWriteMergedDicomAsync(() => _oSSService.GetStreamFromOSSAsync(path), inputStream); @@ -175,42 +334,10 @@ Stream output) } - - - - - //await using var ms = new MemoryStream(); - - //await inputStream.CopyToAsync(ms); - - //ms.Position = 0; - - - #region 测试废弃 - //var localPath = Path.Combine(FileStoreHelper.GetDonwnloadImageFolder(_hostEnvironment), Path.GetFileName(path)); - - //await _oSSService.DownLoadFromOSSAsync(path, localPath); - - //await using var input = File.OpenRead(localPath); - - //var outputPath = Path.Combine(FileStoreHelper.GetDonwnloadImageFolder(_hostEnvironment), "After", Path.GetFileName(path)); - - //var outputFolder = Path.GetDirectoryName(outputPath); - //if (!string.IsNullOrEmpty(outputFolder)) - //{ - // Directory.CreateDirectory(outputFolder); - //} - - //await using var output = File.Create(outputPath); - - //await DicomPixelMasker.MaskAsync(inputStream, output,inCommand.MaskRegionList); - - #endregion - - - var outPutStream = await DicomPixelMasker.MaskAsync(inputStream, inCommand.MaskRegionList); + var needUploadDicomFile = await TryWriteSplitDicomAsync(outPutStream); + var prefix = path.Substring(1, path.LastIndexOf('/') - 1); //每次都用一个新的名字 @@ -234,7 +361,13 @@ Stream output) maskFileName = $"{Path.GetFileName(path)}.MaskDicom_{batchId}"; } - await _oSSService.UploadToOSSAsync(outPutStream, prefix, maskFileName, false); + + // 直接写入内存 + await using var uploadStream = new MemoryStream(); + await needUploadDicomFile.SaveAsync(uploadStream); + uploadStream.Position = 0; + + await _oSSService.UploadToOSSAsync(uploadStream, prefix, maskFileName, false); var newPath = $"/{prefix}/{maskFileName}"; From 7957a33fc4fd1baac600c050dca9c8a1fd82d420 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Thu, 7 May 2026 14:31:27 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=9C=AA=E9=98=85?= =?UTF-8?q?=E7=89=87=E5=AE=8C=E6=88=90=E7=9A=84=E8=AE=BF=E8=A7=86=EF=BC=8C?= =?UTF-8?q?=E6=96=B9=E4=BE=BF=E8=BF=9B=E8=A1=8C=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/TrialImageDownloadService.cs | 9 +++++++-- .../ImageAndDoc/DownloadAndUploadService.cs | 19 +++++++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index d1b1c3ed7..26b3e73a8 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -61,6 +61,9 @@ namespace IRaCIS.Core.Application.Service [AllowAnonymous] public async Task DownloadTrialImage(Guid trialId) { + //找到项目里面未阅片的影像 + + //var subjectCodeList = new List() { "05002", "07006", "07026" }; var downloadInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new { @@ -213,7 +216,7 @@ namespace IRaCIS.Core.Application.Service } catch (Exception ex) { - Console.WriteLine($"下载失败: {ex.Message}"); + Log.Logger.Error($"下载失败: {ex.Message}"); } downloadedCount++; @@ -221,7 +224,9 @@ namespace IRaCIS.Core.Application.Service // 每处理50个,输出一次进度(或最后一个时也输出) if (downloadedCount % 50 == 0 || downloadedCount == totalCount) { - Console.WriteLine($"已下载 {downloadedCount} / {totalCount} 个文件,完成 {(downloadedCount * 100.0 / totalCount):F2}%"); + + Log.Logger.Error($"已下载 {downloadedCount} / {totalCount} 个文件,完成 {(downloadedCount * 100.0 / totalCount):F2}%"); + } } #endregion diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs index f7267956f..db29531ad 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs @@ -1660,7 +1660,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc #endregion var query = from sv in _subjectRepository.Where(t => t.Id == inQuery.SubjectId).SelectMany(t => t.SubjectVisitList.Where(t => subjectVisitIdList.Contains(t.Id) && t.CheckState == CheckStateEnum.CVPassed)) - //一致性分析,导致查询出来两条数据 + //一致性分析,导致查询出来两条数据 join visitTask in _visitTaskRepository.Where(t => taskIdList.Contains(t.Id)) on sv.Id equals visitTask.SourceSubjectVisitId into cc from leftVisitTask in cc.DefaultIfEmpty() select new ImageDownloadDto() @@ -2005,6 +2005,21 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc } + + /// + /// 获取未阅片完成的访视,方便前端调用下载 + /// + /// + /// + public async Task> GetTrialUnreadVisitList(Guid trialId) + { + + var subjectVisitList = await _visitTaskRepository.Where(t => t.TrialId == trialId && t.TaskState == TaskState.Effect && t.ReadingCategory == ReadingCategory.Visit && t.ReadingTaskState != ReadingTaskState.HaveSigned && t.SourceSubjectVisitId!=null).Select(t => t.SourceSubjectVisitId) + .Distinct().ToListAsync(); + + return subjectVisitList; + } + /// /// 批量勾选访视 进行下载 /// @@ -2585,7 +2600,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc } - + /// /// 后台任务调用,前端忽略该接口 ///