1833 lines
		
	
	
		
			75 KiB
		
	
	
	
		
			C#
		
	
	
			
		
		
	
	
			1833 lines
		
	
	
		
			75 KiB
		
	
	
	
		
			C#
		
	
	
| using Aliyun.OSS;
 | ||
| using DocumentFormat.OpenXml.EMMA;
 | ||
| using DocumentFormat.OpenXml.Office.CustomUI;
 | ||
| using DocumentFormat.OpenXml.Office2010.Excel;
 | ||
| using DocumentFormat.OpenXml.Office2013.Drawing.ChartStyle;
 | ||
| using FellowOakDicom;
 | ||
| using FellowOakDicom.Imaging;
 | ||
| using FellowOakDicom.Imaging.Render;
 | ||
| using FellowOakDicom.IO.Buffer;
 | ||
| using IRaCIS.Core.Application.Helper;
 | ||
| using IRaCIS.Core.Application.ViewModel;
 | ||
| using IRaCIS.Core.Domain.Models;
 | ||
| using IRaCIS.Core.Infrastructure;
 | ||
| using MassTransit;
 | ||
| using Medallion.Threading;
 | ||
| using Microsoft.AspNetCore.Authorization;
 | ||
| using Microsoft.AspNetCore.Hosting;
 | ||
| using Microsoft.AspNetCore.Mvc;
 | ||
| using MiniExcelLibs;
 | ||
| using SharpCompress.Common;
 | ||
| using System;
 | ||
| using System.Collections.Generic;
 | ||
| using System.Data;
 | ||
| using System.Diagnostics;
 | ||
| using System.Linq;
 | ||
| using System.Runtime.InteropServices;
 | ||
| using System.Text;
 | ||
| using System.Text.RegularExpressions;
 | ||
| using System.Threading.Tasks;
 | ||
| using System.Web;
 | ||
| using static IRaCIS.Core.Domain.Share.StaticData;
 | ||
| using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
 | ||
| 
 | ||
| namespace IRaCIS.Core.Application.Service
 | ||
| {
 | ||
| 
 | ||
|     /// <summary>
 | ||
|     /// 项目影像后台下载,不打包
 | ||
|     /// </summary>
 | ||
|     /// <param name="_trialRepository"></param>
 | ||
|     /// <param name="_oSSService"></param>
 | ||
|     [ApiExplorerSettings(GroupName = "Common")]
 | ||
|     public class TrialImageDownloadService(IRepository<Trial> _trialRepository, IOSSService _oSSService, IWebHostEnvironment _hostEnvironment,
 | ||
|         IRepository<DicomStudy> _studyRepository,
 | ||
|          IRepository<DicomSeries> _seriesRepository,
 | ||
|          IRepository<DicomInstance> _instanceRepository) : BaseService
 | ||
|     {
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|         /// <summary>
 | ||
|         /// 后端api swagger 下载项目影像
 | ||
|         /// </summary>
 | ||
|         /// <param name="trialId"></param>
 | ||
|         /// <returns></returns>
 | ||
|         [HttpPost]
 | ||
|         [AllowAnonymous]
 | ||
|         public async Task<IResponseOutput> DownloadTrialImage(Guid trialId)
 | ||
|         {
 | ||
|             //var subjectCodeList = new List<string>() { "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<Func<Task>>();
 | ||
| 
 | ||
|                 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();
 | ||
| 
 | ||
| 
 | ||
|         }
 | ||
| 
 | ||
| 
 | ||
|         /// <summary>
 | ||
|         /// 下载影像  维护dir信息  并回传到OSS
 | ||
|         /// </summary>
 | ||
|         /// <param name="trialId"></param>
 | ||
|         /// <returns></returns>
 | ||
|         [HttpGet]
 | ||
|         [AllowAnonymous]
 | ||
|         public async Task<IResponseOutput> DownloadAndUploadTrialData(Guid trialId,
 | ||
|             [FromServices] IRepository<DicomInstance> _instanceRepository,
 | ||
|             [FromServices] IRepository<DicomStudy> _studyRepository,
 | ||
|             [FromServices] IRepository<DicomSeries> _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();
 | ||
|         }
 | ||
| 
 | ||
| 
 | ||
|         /// <summary>
 | ||
|         /// 维护dir 需求新增的字段
 | ||
| 
 | ||
|         /// </summary>
 | ||
|         /// <param name="trialId"></param>
 | ||
|         /// <param name="_instanceRepository"></param>
 | ||
|         /// <param name="_studyRepository"></param>
 | ||
|         /// <param name="_seriesRepository"></param>
 | ||
|         /// <returns></returns>
 | ||
|         public async Task<IResponseOutput> TrialImageAddExtralField(Guid trialId,
 | ||
|                         [FromServices] IRepository<DicomInstance> _instanceRepository,
 | ||
|              [FromServices] IRepository<DicomStudy> _studyRepository,
 | ||
|              [FromServices] IRepository<DicomSeries> _seriesRepository)
 | ||
|         {
 | ||
|             //            UPDATE DicomStudy
 | ||
|             //SET DicomStudyDate = CONVERT(char(8), StudyTime, 112),                --yyyyMMdd
 | ||
|             //    DicomStudyTime = REPLACE(CONVERT(char(8), StudyTime, 108), ':', ''); --HHmmss
 | ||
|             //                where DicomStudyDate = ''
 | ||
| 
 | ||
| 
 | ||
|             //instance 找到传输语法为空的,然后分组
 | ||
|             var seriesList = _instanceRepository.Where(t => t.TrialId == trialId && t.TransferSytaxUID == "")
 | ||
|                 //按照序列 和  NumberOfFrames 分组
 | ||
|                 .GroupBy(t => new { t.NumberOfFrames, t.SeriesId })
 | ||
|                 // 每个分组 取数据最小的一条
 | ||
|                 .Select(g => new { g.Key.SeriesId, g.Key.NumberOfFrames, g.OrderBy(t => t.FileSize).First().Path }).ToList();
 | ||
| 
 | ||
|             foreach (var item in seriesList)
 | ||
|             {
 | ||
|                 var stream = await _oSSService.GetStreamFromOSSAsync(item.Path);
 | ||
| 
 | ||
|                 var dicomFile = DicomFile.Open(stream);
 | ||
| 
 | ||
|                 var pixelData = DicomPixelData.Create(dicomFile.Dataset);
 | ||
| 
 | ||
|                 //获取像素是否为封装形式
 | ||
|                 var syntax = dicomFile.Dataset.InternalTransferSyntax;
 | ||
| 
 | ||
|                 //读取需要维护的值
 | ||
|                 var transferSyntaxUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty);
 | ||
|                 var mediaStorageSOPClassUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty);
 | ||
|                 var mediaStorageSOPInstanceUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty);
 | ||
|                 var sOPClassUID = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty);
 | ||
| 
 | ||
|                 //维护序列层级四个字段   后再用sql 维护study series 时间拆分 和 MediaStorageSOPInstanceUID
 | ||
|                 await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.SeriesId == item.SeriesId, t => new DicomInstance()
 | ||
|                 {
 | ||
|                     IsEncapsulated = syntax.IsEncapsulated,
 | ||
|                     TransferSytaxUID = transferSyntaxUID,
 | ||
|                     MediaStorageSOPClassUID = mediaStorageSOPClassUID,
 | ||
|                     SOPClassUID = sOPClassUID,
 | ||
|                 });
 | ||
| 
 | ||
|             }
 | ||
| 
 | ||
|             return ResponseOutput.Ok();
 | ||
| 
 | ||
|         }
 | ||
| 
 | ||
| 
 | ||
|         /// <summary>
 | ||
|         /// 下载已经删除的影像
 | ||
|         /// </summary>
 | ||
|         /// <param name="trialId"></param>
 | ||
|         /// <returns></returns>
 | ||
|         [HttpPost]
 | ||
|         [AllowAnonymous]
 | ||
|         public async Task<IResponseOutput> DownloadDeleteTrialImage(Guid trialId)
 | ||
|         {
 | ||
|             trialId = Guid.Parse("01000000-ac13-0242-6397-08dcd2d2a091");
 | ||
| 
 | ||
|             var downloadInfo = _trialRepository.Where(t => t.Id == trialId, ignoreQueryFilters: true).Select(t => new
 | ||
|             {
 | ||
|                 t.ResearchProgramNo,
 | ||
| 
 | ||
|                 VisitList = t.SubjectVisitList
 | ||
|                 .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.Select(z => new
 | ||
|                         {
 | ||
|                             z.Modality,
 | ||
|                             z.SeriesNumber,
 | ||
| 
 | ||
|                             InstancePathList = z.DicomInstanceList.Where(t => t.IsDeleted == true || t.DicomSerie.IsDeleted == true || t.IsReading == false || t.DicomSerie.IsReading == false).Select(k => new
 | ||
|                             {
 | ||
|                                 k.Path,
 | ||
|                                 k.FileSize
 | ||
|                             }).ToList()
 | ||
|                         })
 | ||
| 
 | ||
|                     }).ToList()
 | ||
|                 }).ToList()
 | ||
| 
 | ||
|             }).FirstOrDefault();
 | ||
| 
 | ||
| 
 | ||
