多帧合并然后遮盖

Uat_IRC_Net8
hang 2026-05-07 10:50:04 +08:00
parent 3179fca112
commit d38ce06f90
7 changed files with 156 additions and 16 deletions

View File

@ -1073,6 +1073,13 @@ public class OSSService(IOptionsMonitor<ObjectStoreServiceOptions> options,
{ {
BackBatchGetToken(); BackBatchGetToken();
// 确保目标目录存在
string directory = Path.GetDirectoryName(localFilePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
ossRelativePath = ossRelativePath.TrimStart('/'); ossRelativePath = ossRelativePath.TrimStart('/');
try try
{ {

View File

@ -15367,7 +15367,7 @@
<param name="_userInfo"></param> <param name="_userInfo"></param>
<param name="_localizer"></param> <param name="_localizer"></param>
</member> </member>
<member name="M:IRaCIS.Core.Application.Service.SegmentationService.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Segmentation},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SegmentBinding},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingTaskQuestionAnswer},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.VisitTask},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingTableAnswerRowInfo},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingTableQuestionAnswer},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Segment},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingQuestionTrial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingTableQuestionTrial},AutoMapper.IMapper,IRaCIS.Core.Domain.Share.IUserInfo,Microsoft.Extensions.Localization.IStringLocalizer)"> <member name="M:IRaCIS.Core.Application.Service.SegmentationService.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Segmentation},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SegmentBinding},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingTaskQuestionAnswer},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.VisitTask},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingTableAnswerRowInfo},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingTableQuestionAnswer},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Segment},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingQuestionTrial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingTableQuestionTrial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SegmentationVersion},AutoMapper.IMapper,IRaCIS.Core.Domain.Share.IUserInfo,Microsoft.Extensions.Localization.IStringLocalizer)">
<summary> <summary>
分割 分割
</summary> </summary>
@ -15390,6 +15390,27 @@
<param name="addOrEditSegmentation"></param> <param name="addOrEditSegmentation"></param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:IRaCIS.Core.Application.Service.SegmentationService.SaveSegmentationVersionAsync(System.Guid)">
<summary>
添加新版本
</summary>
<param name="segmentationId"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.SegmentationService.GetSegmentationVersionList(IRaCIS.Core.Application.ViewModel.SegmentationVersionQuery)">
<summary>
获取分割组历史版本
</summary>
<param name="inQuery"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.SegmentationService.RestoreSegmentationVersion(IRaCIS.Core.Application.ViewModel.RestoreSegmentationVersionInDto)">
<summary>
恢复分割组历史版本
</summary>
<param name="inDto"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.SegmentationService.DeleteSegmentation(System.Guid)"> <member name="M:IRaCIS.Core.Application.Service.SegmentationService.DeleteSegmentation(System.Guid)">
<summary> <summary>
删除分割组 删除分割组
@ -18103,6 +18124,11 @@
是否保存 是否保存
</summary> </summary>
</member> </member>
<member name="P:IRaCIS.Core.Application.ViewModel.SegmentationAddOrEdit.FileSize">
<summary>
文件大小,单位字节
</summary>
</member>
<member name="P:IRaCIS.Core.Application.ViewModel.SegmentBindingView.IsLock"> <member name="P:IRaCIS.Core.Application.ViewModel.SegmentBindingView.IsLock">
<summary> <summary>
是否锁定 是否锁定

View File

@ -118,9 +118,9 @@ namespace IRaCIS.Core.Application.Service
{ {
var downloadJobs = new List<Func<Task>>(); var downloadJobs = new List<Func<Task>>();
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()); string invalidChars = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());

View File

@ -741,6 +741,7 @@ namespace IRaCIS.Core.Application.Contracts
public class DownloadDicomInstanceDto public class DownloadDicomInstanceDto
{ {
public int NumberOfFrames { get; set; }
public bool IsEncapsulated { get; set; } public bool IsEncapsulated { get; set; }
public Guid InstanceId { get; set; } public Guid InstanceId { get; set; }
public string FileName { get; set; } public string FileName { get; set; }
@ -1171,4 +1172,6 @@ namespace IRaCIS.Core.Application.Contracts
public string Path { get; set; } public string Path { get; set; }
} }
} }

View File

@ -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.Filter;
using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Service.ImageAndDoc.DTO; using IRaCIS.Core.Application.Service.ImageAndDoc.DTO;
using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure; using IRaCIS.Core.Infrastructure;
using MathNet.Numerics;
using Medallion.Threading; using Medallion.Threading;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using NetTopologySuite.Algorithm; using NetTopologySuite.Algorithm;
using SharpCompress.Common;
using SkiaSharp; using SkiaSharp;
using System.Drawing; using System.Drawing;
using ZiggyCreatures.Caching.Fusion; using ZiggyCreatures.Caching.Fusion;
@ -29,7 +37,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
IRepository<SystemAnonymization> _systemAnonymizationRepository, IRepository<SystemAnonymization> _systemAnonymizationRepository,
IRepository<NoneDicomStudy> _noneDicomStudyRepository, IRepository<NoneDicomStudy> _noneDicomStudyRepository,
IDistributedLockProvider _distributedLockProvider, IOSSService _oSSService, 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<bool> TryWriteMergedDicomAsync(
Func<Task<Stream>> 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<DicomOtherByteFragment>(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;
}
}
/// <summary> /// <summary>
/// 标注遮盖影像 路径后面加了.MaskImage 就是遮盖的新路径 /// 标注遮盖影像 路径后面加了.MaskImage 就是遮盖的新路径
/// </summary> /// </summary>
@ -81,16 +137,16 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
} }
var idPathList = new List<InstanceIdPath>(); var idPathList = new List<DownloadDicomInstanceDto>();
if (inCommand.SeriesId == null && inCommand.InstanceIdList != null) 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<DownloadDicomInstanceDto>(_mapper.ConfigurationProvider).ToListAsync();
} }
else 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<DownloadDicomInstanceDto>(_mapper.ConfigurationProvider).ToListAsync();
} }
var errorList = new List<InstanceIdPath>(); var errorList = new List<InstanceIdPath>();
@ -105,7 +161,53 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
try 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, outputinCommand.MaskRegionList);
#endregion
var outPutStream = await DicomPixelMasker.MaskAsync(inputStream, inCommand.MaskRegionList); var outPutStream = await DicomPixelMasker.MaskAsync(inputStream, inCommand.MaskRegionList);
@ -120,7 +222,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
await _oSSService.DeleteFromPrefix(path, true); //清理缓存的里面的遮盖图,多次遮盖同一张图时,清除缓存很重要 await _oSSService.DeleteFromPrefix(path, true); //清理缓存的里面的遮盖图,多次遮盖同一张图时,清除缓存很重要
//本身就是遮盖的图那么就要要替换guid //本身就是遮盖的图那么就要要替换guid
var length = Guid.Empty.ToString().Length + ".MaskDicom_".Length ; var length = Guid.Empty.ToString().Length + ".MaskDicom_".Length;
var restorePath = item.Path[..^length]; var restorePath = item.Path[..^length];
@ -136,14 +238,14 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc
var newPath = $"/{prefix}/{maskFileName}"; 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) 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}"); Log.Logger.Error(ex, $"StudyMaskImage Error for InstanceIdList Path:{path} {ex.Message}");
} }

