using EasyCaching.Core; using FellowOakDicom; using IRaCIS.Core.Application.Contracts; using IRaCIS.Core.Application.Filter; using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.Service.ImageAndDoc.DTO; using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Infrastructure; using MassTransit; using MathNet.Numerics; using Medallion.Threading; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Data; using System.IO.Compression; using System.Linq; using System.Text; using System.Threading.Tasks; namespace IRaCIS.Core.Application.Service.ImageAndDoc { public interface IDownloadAndUploadService { Task PackageAndAnonymizImage(Guid trialId, Guid subjectVisitId, bool isAnonymize = true); } [ApiExplorerSettings(GroupName = "Trial")] public class DownloadAndUploadService : BaseService, IDownloadAndUploadService { private readonly IEasyCachingProvider _provider; private readonly IRepository _systemAnonymizationRepository; private readonly IRepository _visitTaskRepository; private readonly IRepository _subjectVisitRepository; private readonly IOSSService _oSSService; private readonly IRepository _studyMonitorRepository; private readonly IDistributedLockProvider _distributedLockProvider; public DownloadAndUploadService(IEasyCachingProvider provider, IRepository systemAnonymizationRepository, IRepository subjectVisitRepository, IOSSService oSSService, IRepository studyMonitorRepository, IDistributedLockProvider distributedLockProvider, IRepository visitTaskRepository) { _systemAnonymizationRepository = systemAnonymizationRepository; _subjectVisitRepository = subjectVisitRepository; _oSSService = oSSService; _studyMonitorRepository = studyMonitorRepository; _distributedLockProvider = distributedLockProvider; _provider = provider; _visitTaskRepository = visitTaskRepository; } /// /// 受试者随机阅片,任务进行随机编号 /// 进入阅片任务前,随机挑选出该受试者的一个任务,然后给该任务一个编号,编号给的逻辑是:TimePoint Ran+ 已阅任务数量+1 /// 根据当前受试者该标准已阅任务数量(生效失效的任务都算,考虑重阅,最后编号不重复) 第一个就是TimePoint Ran1,后面依次随机挑选出来的阅片序号依次递增 /// /// /// /// public async Task SubejctRandomReadingTaskNameDeal(Guid subjectId, Guid trialReadingCriterionId) { //subject 随机阅片 才处理任务编号 if (_visitTaskRepository.Any(t => t.SubjectId == subjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.TrialReadingCriterion.IsReadingTaskViewInOrder == ReadingOrder.SubjectRandom)) { //找到 非一致性分析,未签名,状态正常的 并且任务名称是TimePoint的 任务 var needDealTaskList = await _visitTaskRepository.Where(t => t.SubjectId == subjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.IsAnalysisCreate == false && t.DoctorUserId==_userInfo.Id && t.ReadingTaskState != ReadingTaskState.HaveSigned && t.TaskBlindName == "Timepoint" && t.ReadingCategory == ReadingCategory.Visit && (t.TaskState == TaskState.Effect || t.TaskState == TaskState.Freeze), true).ToListAsync(); if (needDealTaskList.Count>0) { //已完成的访视任务数量(包含重阅的) var haveFinishedTaskCount = await _visitTaskRepository.CountAsync(t => t.SubjectId == subjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.IsAnalysisCreate == false && t.ReadingTaskState == ReadingTaskState.HaveSigned && t.ReadingCategory == ReadingCategory.Visit); //随机赋值编号 比如要处理5个任务,实例化一个包含1-5的数组,每次随机取出一个 List availableNumbers = Enumerable.Range(haveFinishedTaskCount + 1, needDealTaskList.Count).ToList(); Random rng = new Random(); foreach (var visitTask in needDealTaskList) { int randomIndex = rng.Next(availableNumbers.Count); visitTask.TaskBlindName = $"Timepoint Ran {availableNumbers[randomIndex]}"; availableNumbers.RemoveAt(randomIndex); } await _visitTaskRepository.SaveChangesAsync(); } } return ResponseOutput.Ok(); } /// /// 获取该受试者任务上传列表(展示已上传情况) /// /// /// /// public async Task>> GetSubjectImageUploadList(Guid subjectId,Guid trialReadingCriterionId) { await SubejctRandomReadingTaskNameDeal(subjectId, trialReadingCriterionId); var query = _repository.Where(t => t.SubjectId == subjectId &&t.TrialReadingCriterionId== trialReadingCriterionId && t.SourceSubjectVisitId != null && t.DoctorUserId == _userInfo.Id) .Select(u => new SubjectImageUploadDTO() { VisitTaskId = u.Id, SubejctId = u.SubjectId, TrialSiteId = u.Subject.TrialSiteId, IsImageFilter = u.TrialReadingCriterion.IsImageFilter, CriterionModalitys = u.TrialReadingCriterion.CriterionModalitys, SubjectCode = u.IsSelfAnalysis == true ? u.BlindSubjectCode : u.Subject.Code, TaskBlindName = u.TaskBlindName, TaskName = u.TaskName, SourceSubjectVisitId = u.SourceSubjectVisitId, PackState = u.SourceSubjectVisit.PackState, OrginalStudyList = u.SourceSubjectVisit.StudyList.Select(t => new StudyBasicInfo() { Id = t.Id, StudyInstanceUid = t.StudyInstanceUid, ModalityForEdit = t.ModalityForEdit, BodyPartExamined = t.BodyPartExamined, BodyPartForEdit = t.BodyPartForEdit, StudyCode = t.StudyCode, StudyTime = t.StudyTime, Description = t.Description, InstanceCount = t.InstanceCount, Modalities = t.Modalities, SeriesCount = t.SeriesCount, }).ToList(), UploadStudyList = u.TaskStudyList.Select(t => new StudyBasicInfo() { Id = t.Id, StudyInstanceUid = t.StudyInstanceUid, ModalityForEdit = t.ModalityForEdit, BodyPartExamined = t.BodyPartExamined, BodyPartForEdit = t.BodyPartForEdit, StudyCode = t.StudyCode, StudyTime = t.StudyTime, Description = t.Description, InstanceCount = t.InstanceCount, Modalities = t.Modalities, SeriesCount = t.SeriesCount, SopInstanceUidList = t.InstanceList.Select(t => t.SopInstanceUid).ToList(), }).ToList() }) ; var list = await query.ToListAsync(); return ResponseOutput.Ok(list); } private void SpecialArchiveStudyDeal(TaskStudy study) { #region 特殊逻辑 if (study.PatientBirthDate.Length == 8) { study.PatientBirthDate = $"{study.PatientBirthDate[0]}{study.PatientBirthDate[1]}{study.PatientBirthDate[2]}{study.PatientBirthDate[3]}-{study.PatientBirthDate[4]}{study.PatientBirthDate[5]}-{study.PatientBirthDate[6]}{study.PatientBirthDate[7]}"; } var dicModalityList = _repository.Where(t => t.Code == "Modality").SelectMany(t => t.ChildList.Select(c => c.Value)).ToList(); var modality = study.Modalities; var modalityForEdit = dicModalityList.Contains(modality) ? modality : String.Empty; if (modality == "MR") { modalityForEdit = "MRI"; } if (modality == "PT") { modalityForEdit = "PET"; } if (modality == "PT、CT") { modalityForEdit = "PET-CT"; } study.ModalityForEdit = modalityForEdit; #endregion } [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task PreArchiveDicomStudy(PriArchiveTaskStudyCommand preArchiveStudyCommand) { var studyMonitor = new StudyMonitor() { TrialId = preArchiveStudyCommand.TrialId, SubjectId = preArchiveStudyCommand.SubjectId, SubjectVisitId = preArchiveStudyCommand.SubjectVisitId, IsSuccess = false, UploadStartTime = DateTime.Now, IsDicom = true, IP = _userInfo.IP, IsDicomReUpload = preArchiveStudyCommand.IsDicomReUpload, FileSize = preArchiveStudyCommand.FileSize, FileCount = preArchiveStudyCommand.FileCount, }; var addEntity = await _studyMonitorRepository.AddAsync(studyMonitor, true); return ResponseOutput.Ok(addEntity.Id); } [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task AddOrUpdateArchiveTaskStudy(TaskArchiveStudyCommand incommand) { #region 获取该subject 已生成任务的访视的检查 var queryStudy = _repository.Where(t => t.SubjectId == incommand.SubjectId && t.SourceSubjectVisitId != null && t.DoctorUserId == _userInfo.Id).Select(u => new { VisitTaskId = u.Id, SourceSubjectVisitId = u.SourceSubjectVisitId, OrginalStudyList = u.SourceSubjectVisit.StudyList.Select(t => new StudyBasicInfo() { Id = t.Id, StudyInstanceUid = t.StudyInstanceUid }).ToList(), }); var studyList = await queryStudy.ToListAsync(); var findOriginStudy = studyList.FirstOrDefault(c => c.OrginalStudyList.Any(t => t.StudyInstanceUid == incommand.Study.StudyInstanceUid)); if (findOriginStudy == null) { return ResponseOutput.NotOk("该检查不属于该受试者,请核查"); } #endregion var modalitys = string.Empty; try { var trialId = incommand.TrialId; var studyMonitor = await _studyMonitorRepository.FirstOrDefaultAsync(t => t.Id == incommand.StudyMonitorId); studyMonitor.UploadFinishedTime = DateTime.Now; studyMonitor.ArchiveFinishedTime = DateTime.Now; studyMonitor.FailedFileCount = incommand.FailedFileCount; studyMonitor.IsSuccess = incommand.FailedFileCount == 0; studyMonitor.RecordPath = incommand.RecordPath; //上传 if (studyMonitor.IsDicomReUpload == false) { var study = _mapper.Map(incommand.Study); var @lock = _distributedLockProvider.CreateLock($"StudyCode"); using (await @lock.AcquireAsync()) { //查询数据库获取最大的Code 没有记录则为0 var dbStudyCodeIntMax = _repository.Where(s => s.TrialId == trialId).Select(t => t.Code).DefaultIfEmpty().Max(); //获取缓存中的值 并发的时候,需要记录,已被占用的值 这样其他线程在此占用的最大的值上递增 var cacheMaxCodeInt = _provider.Get($"{trialId}_{StaticData.CacheKey.StudyMaxCode}").Value; int currentNextCodeInt = cacheMaxCodeInt > dbStudyCodeIntMax ? cacheMaxCodeInt + 1 : dbStudyCodeIntMax + 1; study.Code = currentNextCodeInt; study.StudyCode = AppSettings.GetCodeStr(currentNextCodeInt, nameof(DicomStudy)); _provider.Set($"{trialId}_{StaticData.CacheKey.StudyMaxCode}", study.Code, TimeSpan.FromMinutes(30)); } study.Id = IdentifierHelper.CreateGuid(incommand.Study.StudyInstanceUid, incommand.TrialId.ToString(), findOriginStudy.VisitTaskId.ToString()); study.TrialId = incommand.TrialId; study.SubjectId = incommand.SubjectId; study.VisitTaskId = findOriginStudy.VisitTaskId; //study.SubjectVisitId = incommand.SubjectVisitId; //特殊处理逻辑 study.Modalities = string.Join("、", incommand.Study.SeriesList.Select(t => t.Modality).Distinct()); SpecialArchiveStudyDeal(study); modalitys = study.Modalities; await _repository.AddAsync(study); studyMonitor.StudyId = study.Id; studyMonitor.StudyCode = study.StudyCode; foreach (var seriesItem in incommand.Study.SeriesList) { var series = _mapper.Map(seriesItem); series.Id = IdentifierHelper.CreateGuid(seriesItem.StudyInstanceUid, seriesItem.SeriesInstanceUid, incommand.TrialId.ToString(), findOriginStudy.VisitTaskId.ToString()); series.StudyId = study.Id; series.TrialId = incommand.TrialId; series.SubjectId = incommand.SubjectId; //series.SubjectVisitId = incommand.SubjectVisitId; series.VisitTaskId = findOriginStudy.VisitTaskId; //前端传递的数量不准,上传的时候,把失败的也加进来了,以实际数组的数字为准 series.InstanceCount = seriesItem.InstanceList.Count; await _repository.AddAsync(series); foreach (var instanceItem in seriesItem.InstanceList) { var isntance = _mapper.Map(instanceItem); Guid instanceId = IdentifierHelper.CreateGuid(study.StudyInstanceUid, series.SeriesInstanceUid, isntance.SopInstanceUid, study.TrialId.ToString(), findOriginStudy.VisitTaskId.ToString()); isntance.Id = instanceId; isntance.StudyId = study.Id; isntance.SeriesId = series.Id; isntance.TrialId = incommand.TrialId; isntance.SubjectId = incommand.SubjectId; //isntance.SubjectVisitId = incommand.SubjectVisitId; isntance.VisitTaskId = findOriginStudy.VisitTaskId; await _repository.AddAsync(isntance); } } } else { var studyId = IdentifierHelper.CreateGuid(incommand.Study.StudyInstanceUid, incommand.TrialId.ToString(), findOriginStudy.VisitTaskId.ToString()); var study = await _repository.FirstOrDefaultAsync(t => t.Id == studyId); //重传的时候也要赋值检查Id studyMonitor.StudyId = study.Id; studyMonitor.StudyCode = study.StudyCode; //特殊处理逻辑 study.Modalities = string.Join("、", incommand.Study.SeriesList.Select(t => t.Modality).Union(study.Modalities.Split("、", StringSplitOptions.RemoveEmptyEntries)).Distinct()); SpecialArchiveStudyDeal(study); modalitys = study.Modalities; // 少了整个序列 //某个序列下少了instance foreach (var seriesItem in incommand.Study.SeriesList) { var seriesId = IdentifierHelper.CreateGuid(seriesItem.StudyInstanceUid, seriesItem.SeriesInstanceUid, trialId.ToString(), findOriginStudy.VisitTaskId.ToString()); TaskSeries dicomSeries = await _repository.FirstOrDefaultAsync(t => t.Id == seriesId); //判断重复 if (dicomSeries == null) { var series = _mapper.Map(seriesItem); series.Id = seriesId; series.StudyId = study.Id; series.TrialId = incommand.TrialId; series.SubjectId = incommand.SubjectId; series.VisitTaskId = findOriginStudy.VisitTaskId; //series.SubjectVisitId = incommand.SubjectVisitId; dicomSeries = await _repository.AddAsync(series); //新的序列 那么 检查的序列数量+1 study.SeriesCount += 1; } else { //该序列掉了instance dicomSeries.InstanceCount += seriesItem.InstanceList.Count; } foreach (var instanceItem in seriesItem.InstanceList) { var insntance = _mapper.Map(instanceItem); insntance.Id = IdentifierHelper.CreateGuid(insntance.StudyInstanceUid, insntance.SeriesInstanceUid, insntance.SopInstanceUid, trialId.ToString(), findOriginStudy.VisitTaskId.ToString()); insntance.StudyId = study.Id; insntance.SeriesId = dicomSeries.Id; insntance.TrialId = incommand.TrialId; insntance.SubjectId = incommand.SubjectId; insntance.VisitTaskId = findOriginStudy.VisitTaskId; await _repository.AddAsync(insntance); } // 不管是新的序列 还是 该序列 掉了Instance 重传的时候 检查的instance 数量都会增加 study.InstanceCount += seriesItem.InstanceList.Count; } } var @lock2 = _distributedLockProvider.CreateLock($"StudyCommit"); using (await @lock2.AcquireAsync()) { await _repository.SaveChangesAsync(); } } catch (Exception ex) { return ResponseOutput.NotOk(ex.Message); } finally { _provider.Remove($"StudyUid_{incommand.TrialId}_{incommand.Study.StudyInstanceUid}"); } return ResponseOutput.Ok(modalitys); } [HttpDelete] public async Task DeleteTaskStudy(Guid visitTaskId) { await _repository.BatchDeleteAsync(t => t.VisitTaskId == visitTaskId); await _repository.BatchDeleteAsync(t => t.VisitTaskId == visitTaskId); await _repository.BatchDeleteAsync(t => t.VisitTaskId == visitTaskId); return ResponseOutput.Ok(); } /// /// 打包和匿名化影像 默认是匿名化打包,也可以不匿名化打包 /// /// /// /// /// public async Task RequestPackageAndAnonymizImage(Guid trialId, Guid subjectVisitId, bool isAnonymize = true) { var subjectVisit = await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId); if (subjectVisit.PackState == PackState.WaitPack) { HangfireJobHelper.NotImmediatelyOnceOnlyJob(t => t.PackageAndAnonymizImage(trialId, subjectVisitId, isAnonymize), TimeSpan.FromSeconds(1)); subjectVisit.PackState = PackState.Packing; await _subjectVisitRepository.SaveChangesAsync(); } return ResponseOutput.Ok(subjectVisit.VisitImageZipPath); } /// /// 后台任务调用,前端忽略该接口 /// /// /// /// /// public async Task PackageAndAnonymizImage(Guid trialId, Guid subjectVisitId, bool isAnonymize = true) { var subjectVisit = await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId); try { var addOrUpdateFixedFieldList = new List(); var ircFieldList = new List(); if (isAnonymize) { var systemAnonymizationList = _systemAnonymizationRepository.Where(t => t.IsEnable).ToList(); addOrUpdateFixedFieldList = systemAnonymizationList.Where(t => t.IsFixed).ToList(); ircFieldList = systemAnonymizationList.Where(t => t.IsFixed == false).ToList(); } var subjectAndVisitInfo = _subjectVisitRepository.Where(t => t.Id == subjectVisitId).Select(t => new { SubjectCode = t.Subject.Code, t.Trial.TrialCode, t.VisitNum }).FirstOrDefault(); var query = from sv in _subjectVisitRepository.Where(t => t.Id == subjectVisitId) select new { 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, InstancePathList = z.DicomInstanceList.Select(k => new { k.Path }) }) }) }; var info = query.FirstOrDefault(); if (info != null) { // 创建一个临时文件夹来存放文件 string tempFolderPath = Path.Combine(Directory.GetCurrentDirectory(), $"DownloadTemp_{NewId.NextGuid()}"); Directory.CreateDirectory(tempFolderPath); // 遍历查询结果 foreach (var studyInfo in info.StudyList) { // 遍历 Series foreach (var seriesInfo in studyInfo.SeriesList) { string studyFolderPath = Path.Combine(tempFolderPath, $"{info.SubjectCode}_{info.VisitName}", $"{studyInfo.StudyCode}_{studyInfo.StudyTime?.ToString("yyyy-MM-dd")}_{seriesInfo.Modality}"); // 创建 影像 文件夹 Directory.CreateDirectory(studyFolderPath); // 遍历 InstancePathList foreach (var instanceInfo in seriesInfo.InstancePathList) { // 复制文件到相应的文件夹 string destinationPath = Path.Combine(studyFolderPath, Path.GetFileName(instanceInfo.Path)); //下载到当前目录 await _oSSService.DownLoadFromOSSAsync(instanceInfo.Path, destinationPath); #region 匿名化逻辑 if (isAnonymize) { //受试者随机阅片,需要匿名化检查时间 DicomFile dicomFile = await DicomFile.OpenAsync(destinationPath, Encoding.Default); DicomDataset dataset = dicomFile.Dataset; dataset.AddOrUpdate(DicomTag.StudyDate, string.Empty); dataset.AddOrUpdate(DicomTag.StudyTime, string.Empty); #region 前端已经匿名化,不需要做相关tag匿名化 //DicomFile dicomFile = await DicomFile.OpenAsync(destinationPath, Encoding.Default); //DicomDataset dataset = dicomFile.Dataset; //foreach (var item in addOrUpdateFixedFieldList) //{ // var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16)); // dataset.AddOrUpdate(dicomTag, item.ReplaceValue); //} //foreach (var item in ircFieldList) //{ // var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16)); // if (dicomTag == DicomTag.ClinicalTrialProtocolID) // { // dataset.AddOrUpdate(dicomTag, subjectAndVisitInfo.TrialCode); // } // if (dicomTag == DicomTag.ClinicalTrialSiteID) // { // //dataset.AddOrUpdate(dicomTag, subjectAndVisitInfo.TrialSiteCode); // } // if (dicomTag == DicomTag.ClinicalTrialSubjectID) // { // dataset.AddOrUpdate(dicomTag, subjectAndVisitInfo.SubjectCode); // } // if (dicomTag == DicomTag.ClinicalTrialTimePointID) // { // dataset.AddOrUpdate(dicomTag, subjectAndVisitInfo.VisitNum.ToString()); // } // if (dicomTag == DicomTag.PatientID) // { // dataset.AddOrUpdate(dicomTag, subjectAndVisitInfo.TrialCode + "_" + subjectAndVisitInfo.SubjectCode); // } //} #endregion } #endregion } } } var zipPath = Path.Combine(Directory.GetCurrentDirectory(), $"{info.SubjectCode}_{info.VisitName}_ImageStudy.zip"); ZipFile.CreateFromDirectory(tempFolderPath, zipPath); //上传到Oss var relativePath = await _oSSService.UploadToOSSAsync(zipPath, $"download_zip", false); //subjectVisit.PackState = PackState.Packed; //subjectVisit.VisitImageZipPath = relativePath; //await _subjectVisitRepository.SaveChangesAsync(); await _subjectVisitRepository.BatchUpdateNoTrackingAsync(t => t.Id == subjectVisitId, u => new SubjectVisit() { PackState = PackState.Packed, VisitImageZipPath = relativePath }); //清理文件夹 Directory.Delete(tempFolderPath, true); File.Delete(zipPath); } } catch (Exception ex) { await _subjectVisitRepository.BatchUpdateNoTrackingAsync(t => t.Id == subjectVisitId, u => new SubjectVisit() { PackState = PackState.WaitPack }); } } } }