|             var filesizes = downloadInfo.VisitList.SelectMany(t => t.StudyList).SelectMany(t => t.SeriesList).SelectMany(t => t.InstancePathList).Sum(t => t.FileSize);
 | ||
|             var count2 = downloadInfo.VisitList.SelectMany(t => t.StudyList).SelectMany(t => t.SeriesList).SelectMany(t => t.InstancePathList).Count();
 | ||
| 
 | ||
|             Console.WriteLine($"下载总数量:{count2},总大小{filesizes}");
 | ||
| 
 | ||
|             if (downloadInfo != null)
 | ||
|             {
 | ||
|                 var downloadJobs = new List<(string Path, Func<Task> Job)>();
 | ||
| 
 | ||
|                 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)
 | ||
|                     {
 | ||
|                         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(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}", $"{studyInfo.StudyCode}_{studyInfo.StudyTime?.ToString("yyyy-MM-dd")}", $"{seriesInfo.SeriesNumber}");
 | ||
| 
 | ||
|                             // 创建 影像 文件夹
 | ||
|                             Directory.CreateDirectory(studyDicomFolderPath);
 | ||
| 
 | ||
|                             // 遍历 InstancePathList
 | ||
|                             foreach (var instanceInfo in seriesInfo.InstancePathList)
 | ||
|                             {
 | ||
|                                 // 复制文件到相应的文件夹
 | ||
|                                 string destinationPath = Path.Combine(studyDicomFolderPath, Path.GetFileName(instanceInfo.Path));
 | ||
| 
 | ||
| 
 | ||
|                                 //加入到下载任务里
 | ||
|                                 downloadJobs.Add((instanceInfo.Path, () => _oSSService.DownLoadFromOSSAsync(instanceInfo.Path, destinationPath)));
 | ||
| 
 | ||
|                                 //下载到当前目录
 | ||
|                                 //await _oSSService.DownLoadFromOSSAsync(instanceInfo.Path, destinationPath);
 | ||
|                             }
 | ||
|                         }
 | ||
| 
 | ||
| 
 | ||
|                     }
 | ||
|                 }
 | ||
| 
 | ||
|                 #region 异步方式处理
 | ||
| 
 | ||
|                 int totalCount = downloadJobs.Count;
 | ||
|                 int downloadedCount = 0;
 | ||
| 
 | ||
|                 // 在 trialFolderPath 下面放一个失败记录文件
 | ||
|                 string failedLogPath = Path.Combine(trialFolderPath, "failed_downloads.txt");
 | ||
| 
 | ||
|                 // 确保文件存在(如果之前有就清空)
 | ||
|                 File.WriteAllText(failedLogPath, "");
 | ||
| 
 | ||
|                 foreach (var job in downloadJobs)
 | ||
|                 {
 | ||
|                     try
 | ||
|                     {
 | ||
|                         await job.Job();
 | ||
|                     }
 | ||
|                     catch (Exception ex)
 | ||
|                     {
 | ||
| 
 | ||
| 
 | ||
|                         string errorMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 下载失败: {job.Path}, 错误: {ex.Message}\r\n";
 | ||
| 
 | ||
|                         Console.WriteLine(errorMessage);
 | ||
| 
 | ||
|                         await File.AppendAllTextAsync(failedLogPath, errorMessage);
 | ||
|                     }
 | ||
| 
 | ||
|                     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();
 | ||
| 
 | ||
| 
 | ||
|         }
 | ||
| 
 | ||
| 
 | ||
|         [AllowAnonymous]
 | ||
|         public async Task<IResponseOutput> ReadDicomDataWriteDB([FromServices] IRepository<DicomInstance> _instanceRepository)
 | ||
|         {
 | ||
|             var testPath = @"E:\WXT001";
 | ||
|             var path = @"E:\WXT001";
 | ||
| 
 | ||
|             var files = Directory.GetFiles(testPath, "*", SearchOption.AllDirectories)
 | ||
|                 // 只要没有后缀(Windows 显示类型是 .file)
 | ||
|                 .Where(f => string.IsNullOrEmpty(Path.GetExtension(f)))
 | ||
|                 .Where(f => Guid.TryParse(Path.GetFileNameWithoutExtension(f), out _))
 | ||
|                 .ToList();
 | ||
| 
 | ||
|             Console.WriteLine($"找到 {files.Count} 个 DICOM 文件");
 | ||
| 
 | ||
|             int total = files.Count;
 | ||
|             int processed = 0;
 | ||
|             double lastPercent = 0;
 | ||
| 
 | ||
|             var options = new ParallelOptions { MaxDegreeOfParallelism = 12 };
 | ||
| 
 | ||
|             // 输出文件路径
 | ||
|             var outputFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info.txt");
 | ||
| 
 | ||
|             var outputErrorFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info_error.txt");
 | ||
| 
 | ||
|             // 用并发安全的写法(锁保护)
 | ||
|             var fileLock = new object();
 | ||
| 
 | ||
|             foreach (var file in files)
 | ||
|             {
 | ||
|                 try
 | ||
|                 {
 | ||
|                     var id = Guid.Parse(Path.GetFileNameWithoutExtension(file));
 | ||
|                     var dicomFile = DicomFile.Open(file);
 | ||
| 
 | ||
|                     var dataset = dicomFile.Dataset;
 | ||
|                     var fileMeta = dicomFile.FileMetaInfo;
 | ||
|                     var syntax = dataset.InternalTransferSyntax;
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|                     //单位  设备   PatientId Visit   检查UId  帧数  
 | ||
| 
 | ||
|                     var stationName = dataset.GetSingleValueOrDefault(DicomTag.StationName, string.Empty);
 | ||
| 
 | ||
|                     var institutionName = dataset.GetSingleValueOrDefault(DicomTag.InstitutionName, string.Empty);
 | ||
| 
 | ||
|                     var manufacturer = dataset.GetSingleValueOrDefault(DicomTag.Manufacturer, string.Empty);
 | ||
| 
 | ||
|                     //PatientID TrialCode_SubjectCode
 | ||
|                     //var patientID = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
 | ||
| 
 | ||
|                     //SubjectCode
 | ||
|                     var clinicalTrialSubjectID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialSubjectID, string.Empty);
 | ||
|                     //访视visitNum
 | ||
|                     var clinicalTrialTimePointID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialTimePointID, string.Empty);
 | ||
|                     var studyInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty);
 | ||
|                     var seriesInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty);
 | ||
|                     var sOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty);
 | ||
| 
 | ||
|                     var numberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1);
 | ||
| 
 | ||
| 
 | ||
|                     // 传输语法 
 | ||
|                     var transferSyntaxUID = fileMeta.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty);
 | ||
|                     var sOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty);
 | ||
|                     var mediaStorageSOPClassUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty);
 | ||
| 
 | ||
|                     var mediaStorageSOPInstanceUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty);
 | ||
| 
 | ||
| 
 | ||
|                     // 拼接一行 CSV 格式
 | ||
|                     var line = string.Join(",",
 | ||
|                         id,
 | ||
|                         stationName,
 | ||
|                         institutionName,
 | ||
|                         manufacturer,
 | ||
|                         clinicalTrialSubjectID,
 | ||
|                         clinicalTrialTimePointID,
 | ||
|                         studyInstanceUID,
 | ||
|                         seriesInstanceUID,
 | ||
|                         sOPInstanceUID,
 | ||
|                         numberOfFrames,
 | ||
|                         transferSyntaxUID,
 | ||
|                         sOPClassUID,
 | ||
|                         mediaStorageSOPClassUID,
 | ||
|                         mediaStorageSOPInstanceUID
 | ||
| 
 | ||
|                     );
 | ||
| 
 | ||
| 
 | ||
|                     await File.AppendAllTextAsync(outputFile, line + Environment.NewLine);
 | ||
| 
 | ||
|                     await _instanceRepository.BatchUpdateNoTrackingAsync(
 | ||
|                         t => t.Id == id,
 | ||
|                         t => new DicomInstance
 | ||
|                         {
 | ||
|                             IsEncapsulated = syntax.IsEncapsulated,
 | ||
|                             TransferSytaxUID = transferSyntaxUID,
 | ||
|                             MediaStorageSOPClassUID = mediaStorageSOPClassUID,
 | ||
|                             SOPClassUID = sOPClassUID,
 | ||
|                             MediaStorageSOPInstanceUID = mediaStorageSOPInstanceUID
 | ||
|                         }, false);
 | ||
|                 }
 | ||
|                 catch (Exception ex)
 | ||
|                 {
 | ||
|                     var errorMsg = $"{DateTime.Now}❌ {file} 解析失败: {ex.Message}";
 | ||
|                     Console.WriteLine(errorMsg);
 | ||
| 
 | ||
|                     await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine);
 | ||
|                 }
 | ||
|                 finally
 | ||
|                 {
 | ||
|                     var done = Interlocked.Increment(ref processed);
 | ||
|                     double percent = done * 100.0 / total;
 | ||
| 
 | ||
|                     // 只在进度提升 >= 1% 时打印
 | ||
|                     if (percent - lastPercent >= 5.0 || done == total)
 | ||
|                     {
 | ||
|                         lastPercent = percent;
 | ||
|                         Console.WriteLine($"{DateTime.Now}   进度: {done}/{total} ({percent:F2}%)");
 | ||
|                     }
 | ||
|                 }
 | ||