View File

@ -185,6 +185,8 @@ namespace IRaCIS.Core.Application.Service
CreateMap<ImageMarkNoneDicomStudyBasicInfo, NoneDicomStudyBasicInfo>(); CreateMap<ImageMarkNoneDicomStudyBasicInfo, NoneDicomStudyBasicInfo>();
CreateMap<DicomInstance, DownloadDicomInstanceDto>()
.ForMember(d => d.InstanceId, u => u.MapFrom(s => s.Id));
} }

View File

@ -129,8 +129,8 @@ namespace IRaCIS.Core.Application.Service
public async Task<IResponseOutput> MaskImage() public async Task<IResponseOutput> MaskImage()
{ {
var sourceDir = @"D:\images\11\像素匿名\隐私信息2&测量值\08\08017\基线V1\ST01371_1970-01-01_US\IMAGE"; var sourceDir = @"C:\work\gitea\irc-netcore-api\DownloadImage";
var targetDir = @"D:\images\11\像素匿名\隐私信息2&测量值\08\08017\基线V1\ST01371_1970-01-01_US\IMAGE\after"; var targetDir = @"C:\work\gitea\irc-netcore-api\DownloadImage\after";
Directory.CreateDirectory(targetDir); Directory.CreateDirectory(targetDir);
var regions = new[] var regions = new[]