using DocumentFormat.OpenXml.EMMA; using FellowOakDicom; using FellowOakDicom.Imaging; using FellowOakDicom.Imaging.Render; using FellowOakDicom.IO.Buffer; using IRaCIS.Core.Application.Helper; using MassTransit; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using SharpCompress.Common; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; using static IRaCIS.Core.Domain.Share.StaticData; namespace IRaCIS.Core.Application.Service { /// /// 项目影像后台下载,不打包 /// /// /// [ApiExplorerSettings(GroupName = "Common")] public class TrialImageDownloadService(IRepository _trialRepository, IOSSService _oSSService, IWebHostEnvironment _hostEnvironment) : BaseService { /// /// 后端api swagger 下载项目影像 /// /// /// [HttpPost] [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 { t.ResearchProgramNo, VisitList = t.SubjectVisitList //.Where(t=>subjectCodeList.Contains(t.Subject.Code)) .Select(sv => new { TrialSiteCode = sv.TrialSite.TrialSiteCode, SubjectCode = sv.Subject.Code, VisitName = sv.VisitName, StudyList = sv.StudyList.Select(u => new { u.PatientId, u.StudyTime, u.StudyCode, SeriesList = u.SeriesList.Where(t => t.IsReading).Select(z => new { z.Modality, InstancePathList = z.DicomInstanceList.Where(t => t.IsReading).Select(k => new { k.Path }).ToList() }) }).ToList(), NoneDicomStudyList = sv.NoneDicomStudyList.Where(t => t.IsReading).Select(nd => new { nd.Modality, nd.StudyCode, nd.ImageDate, FileList = nd.NoneDicomFileList.Where(t => t.IsReading).Select(file => new { file.FileName, file.Path, file.FileType }).ToList() }).ToList() }).ToList() }).FirstOrDefault(); var count = downloadInfo.VisitList.SelectMany(t => t.NoneDicomStudyList).SelectMany(t => t.FileList).Count(); var count2 = downloadInfo.VisitList.SelectMany(t => t.StudyList).SelectMany(t => t.SeriesList).SelectMany(t => t.InstancePathList).Count(); Console.WriteLine($"下载总数量:{count}+{count2}={count + count2}"); if (downloadInfo != null) { var downloadJobs = new List>(); var rootFolder = @"E:\DownloadImage"; //var rootFolder = FileStoreHelper.GetDonwnloadImageFolder(_hostEnvironment); // 获取无效字符(系统定义的) string invalidChars = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); // 用正则表达式替换所有非法字符为下划线或空字符 string pattern = $"[{Regex.Escape(invalidChars)}]"; var regexNo = Regex.Replace(downloadInfo.ResearchProgramNo, pattern, "_"); // 创建一个临时文件夹来存放文件 string trialFolderPath = Path.Combine(rootFolder, $"{regexNo}_{NewId.NextGuid()}"); Directory.CreateDirectory(trialFolderPath); foreach (var visitItem in downloadInfo.VisitList) { if (visitItem.StudyList.Count() == 0 && visitItem.NoneDicomStudyList.Count() == 0) { continue; } #region 处理 中心,受试者dicom non-dicom 文件夹层级 var siteFolderPath = Path.Combine(trialFolderPath, visitItem.TrialSiteCode); if (!Directory.Exists(siteFolderPath)) { Directory.CreateDirectory(siteFolderPath); } #endregion foreach (var studyInfo in visitItem.StudyList) { // 遍历 Series foreach (var seriesInfo in studyInfo.SeriesList) { string studyDicomFolderPath = Path.Combine(siteFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}_DICOM", $"{studyInfo.StudyCode}_{studyInfo.StudyTime?.ToString("yyyy-MM-dd")}_{seriesInfo.Modality}"); // 创建 影像 文件夹 Directory.CreateDirectory(studyDicomFolderPath); // 遍历 InstancePathList foreach (var instanceInfo in seriesInfo.InstancePathList) { // 复制文件到相应的文件夹 string destinationPath = Path.Combine(studyDicomFolderPath, Path.GetFileName(instanceInfo.Path)); //加入到下载任务里 downloadJobs.Add(() => _oSSService.DownLoadFromOSSAsync(instanceInfo.Path, destinationPath)); //下载到当前目录 //await _oSSService.DownLoadFromOSSAsync(instanceInfo.Path, destinationPath); } } } foreach (var noneDicomStudy in visitItem.NoneDicomStudyList) { string studyNoneDicomFolderPath = Path.Combine(siteFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}_Non-DICOM", $"{noneDicomStudy.StudyCode}_{noneDicomStudy.ImageDate.ToString("yyyy-MM-dd")}_{noneDicomStudy.Modality}"); Directory.CreateDirectory(studyNoneDicomFolderPath); foreach (var file in noneDicomStudy.FileList) { string destinationPath = Path.Combine(studyNoneDicomFolderPath, Path.GetFileName(file.FileName)); //加入到下载任务里 downloadJobs.Add(() => _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath)); //下载到当前目录 //await _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath); } } } #region 异步方式处理 int totalCount = downloadJobs.Count; int downloadedCount = 0; foreach (var job in downloadJobs) { try { await job(); } catch (Exception ex) { Console.WriteLine($"下载失败: {ex.Message}"); } downloadedCount++; // 每处理50个,输出一次进度(或最后一个时也输出) if (downloadedCount % 50 == 0 || downloadedCount == totalCount) { Console.WriteLine($"已下载 {downloadedCount} / {totalCount} 个文件,完成 {(downloadedCount * 100.0 / totalCount):F2}%"); } } #endregion #region 多线程测试 //const int batchSize = 15; //int totalCount = downloadJobs.Count; //int downloadedCount = 0; //for (int i = 0; i < downloadJobs.Count; i += batchSize) //{ // var batch = downloadJobs.Skip(i).Take(batchSize).Select(job => job()); // await Task.WhenAll(batch); // downloadedCount += batch.Count(); // Console.WriteLine($"已下载 {downloadedCount} / {totalCount} 个文件,完成 {(downloadedCount * 100.0 / totalCount):F2}%"); //} #endregion } return ResponseOutput.Ok(); } /// /// 下载影像 维护dir信息 并回传到OSS /// /// /// [HttpGet] [AllowAnonymous] public async Task DownloadAndUploadTrialData(Guid trialId, [FromServices] IRepository _instanceRepository, [FromServices] IRepository _studyRepository, [FromServices] IRepository _seriesRepository) { var list = await _instanceRepository.Where(t => t.TrialId == trialId && t.SubjectVisitId == Guid.Parse("01000000-0a00-0242-bd20-08dcce543ded" ) && t.DicomStudy.ModalityForEdit == "IVUS") .Select(t => new { t.SeriesId, t.StudyId, t.Id, t.Path }).ToListAsync(); int totalCount = list.Count; int dealCount = 0; foreach (var item in list) { var stream = await _oSSService.GetStreamFromOSSAsync(item.Path); var dicomFile = DicomFile.Open(stream); // 获取 Pixel Data 标签 var pixelData = DicomPixelData.Create(dicomFile.Dataset); //获取像素是否为封装形式 var syntax = dicomFile.Dataset.InternalTransferSyntax; //对于封装像素的文件做转换 if (syntax.IsEncapsulated) { // 创建一个新的片段序列 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)); } // 替换原有的片段序列 dicomFile.Dataset.AddOrUpdate(newFragments); } #region 获取dir信息 维护数据库三个表数据 var dirInfo = DicomDIRHelper.ReadDicomDIRInfo(dicomFile); await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id, u => new DicomInstance() { IsEncapsulated= syntax.IsEncapsulated, TransferSytaxUID = dirInfo.TransferSytaxUID, SOPClassUID = dirInfo.SOPClassUID, MediaStorageSOPClassUID = dirInfo.MediaStorageSOPClassUID, MediaStorageSOPInstanceUID = dirInfo.MediaStorageSOPInstanceUID }, false); await _seriesRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.SeriesId, u => new DicomSeries() { DicomSeriesDate = dirInfo.DicomSeriesDate, DicomSeriesTime = dirInfo.DicomSeriesTime, }, false); await _studyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.StudyId, u => new DicomStudy() { DicomStudyDate = dirInfo.DicomStudyDate, DicomStudyTime = dirInfo.DicomStudyTime }, false); #endregion //保存到内存流 using var memoryStream = new MemoryStream(); dicomFile.Save(memoryStream); memoryStream.Position = 0; //获取原始目录 和文件名 var folder = item.Path.Substring(0, item.Path.LastIndexOf('/')).TrimStart('/'); var fileName = Path.GetFileName(item.Path); //dicomFile.Save($"download_{Guid.NewGuid()}"); await _oSSService.UploadToOSSAsync(memoryStream, folder, fileName, false); dealCount++; Console.WriteLine($"{DateTime.Now}已下载 {dealCount} / {totalCount} 个文件,完成 {(dealCount * 100.0 / totalCount):F2}%"); } return ResponseOutput.Ok(); } } }