diff --git a/IRaCIS.Core.Application/Helper/OSSService.cs b/IRaCIS.Core.Application/Helper/OSSService.cs index 3c84236b2..aa63d57e4 100644 --- a/IRaCIS.Core.Application/Helper/OSSService.cs +++ b/IRaCIS.Core.Application/Helper/OSSService.cs @@ -1073,6 +1073,13 @@ public class OSSService(IOptionsMonitor options, { BackBatchGetToken(); + // 确保目标目录存在 + string directory = Path.GetDirectoryName(localFilePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + ossRelativePath = ossRelativePath.TrimStart('/'); try { diff --git a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs index f43b076b9..d1b1c3ed7 100644 --- a/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs +++ b/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs @@ -118,9 +118,9 @@ namespace IRaCIS.Core.Application.Service { var downloadJobs = new List>(); - var rootFolder = @"E:\DownloadImage"; + //var rootFolder = @"E:\DownloadImage"; - //var rootFolder = FileStoreHelper.GetDonwnloadImageFolder(_hostEnvironment); + var rootFolder = FileStoreHelper.GetDonwnloadImageFolder(_hostEnvironment); // 获取无效字符(系统定义的) string invalidChars = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/DTO/UnionStudyViewDodel.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/DTO/UnionStudyViewDodel.cs index 4c4c361be..9edf88c4b 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/DTO/UnionStudyViewDodel.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/DTO/UnionStudyViewDodel.cs @@ -741,6 +741,7 @@ namespace IRaCIS.Core.Application.Contracts public class DownloadDicomInstanceDto { + public int NumberOfFrames { get; set; } public bool IsEncapsulated { get; set; } public Guid InstanceId { get; set; } public string FileName { get; set; } @@ -1171,4 +1172,6 @@ namespace IRaCIS.Core.Application.Contracts public string Path { get; set; } } + + } diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs index 0964262f9..4cc69c749 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/StudyService.cs @@ -1,13 +1,21 @@ -using IRaCIS.Core.Application.Contracts; +using FellowOakDicom; +using FellowOakDicom.Imaging; +using FellowOakDicom.IO.Buffer; +using IRaCIS.Core.Application.Contracts; using IRaCIS.Core.Application.Filter; using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.Service.ImageAndDoc.DTO; using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Infrastructure; +using MathNet.Numerics; using Medallion.Threading; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Hosting; using NetTopologySuite.Algorithm; +using SharpCompress.Common; using SkiaSharp; using System.Drawing; using ZiggyCreatures.Caching.Fusion; @@ -29,7 +37,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc IRepository _systemAnonymizationRepository, IRepository _noneDicomStudyRepository, IDistributedLockProvider _distributedLockProvider, IOSSService _oSSService, - IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IFusionCache _fusionCache) : BaseService, IStudyService + IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IFusionCache _fusionCache, IWebHostEnvironment _hostEnvironment) : BaseService, IStudyService { @@ -69,6 +77,54 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc } + private static async Task TryWriteMergedDicomAsync( +Func> sourceFactory, +Stream output) + { + try + { + await using var source = await sourceFactory(); + // 如果你是从 stream 打开 + var dicomFile = await DicomFile.OpenAsync(source); + + //获取像素是否为封装形式 + var syntax = dicomFile.Dataset.InternalTransferSyntax; + + //对于封装像素的文件做转换 + if (syntax.IsEncapsulated) + { + // 获取 Pixel Data 标签 + var pixelData = DicomPixelData.Create(dicomFile.Dataset); + + // 创建一个新的片段序列 + var newFragments = new DicomOtherByteFragment(DicomTag.PixelData); + // 获取每帧数据并封装为单独的片段 + for (int n = 0; n < pixelData.NumberOfFrames; n++) + { + var frameData = pixelData.GetFrame(n); + newFragments.Fragments.Add(new MemoryByteBuffer(frameData.Data)); + } + + var frag = dicomFile.Dataset.GetDicomItem(DicomTag.PixelData); + + var originOffsetTable = frag?.OffsetTable; + + newFragments.OffsetTable.AddRange(originOffsetTable?.ToArray()); + // 替换原有的片段序列 + dicomFile.Dataset.AddOrUpdate(newFragments); + } + + await dicomFile.SaveAsync(output); + return true; + } + catch (Exception ex) + { + // 只记录,不传播 + Log.Logger.Warning($"TryWriteMergedDicomAsync failed: {ex.Message}"); + return false; + } + } + /// /// 标注遮盖影像 路径后面加了.MaskImage 就是遮盖的新路径 /// @@ -81,16 +137,16 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc } - var idPathList = new List(); + var idPathList = new List(); if (inCommand.SeriesId == null && inCommand.InstanceIdList != null) { - idPathList = await _dicomInstanceRepository.Where(t => inCommand.InstanceIdList.Contains(t.Id)).Select(t => new InstanceIdPath { Id = t.Id, Path = t.Path }).ToListAsync(); + idPathList = await _dicomInstanceRepository.Where(t => inCommand.InstanceIdList.Contains(t.Id)).ProjectTo(_mapper.ConfigurationProvider).ToListAsync(); } else { - idPathList = await _dicomInstanceRepository.Where(t => t.SeriesId == inCommand.SeriesId).Select(t => new InstanceIdPath { Id = t.Id, Path = t.Path }).ToListAsync(); + idPathList = await _dicomInstanceRepository.Where(t => t.SeriesId == inCommand.SeriesId).ProjectTo(_mapper.ConfigurationProvider).ToListAsync(); } var errorList = new List(); @@ -105,7 +161,53 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc try { - var inputStream = await _oSSService.GetStreamFromOSSAsync(path); + + await using var inputStream = new MemoryStream(); + + if (item.IsEncapsulated /*&& item.NumberOfFrames > 1*/) + { + + var ok = await TryWriteMergedDicomAsync(() => _oSSService.GetStreamFromOSSAsync(path), inputStream); + } + else + { + await (await _oSSService.GetStreamFromOSSAsync(path)).CopyToAsync(inputStream); + } + + + + + + + //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); @@ -120,7 +222,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc await _oSSService.DeleteFromPrefix(path, true); //清理缓存的里面的遮盖图,多次遮盖同一张图时,清除缓存很重要 //本身就是遮盖的图,那么就要要替换guid - var length = Guid.Empty.ToString().Length + ".MaskDicom_".Length ; + var length = Guid.Empty.ToString().Length + ".MaskDicom_".Length; var restorePath = item.Path[..^length]; @@ -136,14 +238,14 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc var newPath = $"/{prefix}/{maskFileName}"; - okList.Add(new InstanceIdPath() { Id = item.Id, Path = newPath }); + okList.Add(new InstanceIdPath() { Id = item.InstanceId, Path = newPath }); - await _dicomInstanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id, u => new DicomInstance() { Path = newPath, IsMasked = true }); + await _dicomInstanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.InstanceId, u => new DicomInstance() { Path = newPath, IsMasked = true }); } catch (Exception ex) { - errorList.Add(new InstanceIdPath() { Id = item.Id, Path = path }); + errorList.Add(new InstanceIdPath() { Id = item.InstanceId, Path = path }); Log.Logger.Error(ex, $"StudyMaskImage Error for InstanceIdList Path:{path} {ex.Message}"); } diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/_MapConfig.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/_MapConfig.cs index 622806d77..cf5f5675f 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/_MapConfig.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/_MapConfig.cs @@ -185,7 +185,9 @@ namespace IRaCIS.Core.Application.Service CreateMap(); - + CreateMap() + .ForMember(d => d.InstanceId, u => u.MapFrom(s => s.Id)); + } } diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index 7825aed6a..1b2e67f6e 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -129,8 +129,8 @@ namespace IRaCIS.Core.Application.Service public async Task MaskImage() { - var sourceDir = @"D:\images\11\像素匿名\隐私信息2&测量值\08\08017\基线(V1)\ST01371_1970-01-01_US\IMAGE"; - var targetDir = @"D:\images\11\像素匿名\隐私信息2&测量值\08\08017\基线(V1)\ST01371_1970-01-01_US\IMAGE\after"; + var sourceDir = @"C:\work\gitea\irc-netcore-api\DownloadImage"; + var targetDir = @"C:\work\gitea\irc-netcore-api\DownloadImage\after"; Directory.CreateDirectory(targetDir); var regions = new[]