| 
 | ||
|             }
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|             return ResponseOutput.Ok();
 | ||
|         }
 | ||
| 
 | ||
|         #region 维护已经下载本地的数据
 | ||
| 
 | ||
|         [AllowAnonymous]
 | ||
|         public async Task<IResponseOutput> ReadExcelData([FromServices] IRepository<DicomInstance> _instanceRepository)
 | ||
|         {
 | ||
|             var rows = await MiniExcel.QueryAsync<DicomSOPInfo>(@"C:\Users\hang\Desktop\维护数据读取.xlsx");
 | ||
| 
 | ||
|             rows = rows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList();
 | ||
| 
 | ||
|             int total = rows.Count();
 | ||
|             int processed = 0;
 | ||
|             double lastPercent = 0;
 | ||
| 
 | ||
|             var outputErrorFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info_error.txt");
 | ||
| 
 | ||
|             foreach (var item in rows)
 | ||
|             {
 | ||
| 
 | ||
| 
 | ||
|                 try
 | ||
|                 {
 | ||
|                     await _instanceRepository.BatchUpdateNoTrackingAsync(
 | ||
|                        t => t.Id == item.InstanceId,
 | ||
|                        t => new DicomInstance
 | ||
|                        {
 | ||
| 
 | ||
|                            IsEncapsulated = item.IsEncapsulated,
 | ||
|                            TransferSytaxUID = item.TransferSyntaxUID,
 | ||
|                            MediaStorageSOPClassUID = item.MediaStorageSOPClassUID,
 | ||
|                            SOPClassUID = item.SOPClassUID,
 | ||
|                            MediaStorageSOPInstanceUID = item.MediaStorageSOPInstanceUID
 | ||
|                        }, false);
 | ||
|                 }
 | ||
|                 catch (Exception ex)
 | ||
|                 {
 | ||
| 
 | ||
|                     var errorMsg = $"{item.InstanceId}  {DateTime.Now}  更新失败: {ex.Message}";
 | ||
|                     Console.WriteLine(errorMsg);
 | ||
| 
 | ||
|                     await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine);
 | ||
|                 }
 | ||
|                 finally
 | ||
|                 {
 | ||
|                     processed++;
 | ||
|                     double percent = processed * 100.0 / total;
 | ||
| 
 | ||
|                     // 每提升 5% 或完成时输出
 | ||
|                     if (percent - lastPercent >= 2.0 || processed == total)
 | ||
|                     {
 | ||
|                         lastPercent = percent;
 | ||
|                         Console.WriteLine($"{DateTime.Now}   进度: {processed}/{total} ({percent:F2}%)");
 | ||
|                     }
 | ||
|                 }
 | ||
| 
 | ||
|             }
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|             return ResponseOutput.Ok();
 | ||
|         }
 | ||
| 
 | ||
|         public class DicomSOPInfo
 | ||
|         {
 | ||
|             public Guid InstanceId { get; set; }
 | ||
| 
 | ||
| 
 | ||
|             public string TransferSyntaxUID { get; set; }
 | ||
| 
 | ||
|             public string SOPClassUID { get; set; }
 | ||
| 
 | ||
|             public string MediaStorageSOPClassUID { get; set; }
 | ||
| 
 | ||
|             public string MediaStorageSOPInstanceUID { get; set; }
 | ||
| 
 | ||
|             public bool IsEncapsulated => DicomTransferSyntax.Lookup(DicomUID.Parse(TransferSyntaxUID)).IsEncapsulated;
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|         }
 | ||
| 
 | ||
| 
 | ||
|         [AllowAnonymous]
 | ||
|         public async Task<IResponseOutput> ReadExcelImageDataInstanceIsReading([FromServices] IRepository<DicomInstance> _instanceRepository,
 | ||
|             [FromServices] IRepository<DicomSeries> _seriesRepository,
 | ||
|              [FromServices] IRepository<DicomStudy> _studyRepository)
 | ||
|         {
 | ||
| 
 | ||
|             var trialId = Guid.Parse("01000000-ac13-0242-6397-08dcd2d2a091");
 | ||
| 
 | ||
|             var rows = await MiniExcel.QueryAsync<DicomSOPInstanceInfo>(@"C:\Users\hang\Desktop\instanceReading.xlsx");
 | ||
| 
 | ||
|             rows = rows.Where(t => !string.IsNullOrEmpty(t.SopInstanceUid) && t.SopInstanceUid.Length > 15).ToList();
 | ||
| 
 | ||
|             var outputErrorFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info_error.txt");
 | ||
| 
 | ||
| 
 | ||
|             //foreach (var batch in rows.Chunk(20))
 | ||
|             //{
 | ||
|             //    var sopUids = batch.Select(x => x.SopInstanceUid).ToList();
 | ||
| 
 | ||
|             //    try
 | ||
|             //    {
 | ||
|             //        await _instanceRepository.BatchUpdateNoTrackingAsync(
 | ||
|             //            t => sopUids.Contains(t.SopInstanceUid) && t.TrialId == trialId,
 | ||
|             //            t => new DicomInstance
 | ||
|             //            {
 | ||
|             //                IsReading = true,
 | ||
|             //                IsDeleted = false
 | ||
|             //            }, false);
 | ||
| 
 | ||
| 
 | ||
|             //        await _seriesRepository.BatchUpdateNoTrackingAsync(
 | ||
|             //           t => t.DicomInstanceList.Any(t => sopUids.Contains(t.SopInstanceUid)) && t.TrialId == trialId,
 | ||
|             //           t => new DicomSeries
 | ||
|             //           {
 | ||
|             //               IsReading = true,
 | ||
|             //               IsDeleted = false
 | ||
|             //           }, false);
 | ||
|             //    }
 | ||
|             //    catch (Exception ex)
 | ||
|             //    {
 | ||
|             //        var errorMsg = $"{string.Join(",", sopUids)}  {DateTime.Now}  批量更新失败: {ex.Message}";
 | ||
|             //        Console.WriteLine(errorMsg);
 | ||
|             //        await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine);
 | ||
|             //    }
 | ||
|             //}
 | ||
| 
 | ||
|             //找到该项目的检查,实时统计数量,并且回更数据库
 | ||
| 
 | ||
|             var studyList = _studyRepository.Where(t => t.TrialId == trialId && (t.SeriesCount != t.SeriesList.Count() || t.InstanceCount != t.InstanceList.Count()))
 | ||
|                 .Select(t => new
 | ||
|                 {
 | ||
|                     t.Id,
 | ||
|                     t.StudyCode,
 | ||
|                     DBSeriesCount = t.SeriesCount,
 | ||
|                     DBInstanceCount = t.InstanceCount,
 | ||
| 
 | ||
|                     ActrualSeriesCount = t.SeriesList.Count(),
 | ||
| 
 | ||
|                     ActrualInstanceCount = t.InstanceList.Count(),
 | ||
| 
 | ||
|                 }).ToList();
 | ||
| 
 | ||
| 
 | ||
|             var seriesList = _seriesRepository.Where(t => t.TrialId == trialId && t.InstanceCount != t.DicomInstanceList.Count())
 | ||
|                 .Select(t => new
 | ||
|                 {
 | ||
|                     SeriesId = t.Id,
 | ||
|                     t.DicomStudy.StudyCode,
 | ||
|                     DBInstanceCount = t.InstanceCount,
 | ||
|                     ActrualInstanceCount = t.DicomInstanceList.Count(),
 | ||
| 
 | ||
|                 }).ToList();
 | ||
| 
 | ||
| 
 | ||
|             await File.AppendAllTextAsync(outputErrorFile, studyList.ToJsonStr() + Environment.NewLine);
 | ||
| 
 | ||
|             await File.AppendAllTextAsync(outputErrorFile, seriesList.ToJsonStr() + Environment.NewLine);
 | ||
| 
 | ||
|             return ResponseOutput.Ok();
 | ||
|         }
 | ||
| 
 | ||
|         public class DicomSOPInstanceInfo
 | ||
|         {
 | ||
|             public string SopInstanceUid { get; set; }
 | ||
| 
 | ||
| 
 | ||
|         }
 | ||
| 
 | ||
|         #endregion
 | ||
| 
 | ||
| 
 | ||
|         #region 通过Excel  读取未下载的,边下载边维护数据
 | ||
| 
 | ||
|         [AllowAnonymous]
 | ||
|         public async Task<IResponseOutput> WriteNeedDealData([FromServices] IRepository<DicomInstance> _instanceRepository)
 | ||
|         {
 | ||
| 
 | ||
|             #region 获取差集数据
 | ||
|             //var rows = await MiniExcel.QueryAsync<DicomSOPInfo>(@"C:\Users\hang\Desktop\维护数据读取.xlsx");
 | ||
| 
 | ||
|             //rows = rows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList();
 | ||
| 
 | ||
|             //var allRows = await MiniExcel.QueryAsync<NeedDealInstanceInfo>(@"C:\Users\hang\Desktop\AllData.xlsx");
 | ||
| 
 | ||
|             //allRows = allRows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList();
 | ||
| 
 | ||
|             //var needDealRows = allRows.Where(t => !rows.Select(c => c.InstanceId).Contains(t.InstanceId)).ToList();
 | ||
| 
 | ||
|             //var outputFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info.txt");
 | ||
| 
 | ||
|             //foreach (var item in needDealRows)
 | ||
|             //{
 | ||
|             //    var line = string.Join(",", item.InstanceId, item.Path);
 | ||
| 
 | ||
|             //    await File.AppendAllTextAsync(outputFile, line + Environment.NewLine);
 | ||
|             //}
 | ||
| 
 | ||
|             #endregion
 | ||
| 
 | ||
|             var folder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment);
 | ||
| 
 | ||
|             var needDealRows = await MiniExcel.QueryAsync<NeedDealInstanceInfo>(Path.Combine(folder, "needDownload.xlsx"));
 | ||
| 
 | ||
|             needDealRows = needDealRows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList();
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|             var outputFile = Path.Combine(folder, $"{Guid.NewGuid()}_dicom_info.txt");
 | ||
| 
 | ||
|             var outputErrorFile = Path.Combine(folder, $"{Guid.NewGuid()}_dicom_info_error.txt");
 | ||
| 
 | ||
|             int total = needDealRows.Count();
 | ||
| 
 | ||
|             Console.WriteLine($"需要处理数量{total}");
 | ||
| 
 | ||
|             int processed = 0;
 | ||
|             double lastPercent = 0;
 | ||
| 
 | ||
|             foreach (var item in needDealRows)
 | ||
|             {
 | ||
| 
 | ||
|                 try
 | ||
|                 {
 | ||
|                     await using var stream = await _oSSService.GetStreamFromOSSAsync(item.Path);
 | ||
| 
 | ||
|                     var dicomFile = DicomFile.Open(stream);
 | ||
| 
 | ||
|                     var dataset = dicomFile.Dataset;
 | ||
|                     var fileMeta = dicomFile.FileMetaInfo;
 | ||
| 
 | ||
|                     var pixelData = DicomPixelData.Create(dicomFile.Dataset);
 | ||
| 
 | ||
|                     //获取像素是否为封装形式
 | ||
|                     var syntax = dicomFile.Dataset.InternalTransferSyntax;
 | ||
| 
 | ||
|                     var stationName = dataset.GetSingleValueOrDefault(DicomTag.StationName, string.Empty);
 | ||
| 
 | ||
|                     var institutionName = dataset.GetSingleValueOrDefault(DicomTag.InstitutionName, string.Empty);
 | ||
| 
 | ||
|                     var manufacturer = dataset.GetSingleValueOrDefault(DicomTag.Manufacturer, string.Empty);
 | ||
| 
 | ||
|                     //PatientID TrialCode_SubjectCode
 | ||
|                     //var patientID = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
 | ||
| 
 | ||
|                     //SubjectCode
 | ||
|                     var clinicalTrialSubjectID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialSubjectID, string.Empty);
 | ||
|                     //访视visitNum
 | ||
|                     var clinicalTrialTimePointID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialTimePointID, string.Empty);
 | ||
|                     var studyInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty);
 | ||
|                     var seriesInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty);
 | ||
|                     var sOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty);
 | ||
| 
 | ||
|                     var numberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1);
 | ||
| 
 | ||
| 
 | ||
|                     // 传输语法 
 | ||
|                     var transferSyntaxUID = fileMeta.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty);
 | ||
|                     var sOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty);
 | ||
|                     var mediaStorageSOPClassUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty);
 | ||
| 
 | ||
|                     var mediaStorageSOPInstanceUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty);
 | ||
| 
 | ||
| 
 | ||
|                     var line = string.Join(",",
 | ||
|                        item.InstanceId,
 | ||
|                        stationName,
 | ||
|                        institutionName,
 | ||
|                        manufacturer,
 | ||
|                        clinicalTrialSubjectID,
 | ||
|                        clinicalTrialTimePointID,
 | ||
|                        studyInstanceUID,
 | ||
|                        seriesInstanceUID,
 | ||
|                        sOPInstanceUID,
 | ||
|                        numberOfFrames,
 | ||
|                        transferSyntaxUID,
 | ||
|                        sOPClassUID,
 | ||
|                        mediaStorageSOPClassUID,
 | ||
|                        mediaStorageSOPInstanceUID
 | ||
| 
 | ||
|                    );
 | ||
| 
 | ||
| 
 | ||
|                     await File.AppendAllTextAsync(outputFile, line + Environment.NewLine);
 | ||
| 
 | ||
|                     //维护序列层级四个字段   后再用sql 维护study series 时间拆分 和 MediaStorageSOPInstanceUID
 | ||
|                     await _instanceRepository.BatchUpdateNoTrackingAsync(
 | ||
|                            t => t.Id == item.InstanceId,
 | ||
|                            t => new DicomInstance
 | ||
|                            {
 | ||
|                                IsEncapsulated = syntax.IsEncapsulated,
 | ||
|                                TransferSytaxUID = transferSyntaxUID,
 | ||
|                                MediaStorageSOPClassUID = mediaStorageSOPClassUID,
 | ||
|                                SOPClassUID = sOPClassUID,
 | ||
|                                MediaStorageSOPInstanceUID = mediaStorageSOPInstanceUID
 | ||
|                            }, false);
 | ||
|                 }
 | ||
|                 catch (Exception ex)
 | ||
|                 {
 | ||
| 
 | ||
|                     var errorMsg = $"{DateTime.Now} ❌ 失败: {ex.Message} | InstanceId={item.InstanceId}, Path={item.Path}";
 | ||
| 
 | ||
|                     Console.WriteLine(errorMsg);
 | ||
| 
 | ||
|                     await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine);
 | ||
|                 }
 | ||
|                 finally
 | ||
|                 {
 | ||
|                     processed++;
 | ||
|                     double percent = processed * 100.0 / total;
 | ||
| 
 | ||
|                     // 每提升 5% 或完成时输出
 | ||
|                     if (percent - lastPercent >= 2.0 || processed == total)
 | ||
|                     {
 | ||
|                         lastPercent = percent;
 | ||
|                         Console.WriteLine($"{DateTime.Now}   进度: {processed}/{total} ({percent:F2}%)");
 | ||
|                     }
 | ||
|                 }
 | ||
| 
 | ||
| 
 | ||
|             }
 | ||
| 
 | ||
|             return ResponseOutput.Ok();
 | ||
|         }
 | ||
| 
 | ||
|         public class NeedDealInstanceInfo
 | ||
|         {
 | ||
|             public Guid InstanceId { get; set; }
 | ||
| 
 | ||
|             public string Path { get; set; }
 | ||
|         }
 | ||
| 
 | ||
|         /// <summary>
 | ||
|         /// 读取该项目的数据,进行维护
 | ||
|         /// </summary>
 | ||
|         /// <param name="_instanceRepository"></param>
 | ||
|         /// <param name="trialId"></param>
 | ||
|         /// <returns></returns>
 | ||
|         [AllowAnonymous]
 | ||
|         public async Task<IResponseOutput> WriteTrialNeedDealData([FromServices] IRepository<DicomInstance> _instanceRepository, Guid trialId)
 | ||
|         {
 | ||
| 
 | ||
|             #region 获取差集数据
 | ||
|             //var rows = await MiniExcel.QueryAsync<DicomSOPInfo>(@"C:\Users\hang\Desktop\维护数据读取.xlsx");
 | ||
| 
 | ||
|             //rows = rows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList();
 | ||
| 
 | ||
|             //var allRows = await MiniExcel.QueryAsync<NeedDealInstanceInfo>(@"C:\Users\hang\Desktop\AllData.xlsx");
 | ||
| 
 | ||
|             //allRows = allRows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList();
 | ||
| 
 | ||
|             //var needDealRows = allRows.Where(t => !rows.Select(c => c.InstanceId).Contains(t.InstanceId)).ToList();
 | ||
| 
 | ||
|             //var outputFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info.txt");
 | ||
| 
 | ||
|             //foreach (var item in needDealRows)
 | ||
|             //{
 | ||
|             //    var line = string.Join(",", item.InstanceId, item.Path);
 | ||
| 
 | ||
|             //    await File.AppendAllTextAsync(outputFile, line + Environment.NewLine);
 | ||
|             //}
 | ||
| 
 | ||
|             #endregion
 | ||
| 
 | ||
|             var folder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment);
 | ||
| 
 | ||
|             var needDealRows = _instanceRepository.Where(t => t.TrialId == trialId && t.TransferSytaxUID == "").Select(t => new NeedDealInstanceInfo() { InstanceId = t.Id, Path = t.Path });
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|             var outputFile = Path.Combine(folder, $"{Guid.NewGuid()}_dicom_info.txt");
 | ||
| 
 | ||
|             var outputErrorFile = Path.Combine(folder, $"{Guid.NewGuid()}_dicom_info_error.txt");
 | ||
| 
 | ||
|             int total = needDealRows.Count();
 | ||
| 
 | ||
|             Console.WriteLine($"需要处理数量{total}");
 | ||
| 
 | ||
|             int processed = 0;
 | ||
|             double lastPercent = 0;
 | ||
| 
 | ||
|             foreach (var item in needDealRows)
 | ||
|             {
 | ||
| 
 | ||
|                 try
 | ||
|                 {
 | ||
|                     await using var stream = await _oSSService.GetStreamFromOSSAsync(item.Path);
 | ||
| 
 | ||
|                     var dicomFile = DicomFile.Open(stream);
 | ||
| 
 | ||
|                     var dataset = dicomFile.Dataset;
 | ||
|                     var fileMeta = dicomFile.FileMetaInfo;
 | ||
| 
 | ||
|                     var pixelData = DicomPixelData.Create(dicomFile.Dataset);
 | ||
| 
 | ||
|                     //获取像素是否为封装形式
 | ||
|                     var syntax = dicomFile.Dataset.InternalTransferSyntax;
 | ||
| 
 | ||
|                     var stationName = dataset.GetSingleValueOrDefault(DicomTag.StationName, string.Empty);
 | ||
| 
 | ||
|                     var institutionName = dataset.GetSingleValueOrDefault(DicomTag.InstitutionName, string.Empty);
 | ||
| 
 | ||
|                     var manufacturer = dataset.GetSingleValueOrDefault(DicomTag.Manufacturer, string.Empty);
 | ||
| 
 | ||
|                     //PatientID TrialCode_SubjectCode
 | ||
|                     //var patientID = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
 | ||
| 
 | ||
|                     //SubjectCode
 | ||
|                     var clinicalTrialSubjectID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialSubjectID, string.Empty);
 | ||
|                     //访视visitNum
 | ||
|                     var clinicalTrialTimePointID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialTimePointID, string.Empty);
 | ||
|                     var studyInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty);
 | ||
|                     var seriesInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty);
 | ||
|                     var sOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty);
 | ||
| 
 | ||
|                     var numberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1);
 | ||
| 
 | ||
| 
 | ||
|                     // 传输语法 
 | ||
|                     var transferSyntaxUID = fileMeta.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty);
 | ||
|                     var sOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty);
 | ||
|                     var mediaStorageSOPClassUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty);
 | ||
| 
 | ||
|                     var mediaStorageSOPInstanceUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty);
 | ||
| 
 | ||
| 
 | ||
|                     var line = string.Join(",",
 | ||
|                        item.InstanceId,
 | ||
|                        stationName,
 | ||
|                        institutionName,
 | ||
|                        manufacturer,
 | ||
|                        clinicalTrialSubjectID,
 | ||
|                        clinicalTrialTimePointID,
 | ||
|                        studyInstanceUID,
 | ||
|                        seriesInstanceUID,
 | ||
|                        sOPInstanceUID,
 | ||
|                        numberOfFrames,
 | ||
|                        transferSyntaxUID,
 | ||
|                        sOPClassUID,
 | ||
|                        mediaStorageSOPClassUID,
 | ||
|                        mediaStorageSOPInstanceUID
 | ||
| 
 | ||
|                    );
 | ||
| 
 | ||
| 
 | ||
|                     await File.AppendAllTextAsync(outputFile, line + Environment.NewLine);
 | ||
| 
 | ||
|                     //维护序列层级四个字段   后再用sql 维护study series 时间拆分 和 MediaStorageSOPInstanceUID
 | ||
|                     await _instanceRepository.BatchUpdateNoTrackingAsync(
 | ||
|                            t => t.Id == item.InstanceId,
 | ||
|                            t => new DicomInstance
 | ||
|                            {
 | ||
|                                IsEncapsulated = syntax.IsEncapsulated,
 | ||
|                                TransferSytaxUID = transferSyntaxUID,
 | ||
|                                MediaStorageSOPClassUID = mediaStorageSOPClassUID,
 | ||
|                                SOPClassUID = sOPClassUID,
 | ||
|                                MediaStorageSOPInstanceUID = mediaStorageSOPInstanceUID
 | ||
|                            }, false);
 | ||
|                 }
 | ||
|                 catch (Exception ex)
 | ||
|                 {
 | ||
| 
 | ||
|                     var errorMsg = $"{DateTime.Now} ❌ 失败: {ex.Message} | InstanceId={item.InstanceId}, Path={item.Path}";
 | ||
| 
 | ||
|                     Console.WriteLine(errorMsg);
 | ||
| 
 | ||
|                     await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine);
 | ||
|                 }
 | ||
|                 finally
 | ||
|                 {
 | ||
|                     processed++;
 | ||
|                     double percent = processed * 100.0 / total;
 | ||
| 
 | ||
|                     // 每提升 5% 或完成时输出
 | ||
|                     if (percent - lastPercent >= 2.0 || processed == total)
 | ||
|                     {
 | ||
|                         lastPercent = percent;
 | ||
|                         Console.WriteLine($"{DateTime.Now}   进度: {processed}/{total} ({percent:F2}%)");
 | ||
|                     }
 | ||
|                 }
 | ||
| 
 | ||
| 
 | ||
|             }
 | ||
| 
 | ||
|             return ResponseOutput.Ok();
 | ||
|         }
 | ||
|         #endregion
 | ||
| 
 | ||
| 
 | ||
|         #region oss 下载删除影像 ,并且恢复数据
 | ||
|         [AllowAnonymous]
 | ||
|         public async Task<IResponseOutput> RestoreDBOSSDate(
 | ||
|          [FromServices] IOSSService _oSSService, [FromServices] IWebHostEnvironment _hostEnvironment, [FromServices] IRepository<DicomStudy> _studyRepository)
 | ||
|         {
 | ||
|             var folder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment);
 | ||
| 
 | ||
|             var outputFile = Path.Combine(folder, $"{Guid.NewGuid()}_deleteKey_info.txt");
 | ||
|             var outputFile2 = Path.Combine(folder, $"{Guid.NewGuid()}_deleteKeyExport_info.txt");
 | ||
| 
 | ||
|             var outputErrorFile = Path.Combine(folder, $"{Guid.NewGuid()}_deleteKeyerror.txt");
 | ||
|             var outputErrorFile2 = Path.Combine(folder, $"{Guid.NewGuid()}_deleteKeyerrorStudy.txt");
 | ||
| 
 | ||
| 
 | ||
|             var aliConfig = _oSSService.ObjectStoreServiceOptions.AliyunOSS;
 | ||
| 
 | ||
|             var tempToken = _oSSService.GetObjectStoreTempToken();
 | ||
| 
 | ||
|             var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
 | ||
|                tempToken.AliyunOSS.AccessKeyId,
 | ||
|                 tempToken.AliyunOSS.AccessKeySecret,
 | ||
|                 tempToken.AliyunOSS.SecurityToken);
 | ||
| 
 | ||
| 
 | ||
|             var allVersions = new List<ObjectVersionSummary>();
 | ||
|             var allDeleteMarkers = new List<DeleteMarkerSummary>();
 | ||
| 
 | ||
|             var request = new ListObjectVersionsRequest(tempToken.AliyunOSS.BucketName)
 | ||
|             {
 | ||
|                 Prefix = "01000000-ac13-0242-6397-08dcd2d2a091/Image",
 | ||
|                 //Prefix = "01000000-ac13-0242-6397-08dcd2d2a091/Image/08dd9c04-c1b2-c2da-0242-ac1301000000/01000000-ac13-0242-235b-08dd9c04c1b3",
 | ||
|                 MaxKeys = 1000,
 | ||
|             };
 | ||
| 
 | ||
|             ObjectVersionList result;
 | ||
|             do
 | ||
|             {
 | ||
| 
 | ||
|                 result = _ossClient.ListObjectVersions(request);
 | ||
| 
 | ||
|                 if (result.ObjectVersionSummaries != null)
 | ||
|                     allVersions.AddRange(result.ObjectVersionSummaries);
 | ||
| 
 | ||
|                 if (result.DeleteMarkerSummaries != null)
 | ||
|                     allDeleteMarkers.AddRange(result.DeleteMarkerSummaries);
 | ||
| 
 | ||
|                 request.KeyMarker = result.NextKeyMarker;
 | ||
|                 request.VersionIdMarker = result.NextVersionIdMarker;
 | ||
| 
 | ||
|             } while (result.IsTruncated);
 | ||
| 
 | ||
|             Console.WriteLine($"共找到 {allDeleteMarkers.Count} 个删除标记");
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|             int total = allDeleteMarkers.Count;
 | ||
| 
 | ||
|             int processed = 0;
 | ||
|             double lastPercent = 0;
 | ||
| 
 | ||
| 
 | ||
|             // 按 Key 分组,找每个删除标记前的最近版本
 | ||
|             var versionsByKey = allVersions
 | ||
|                 .GroupBy(v => v.Key)
 | ||
|                 .ToDictionary(g => g.Key, g => g.OrderByDescending(x => x.LastModified).ToList());
 | ||
| 
 | ||
|             foreach (var del in allDeleteMarkers)
 | ||
|             {
 | ||
|                 #region 防止阿里云过期
 | ||
|                 if (tempToken.AliyunOSS.Expiration.AddSeconds(10) <= DateTime.Now)
 | ||
|                 {
 | ||
|                     tempToken = _oSSService.GetObjectStoreTempToken();
 | ||
| 
 | ||
|                     _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
 | ||
|                       tempToken.AliyunOSS.AccessKeyId,
 | ||
|                        tempToken.AliyunOSS.AccessKeySecret,
 | ||
|                        tempToken.AliyunOSS.SecurityToken);
 | ||
|                 }
 | ||
| 
 | ||
|                 #endregion
 | ||
| 
 | ||
|                 if (!versionsByKey.TryGetValue(del.Key, out var versions))
 | ||
|                     continue; // 没有历史版本无法恢复
 | ||
| 
 | ||
|                 var prevVersion = versions.FirstOrDefault(v => v.LastModified < del.LastModified);
 | ||
|                 if (prevVersion == null)
 | ||
|                     continue; // 没找到可恢复版本
 | ||
| 
 | ||
|                 if (Path.GetExtension(prevVersion.Key).IsNotNullOrEmpty())
 | ||
|                 {
 | ||
|                     continue;//不是dicom 文件
 | ||
|                 }
 | ||
| 
 | ||
| 
 | ||
|                 try
 | ||
|                 {
 | ||
|                     //await File.AppendAllTextAsync(outputFile, $"{prevVersion.Key},{prevVersion.VersionId}" + Environment.NewLine);
 | ||
| 
 | ||
|                     var getReq = new GetObjectRequest(tempToken.AliyunOSS.BucketName, prevVersion.Key)
 | ||
|                     {
 | ||
|                         VersionId = prevVersion.VersionId
 | ||
|                     };
 | ||
| 
 | ||
|                     using (var getResult = _ossClient.GetObject(getReq))
 | ||
|                     using (var memStream = new MemoryStream())
 | ||
|                     {
 | ||
|                         // 先把 OSS 流复制到内存流
 | ||
|                         getResult.Content.CopyTo(memStream);
 | ||
|                         memStream.Position = 0;
 | ||
| 
 | ||
|                         // 读取 DICOM 信息
 | ||
|                         var dicomFile = DicomFile.Open(memStream);
 | ||
|                         var studyInstanceUID = dicomFile.Dataset.GetString(DicomTag.StudyInstanceUID);
 | ||
| 
 | ||
|                         var findInfo = await _studyRepository.Where(t => t.StudyInstanceUid == studyInstanceUID && t.TrialId == Guid.Parse("01000000-ac13-0242-6397-08dcd2d2a091"))
 | ||
|                               .Select(t => new { t.StudyInstanceUid, t.Subject.Code, t.SubjectVisit.VisitName, t.SubjectId, t.SubjectVisitId }).FirstOrDefaultAsync();
 | ||
| 
 | ||
|                         if (findInfo != null)
 | ||
|                         {
 | ||
| 
 | ||
|                             // 再保存到另一个路径(可以使用 fo-dicom 保存)
 | ||
| 
 | ||
|                             var fileName = Path.GetFileNameWithoutExtension(prevVersion.Key);
 | ||
|                             var anotherPath = Path.Combine(folder, findInfo.Code, findInfo.VisitName, studyInstanceUID, fileName);
 | ||
|                             // 去掉 folder 部分,得到相对路径
 | ||
|                             var relativePath = Path.GetRelativePath(folder, anotherPath);
 | ||
|                             Directory.CreateDirectory(Path.GetDirectoryName(anotherPath));
 | ||
|                             dicomFile.Save(anotherPath);
 | ||
| 
 | ||
|                             await File.AppendAllTextAsync(outputFile2, $"{findInfo.SubjectId},{findInfo.SubjectVisitId},{prevVersion.Key},{prevVersion.VersionId},{relativePath},{findInfo.Code},{findInfo.VisitName},{findInfo.StudyInstanceUid},{fileName}" + Environment.NewLine);
 | ||
|                         }
 | ||
|                         else
 | ||
|                         {
 | ||
|                             await File.AppendAllTextAsync(outputErrorFile2, $"{studyInstanceUID},{prevVersion.Key},{prevVersion.VersionId}" + Environment.NewLine);
 | ||
|                         }
 | ||
| 
 | ||
|                         //Console.WriteLine($"读取到 studyInstanceUID: {studyInstanceUID}");
 | ||
| 
 | ||
|                         //var localPath = Path.Combine(folder, prevVersion.Key.Trim('/').Replace('/', Path.DirectorySeparatorChar));
 | ||
|                         //Directory.CreateDirectory(Path.GetDirectoryName(localPath));
 | ||
|                         //// 保存到原本路径
 | ||
|                         //memStream.Position = 0;
 | ||
|                         //using (var fs = File.Create(localPath))
 | ||
|                         //{
 | ||
|                         //    memStream.CopyTo(fs);
 | ||
|                         //}
 | ||
| 
 | ||
| 
 | ||
|                     }
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|                     //Console.WriteLine($"✅ 下载成功: {prevVersion.Key} (version={prevVersion.VersionId})");
 | ||
|                 }
 | ||
|                 catch (Exception ex)
 | ||
|                 {
 | ||
|                     Console.WriteLine($"❌ 下载失败: {prevVersion.Key}, 错误: {ex.Message}");
 | ||
| 
 | ||
|                     await File.AppendAllTextAsync(outputErrorFile, $"{prevVersion.Key},{prevVersion.VersionId}" + Environment.NewLine);
 | ||
|                 }
 | ||
|                 finally
 | ||
|                 {
 | ||
|                     processed++;
 | ||
|                     double percent = processed * 100.0 / total;
 | ||
| 
 | ||
|                     // 每提升 5% 或完成时输出
 | ||
|                     if (percent - lastPercent >= 2.0 || processed == total)
 | ||
|                     {
 | ||
|                         lastPercent = percent;
 | ||
|                         Console.WriteLine($"{DateTime.Now}   进度: {processed}/{total} ({percent:F2}%)");
 | ||
|                     }
 | ||
|                 }
 | ||
| 
 | ||
| 
 | ||
|                 // 使用 CopyObject 把历史版本拷贝为最新版本(恢复)
 | ||
|                 //var copyReq = new CopyObjectRequest
 | ||
|                 //{
 | ||
|                 //    Bucket = bucketName,
 | ||
|                 //    Key = prevVersion.Key,
 | ||
|                 //    SourceBucket = bucketName,
 | ||
|                 //    SourceKey = prevVersion.Key,
 | ||
|                 //    SourceVersionId = prevVersion.VersionId
 | ||
|                 //};
 | ||
| 
 | ||
|                 //try
 | ||
|                 //{
 | ||
|                 //    var copyResult = client.CopyObject(copyReq);
 | ||
|                 //    Console.WriteLine($"✅ 恢复成功: {prevVersion.Key} -> newVersionId={copyResult.VersionId}");
 | ||
|                 //}
 | ||
|                 //catch (Exception ex)
 | ||
|                 //{
 | ||
|                 //    Console.WriteLine($"❌ 恢复失败: {prevVersion.Key}, 错误: {ex.Message}");
 | ||
|                 //}
 | ||
|             }
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|             return ResponseOutput.Ok();
 | ||
|         }
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|         public async Task<IResponseOutput> OSSDeleteReStorre([FromServices] IOSSService _oSSService, [FromServices] IWebHostEnvironment _hostEnvironment)
 | ||
|         {
 | ||
|             var aliConfig = _oSSService.ObjectStoreServiceOptions.AliyunOSS;
 | ||
| 
 | ||
|             var tempToken = _oSSService.GetObjectStoreTempToken();
 | ||
| 
 | ||
|             var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
 | ||
|                tempToken.AliyunOSS.AccessKeyId,
 | ||
|                 tempToken.AliyunOSS.AccessKeySecret,
 | ||
|                 tempToken.AliyunOSS.SecurityToken);
 | ||
| 
 | ||
| 
 | ||
|             var allVersions = new List<ObjectVersionSummary>();
 | ||
|             var allDeleteMarkers = new List<DeleteMarkerSummary>();
 | ||
| 
 | ||
|             var request = new ListObjectVersionsRequest(tempToken.AliyunOSS.BucketName)
 | ||
|             {
 | ||
|                 Prefix = "test-delete-restore",
 | ||
|                 //Prefix = "01000000-ac13-0242-6397-08dcd2d2a091/Image/08dd9c04-c1b2-c2da-0242-ac1301000000/01000000-ac13-0242-235b-08dd9c04c1b3",
 | ||
|                 MaxKeys = 1000,
 | ||
|             };
 | ||
| 
 | ||
|             ObjectVersionList result;
 | ||
|             do
 | ||
|             {
 | ||
| 
 | ||
|                 result = _ossClient.ListObjectVersions(request);
 | ||
| 
 | ||
|                 if (result.ObjectVersionSummaries != null)
 | ||
|                     allVersions.AddRange(result.ObjectVersionSummaries);
 | ||
| 
 | ||
|                 if (result.DeleteMarkerSummaries != null)
 | ||
|                     allDeleteMarkers.AddRange(result.DeleteMarkerSummaries);
 | ||
| 
 | ||
|                 request.KeyMarker = result.NextKeyMarker;
 | ||
|                 request.VersionIdMarker = result.NextVersionIdMarker;
 | ||
| 
 | ||
|             } while (result.IsTruncated);
 | ||
| 
 | ||
|             Console.WriteLine($"共找到 {allDeleteMarkers.Count} 个删除标记");
 | ||
| 
 | ||
|             var versionsByKey = allVersions
 | ||
|               .GroupBy(v => v.Key)
 | ||
|               .ToDictionary(g => g.Key, g => g.OrderByDescending(x => x.LastModified).ToList());
 | ||
| 
 | ||
| 
 | ||
|             foreach (var del in allDeleteMarkers)
 | ||
|             {
 | ||
|                 #region 防止阿里云过期
 | ||
|                 if (tempToken.AliyunOSS.Expiration.AddSeconds(10) <= DateTime.Now)
 | ||
|                 {
 | ||
|                     tempToken = _oSSService.GetObjectStoreTempToken();
 | ||
| 
 | ||
|                     _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
 | ||
|                       tempToken.AliyunOSS.AccessKeyId,
 | ||
|                        tempToken.AliyunOSS.AccessKeySecret,
 | ||
|                        tempToken.AliyunOSS.SecurityToken);
 | ||
|                 }
 | ||
| 
 | ||
|                 #endregion
 | ||
| 
 | ||
|                 if (!versionsByKey.TryGetValue(del.Key, out var versions))
 | ||
|                     continue; // 没有历史版本无法恢复
 | ||
| 
 | ||
|                 var prevVersion = versions.FirstOrDefault(v => v.LastModified < del.LastModified);
 | ||
|                 if (prevVersion == null)
 | ||
|                     continue; // 没找到可恢复版本
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|                 // 创建 CopyObject 请求
 | ||
|                 // 先用构造函数指定源和目标
 | ||
|                 var copyReq = new CopyObjectRequest(
 | ||
|                     sourceBucketName: tempToken.AliyunOSS.BucketName,
 | ||
|                     sourceKey: prevVersion.Key,
 | ||
|                     destinationBucketName: tempToken.AliyunOSS.BucketName,
 | ||
|                     destinationKey: prevVersion.Key  // 覆盖到同名 Key,达到“恢复”的效果
 | ||
|                 );
 | ||
| 
 | ||
|                 // 再设置版本号
 | ||
|                 copyReq.SourceVersionId = prevVersion.VersionId;
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|                 try
 | ||
|                 {
 | ||
|                     var copyResult = _ossClient.CopyObject(copyReq);
 | ||
|                     Console.WriteLine($"✅ 恢复成功: {prevVersion.Key}, 新版本ID={copyResult.VersionId}");
 | ||
|                 }
 | ||
|                 catch (Exception ex)
 | ||
|                 {
 | ||
|                     Console.WriteLine($"❌ 恢复失败: {prevVersion.Key}, 错误: {ex.Message}");
 | ||
|                 }
 | ||
| 
 | ||
| 
 | ||
|             }
 | ||
| 
 | ||
|             return ResponseOutput.Ok();
 | ||
| 
 | ||
| 
 | ||
|         }
 | ||
| 
 | ||
|         /// <summary>
 | ||
|         /// 读取excel  恢复oss  数据,读取dicom 恢复序列和Instance
 | ||
|         /// </summary>
 | ||
|         /// <param name="_oSSService"></param>
 | ||
|         /// <param name="_hostEnvironment"></param>
 | ||
|         /// <returns></returns>
 | ||
|         [AllowAnonymous]
 | ||
|         public async Task<IResponseOutput> ReadExcelReStorreOSSDeleteDataAndDBData([FromServices] IOSSService _oSSService, [FromServices] IWebHostEnvironment _hostEnvironment, string subjectCode)
 | ||
|         {
 | ||
|             var folder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment);
 | ||
| 
 | ||
|             var trialId = Guid.Parse("01000000-ac13-0242-6397-08dcd2d2a091");
 | ||
| 
 | ||
|             var rows = await MiniExcel.QueryAsync<RestoreOSSDeleteDataDTO>(Path.Combine(folder, "删除恢复.xlsx"));
 | ||
| 
 | ||
|             var restoreRows = await MiniExcel.QueryAsync<RestoreInstanceInfo>(Path.Combine(folder, "删除需要恢复的数据.xlsx"));
 | ||
| 
 | ||
|             rows = rows.Where(t => t.Key.IsNotNullOrEmpty()).WhereIf(subjectCode.IsNotNullOrEmpty(), t => t.SubjectCode == subjectCode.TrimStart('0')).ToList();
 | ||
| 
 | ||
|             restoreRows = restoreRows.Where(t => t.SOPInstanceUID.IsNotNullOrEmpty()).WhereIf(subjectCode.IsNotNullOrEmpty(), t => t.SubjectCode == subjectCode).ToList();
 | ||
| 
 | ||
|             Console.WriteLine($"恢复数量: {restoreRows.Count()}");
 | ||
| 
 | ||
|             int total = rows.Count();
 | ||
|             int processed = 0;
 | ||
|             double lastPercent = 0;
 | ||
| 
 | ||
|             var outputErrorFile = Path.Combine(folder, $"{Guid.NewGuid()}_dicom_info_error.txt");
 | ||
| 
 | ||
|             var aliConfig = _oSSService.ObjectStoreServiceOptions.AliyunOSS;
 | ||
| 
 | ||
|             var tempToken = _oSSService.GetObjectStoreTempToken();
 | ||
| 
 | ||
|             var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
 | ||
|                tempToken.AliyunOSS.AccessKeyId,
 | ||
|                 tempToken.AliyunOSS.AccessKeySecret,
 | ||
|                 tempToken.AliyunOSS.SecurityToken);
 | ||
| 
 | ||
| 
 | ||
|             var restoreCount = 0;
 | ||
|             foreach (var item in rows)
 | ||
|             {
 | ||
|                 //不包含该subject的忽略
 | ||
|                 if (!restoreRows.Any(t => t.SubjectCode.TrimStart('0') == item.SubjectCode))
 | ||
|                 {
 | ||
|                     continue;
 | ||
|                 }
 | ||
| 
 | ||
|                 try
 | ||
|                 {
 | ||
| 
 | ||
|                     //根据本地文件匹配studyInstanceUid 不匹配忽略
 | ||
| 
 | ||
|                     var localPath = Path.Combine(folder, item.RelativePath);
 | ||
| 
 | ||
| 
 | ||
|                     #region 读取本地dicom ,判断数据库是否存在该序列 
 | ||
| 
 | ||
|                     var dicomFilePath = Path.Combine(folder, item.RelativePath);
 | ||
| 
 | ||
| 
 | ||
|                     var dicomFile = await DicomFile.OpenAsync(dicomFilePath);
 | ||
| 
 | ||
|                     string sopInstanceUid = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty);
 | ||
| 
 | ||
| 
 | ||
|                     if (!restoreRows.Any(t => t.SOPInstanceUID == sopInstanceUid))
 | ||
|                     {
 | ||
|                         continue;
 | ||
|                     }
 | ||
| 
 | ||
|                     restoreCount++;
 | ||
| 
 | ||
|                     Console.WriteLine($"恢复SOPInstanceUID{sopInstanceUid},当前数量{restoreCount}");
 | ||
| 
 | ||
|                     await ArchiveDicomFileAsync(dicomFile.Dataset, trialId, item.SubjectId, item.SubjectVisitId);
 | ||
| 
 | ||
|                     #endregion
 | ||
| 
 | ||
| 
 | ||
|                     #region 防止阿里云过期
 | ||
|                     if (tempToken.AliyunOSS.Expiration.AddSeconds(10) <= DateTime.Now)
 | ||
|                     {
 | ||
|                         tempToken = _oSSService.GetObjectStoreTempToken();
 | ||
| 
 | ||
|                         _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
 | ||
|                           tempToken.AliyunOSS.AccessKeyId,
 | ||
|                            tempToken.AliyunOSS.AccessKeySecret,
 | ||
|                            tempToken.AliyunOSS.SecurityToken);
 | ||
|                     }
 | ||
| 
 | ||
|                     #endregion
 | ||
| 
 | ||
| 
 | ||
|                     // 创建 CopyObject 请求
 | ||
|                     // 先用构造函数指定源和目标
 | ||
|                     var copyReq = new CopyObjectRequest(
 | ||
|                         sourceBucketName: tempToken.AliyunOSS.BucketName,
 | ||
|                         sourceKey: item.Key,
 | ||
|                         destinationBucketName: tempToken.AliyunOSS.BucketName,
 | ||
|                         destinationKey: item.Key  // 覆盖到同名 Key,达到“恢复”的效果
 | ||
|                     );
 | ||
| 
 | ||
|                     // 再设置版本号
 | ||
|                     copyReq.SourceVersionId = item.VersionId;
 | ||
| 
 | ||
|                     var copyResult = _ossClient.CopyObject(copyReq);
 | ||
| 
 | ||
|                 }
 | ||
| 
 | ||
|                 catch (Exception ex)
 | ||
|                 {
 | ||
| 
 | ||
|                     var errorMsg = $"❌ 恢复失败: {item.Key}, 错误: {ex.Message}";
 | ||
|                     Console.WriteLine(errorMsg);
 | ||
| 
 | ||
|                     await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine);
 | ||
|                 }
 | ||
|                 finally
 | ||
|                 {
 | ||
|                     processed++;
 | ||
|                     double percent = processed * 100.0 / total;
 | ||
| 
 | ||
|                     if (percent - lastPercent >= 2.0 || processed == total)
 | ||
|                     {
 | ||
|                         lastPercent = percent;
 | ||
|                         Console.WriteLine($"{DateTime.Now}   进度: {processed}/{total} ({percent:F2}%)");
 | ||
|                     }
 | ||
|                 }
 | ||
| 
 | ||
|             }
 | ||
| 
 | ||
| 
 | ||
|             await _studyRepository.SaveChangesAsync();
 | ||
| 
 | ||
|             return ResponseOutput.Ok();
 | ||
|         }
 | ||
| 
 | ||
|         public class RestoreOSSDeleteDataDTO
 | ||
|         {
 | ||
|             public Guid SubjectId { get; set; }
 | ||
| 
 | ||
|             public Guid SubjectVisitId { get; set; }
 | ||
| 
 | ||
|             public string Key { get; set; }
 | ||
| 
 | ||
|             public string VersionId { get; set; }
 | ||
| 
 | ||
|             public string RelativePath { get; set; }
 | ||
| 
 | ||
|             public string SubjectCode { get; set; }
 | ||
|             public string FileName { get; set; }
 | ||
| 
 | ||
|             #endregion
 | ||
|         }
 | ||
| 
 | ||
|         public class RestoreInstanceInfo
 | ||
|         {
 | ||
|             public string SubjectCode { get; set; }
 | ||
| 
 | ||
|             public string VisitName { get; set; }
 | ||
| 
 | ||
|             public string SOPInstanceUID { get; set; }
 | ||
|         }
 | ||
| 
 | ||
|         /// <summary>
 | ||
|         /// 单个文件接收 归档
 | ||
|         /// </summary>
 | ||
|         /// <param name="dataset"></param>
 | ||
|         /// <param name="trialId"></param>
 | ||
|         /// <param name="subjectId"></param>
 | ||
|         /// <param name="subjectVisitId"></param>
 | ||
|         /// <returns></returns>
 | ||
|         /// <exception cref="NotImplementedException"></exception>
 | ||
|         public async Task ArchiveDicomFileAsync(DicomDataset dataset, Guid trialId, Guid subjectId, Guid subjectVisitId)
 | ||
|         {
 | ||
|             string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID);
 | ||
|             string seriesInstanceUid = dataset.GetString(DicomTag.SeriesInstanceUID);
 | ||
|             string sopInstanceUid = dataset.GetString(DicomTag.SOPInstanceUID);
 | ||
| 
 | ||
|             Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, trialId.ToString());
 | ||
|             Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, trialId.ToString());
 | ||
|             Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, trialId.ToString());
 | ||
| 
 | ||
|             var isSeriesNeedAdd = false;
 | ||
|             var isInstanceNeedAdd = false;
 | ||
| 
 | ||
| 
 | ||
|             var findStudy = await _studyRepository.FirstOrDefaultAsync(t => t.Id == studyId);
 | ||
|             var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
 | ||
|             var findInstance = await _instanceRepository.FirstOrDefaultAsync(t => t.Id == instanceId);
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|             if (findSerice == null)
 | ||
|             {
 | ||
|                 isSeriesNeedAdd = true;
 | ||
| 
 | ||
|                 findSerice = new DicomSeries
 | ||
|                 {
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|                     Id = seriesId,
 | ||
|                     StudyId = findStudy.Id,
 | ||
| 
 | ||
|                     StudyInstanceUid = findStudy.StudyInstanceUid,
 | ||
|                     SeriesInstanceUid = seriesInstanceUid,
 | ||
|                     SeriesNumber = dataset.GetSingleValueOrDefault(DicomTag.SeriesNumber, 1),
 | ||
|                     //SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, DateTime.Now).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, DateTime.Now).TimeOfDay),
 | ||
|                     //SeriesTime = DateTime.TryParse(dataset.GetSingleValue<string>(DicomTag.SeriesDate) + dataset.GetSingleValue<string>(DicomTag.SeriesTime), out DateTime dt) ? dt : null,
 | ||
|                     SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.SeriesDate).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.SeriesTime).TimeOfDay),
 | ||
| 
 | ||
|                     DicomSeriesDate = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty),
 | ||
|                     DicomSeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty),
 | ||
| 
 | ||
|                     Modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
 | ||
|                     Description = dataset.GetSingleValueOrDefault(DicomTag.SeriesDescription, string.Empty),
 | ||
|                     SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
 | ||
| 
 | ||
|                     ImagePositionPatient = dataset.GetSingleValueOrDefault(DicomTag.ImagePositionPatient, string.Empty),
 | ||
|                     ImageOrientationPatient = dataset.GetSingleValueOrDefault(DicomTag.ImageOrientationPatient, string.Empty),
 | ||
|                     BodyPartExamined = dataset.GetSingleValueOrDefault(DicomTag.BodyPartExamined, string.Empty),
 | ||
|                     SequenceName = dataset.GetSingleValueOrDefault(DicomTag.SequenceName, string.Empty),
 | ||
|                     ProtocolName = dataset.GetSingleValueOrDefault(DicomTag.ProtocolName, string.Empty),
 | ||
|                     ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
 | ||
| 
 | ||
|                     AcquisitionTime = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionTime, string.Empty),
 | ||
|                     AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
 | ||
|                     TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
 | ||
| 
 | ||
|                     TrialId = trialId,
 | ||
|                     SubjectId = subjectId,
 | ||
|                     SubjectVisitId = subjectVisitId,
 | ||
| 
 | ||
|                     InstanceCount = 0
 | ||
|                 };
 | ||
| 
 | ||
|                 ++findStudy.SeriesCount;
 | ||
|             }
 | ||
| 
 | ||
| 
 | ||
|             var transferSyntaxUID = dataset.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty);
 | ||
| 
 | ||
|             var isEncapsulated = false;
 | ||
|             if (transferSyntaxUID.IsNotNullOrEmpty())
 | ||
|             {
 | ||
|                 isEncapsulated = DicomTransferSyntax.Lookup(DicomUID.Parse(transferSyntaxUID)).IsEncapsulated;
 | ||
|             }
 | ||
| 
 | ||
|             if (findInstance == null)
 | ||
|             {
 | ||
|                 isInstanceNeedAdd = true;
 | ||
|                 findInstance = new DicomInstance
 | ||
|                 {
 | ||
|                     Id = instanceId,
 | ||
|                     StudyId = findStudy.Id,
 | ||
|                     SeriesId = findSerice.Id,
 | ||
|                     StudyInstanceUid = findStudy.StudyInstanceUid,
 | ||
|                     SeriesInstanceUid = findSerice.SeriesInstanceUid,
 | ||
| 
 | ||
|                     TrialId = trialId,
 | ||
|                     SubjectId = subjectId,
 | ||
|                     SubjectVisitId = subjectVisitId,
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|                     SopInstanceUid = sopInstanceUid,
 | ||
|                     SOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty),
 | ||
|                     MediaStorageSOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty),
 | ||
|                     TransferSytaxUID = transferSyntaxUID,
 | ||
|                     MediaStorageSOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty),
 | ||
|                     IsEncapsulated = isEncapsulated,
 | ||
| 
 | ||
|                     InstanceNumber = dataset.GetSingleValueOrDefault(DicomTag.InstanceNumber, 1),
 | ||
|                     InstanceTime = dataset.GetSingleValueOrDefault(DicomTag.ContentDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.ContentDate).Add(dataset.GetSingleValueOrDefault(DicomTag.ContentTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.ContentTime).TimeOfDay),
 | ||
| 
 | ||
|                     CPIStatus = false,
 | ||
|                     ImageRows = dataset.GetSingleValueOrDefault(DicomTag.Rows, 0),
 | ||
|                     ImageColumns = dataset.GetSingleValueOrDefault(DicomTag.Columns, 0),
 | ||
|                     SliceLocation = dataset.GetSingleValueOrDefault(DicomTag.SliceLocation, 0),
 | ||
| 
 | ||
|                     SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
 | ||
|                     NumberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 0),
 | ||
|                     PixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.PixelSpacing, string.Empty),
 | ||
|                     ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
 | ||
|                     FrameOfReferenceUID = dataset.GetSingleValueOrDefault(DicomTag.FrameOfReferenceUID, string.Empty),
 | ||
|                     WindowCenter = dataset.GetSingleValueOrDefault(DicomTag.WindowCenter, string.Empty),
 | ||
|                     WindowWidth = dataset.GetSingleValueOrDefault(DicomTag.WindowWidth, string.Empty),
 | ||
|                 };
 | ||
| 
 | ||
|                 ++findStudy.InstanceCount;
 | ||
|                 ++findSerice.InstanceCount;
 | ||
|             }
 | ||
| 
 | ||
| 
 | ||
|             if (isSeriesNeedAdd)
 | ||
|             {
 | ||
|                 await _seriesRepository.AddAsync(findSerice);
 | ||
|             }
 | ||
|             if (isInstanceNeedAdd)
 | ||
|             {
 | ||
|                 await _instanceRepository.AddAsync(findInstance);
 | ||
|             }
 | ||
| 
 | ||
|         }
 | ||
| 
 | ||
|     }
 | ||
| } |