904 lines
40 KiB
C#
904 lines
40 KiB
C#
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.Models;
|
||
using IRaCIS.Core.Domain.Share;
|
||
using IRaCIS.Core.Infrastructure;
|
||
using IRaCIS.Core.Infrastructure.Extention;
|
||
using MassTransit;
|
||
using MathNet.Numerics;
|
||
using Medallion.Threading;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
||
using Newtonsoft.Json;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Data;
|
||
using System.IO.Compression;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
using System.Web;
|
||
using ZiggyCreatures.Caching.Fusion;
|
||
|
||
namespace IRaCIS.Core.Application.Service.ImageAndDoc
|
||
{
|
||
public interface IDownloadAndUploadService
|
||
{
|
||
Task PackageAndAnonymizImage(Guid trialId, Guid subjectVisitId, bool isDicom, bool isAnonymize = true);
|
||
}
|
||
[ApiExplorerSettings(GroupName = "Trial")]
|
||
public class DownloadAndUploadService : BaseService, IDownloadAndUploadService
|
||
{
|
||
private readonly IRepository<SystemAnonymization> _systemAnonymizationRepository;
|
||
private readonly IRepository<VisitTask> _visitTaskRepository;
|
||
private readonly IRepository<SubjectVisit> _subjectVisitRepository;
|
||
private readonly IOSSService _oSSService;
|
||
private readonly IRepository<StudyMonitor> _studyMonitorRepository;
|
||
private readonly IDistributedLockProvider _distributedLockProvider;
|
||
public DownloadAndUploadService(IRepository<SystemAnonymization> systemAnonymizationRepository, IRepository<SubjectVisit> subjectVisitRepository, IOSSService oSSService,
|
||
IRepository<StudyMonitor> studyMonitorRepository, IDistributedLockProvider distributedLockProvider, IRepository<VisitTask> visitTaskRepository)
|
||
{
|
||
_systemAnonymizationRepository = systemAnonymizationRepository;
|
||
|
||
_subjectVisitRepository = subjectVisitRepository;
|
||
|
||
_oSSService = oSSService;
|
||
_studyMonitorRepository = studyMonitorRepository;
|
||
_distributedLockProvider = distributedLockProvider;
|
||
_visitTaskRepository = visitTaskRepository;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 受试者随机阅片,任务进行随机编号
|
||
/// 进入阅片任务前,随机挑选出该受试者的一个任务,然后给该任务一个编号,编号给的逻辑是:TimePoint Ran+ 已阅任务数量+1
|
||
/// 根据当前受试者该标准已阅任务数量(生效失效的任务都算,考虑重阅,最后编号不重复) 第一个就是TimePoint Ran1,后面依次随机挑选出来的阅片序号依次递增
|
||
/// </summary>
|
||
/// <param name="subjectId"></param>
|
||
/// <param name="trialReadingCriterionId"></param>
|
||
/// <returns></returns>
|
||
public async Task<IResponseOutput> 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.DoctorUserId == _userInfo.Id
|
||
&& t.ReadingTaskState == ReadingTaskState.HaveSigned && t.ReadingCategory == ReadingCategory.Visit);
|
||
|
||
//已经处理过的任务名称的数量
|
||
|
||
var haveDealedTaskCount = await _visitTaskRepository.CountAsync(t => t.SubjectId == subjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.IsAnalysisCreate == false && t.DoctorUserId == _userInfo.Id
|
||
&& t.ReadingTaskState != ReadingTaskState.HaveSigned && t.ReadingCategory == ReadingCategory.Visit && t.TaskBlindName != "Timepoint");
|
||
|
||
|
||
|
||
//随机赋值编号 比如要处理5个任务,实例化一个包含1-5的数组,每次随机取出一个
|
||
List<int> availableNumbers = Enumerable.Range(haveDealedTaskCount + 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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取该受试者任务上传列表(展示已上传情况)
|
||
/// </summary>
|
||
/// <param name="subjectId"></param>
|
||
/// <param name="trialReadingCriterionId"></param>
|
||
/// <returns></returns>
|
||
public async Task<IResponseOutput<List<SubjectImageUploadDTO>>> GetSubjectImageUploadList(Guid subjectId, Guid trialReadingCriterionId)
|
||
{
|
||
await SubejctRandomReadingTaskNameDeal(subjectId, trialReadingCriterionId);
|
||
|
||
var query = _repository.Where<VisitTask>(t => t.SubjectId == subjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.SourceSubjectVisitId != null
|
||
&& t.DoctorUserId == _userInfo.Id && t.TaskState == TaskState.Effect)
|
||
.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
|
||
.Where(t => u.TrialReadingCriterion.IsImageFilter ? ("|" + u.TrialReadingCriterion.CriterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|") : true)
|
||
.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<Dictionary>(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<IResponseOutput> 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<IResponseOutput> AddOrUpdateArchiveTaskStudy(TaskArchiveStudyCommand incommand)
|
||
{
|
||
#region 获取该subject 已生成任务的访视的检查
|
||
|
||
var queryStudy = _repository.Where<VisitTask>(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<TaskStudy>(incommand.Study);
|
||
|
||
var @lock = _distributedLockProvider.CreateLock($"StudyCode");
|
||
|
||
using (await @lock.AcquireAsync())
|
||
{
|
||
//查询数据库获取最大的Code 没有记录则为0
|
||
var dbStudyCodeIntMax = _repository.Where<TaskStudy>(s => s.TrialId == trialId).Select(t => t.Code).DefaultIfEmpty().Max();
|
||
|
||
//获取缓存中的值 并发的时候,需要记录,已被占用的值 这样其他线程在此占用的最大的值上递增
|
||
var cacheMaxCodeInt = await _fusionCache.GetOrDefaultAsync<int>(CacheKeys.TrialStudyMaxCode(trialId));
|
||
|
||
int currentNextCodeInt = cacheMaxCodeInt > dbStudyCodeIntMax ? cacheMaxCodeInt + 1 : dbStudyCodeIntMax + 1;
|
||
study.Code = currentNextCodeInt;
|
||
|
||
study.StudyCode = AppSettings.GetCodeStr(currentNextCodeInt, nameof(DicomStudy));
|
||
|
||
await _fusionCache.SetAsync<int>(CacheKeys.TrialStudyMaxCode(trialId), 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<TaskSeries>(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<TaskInstance>(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<TaskStudy>(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<TaskSeries>(t => t.Id == seriesId);
|
||
|
||
//判断重复
|
||
if (dicomSeries == null)
|
||
{
|
||
var series = _mapper.Map<TaskSeries>(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<TaskInstance>(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
|
||
{
|
||
await _fusionCache.RemoveAsync(CacheKeys.TrialStudyUidUploading(incommand.TrialId, incommand.Study.StudyInstanceUid));
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
return ResponseOutput.Ok(modalitys);
|
||
}
|
||
|
||
|
||
[HttpDelete]
|
||
public async Task<IResponseOutput> DeleteTaskStudy(Guid visitTaskId)
|
||
{
|
||
|
||
await _repository.BatchDeleteAsync<TaskStudy>(t => t.VisitTaskId == visitTaskId);
|
||
await _repository.BatchDeleteAsync<TaskSeries>(t => t.VisitTaskId == visitTaskId);
|
||
await _repository.BatchDeleteAsync<TaskInstance>(t => t.VisitTaskId == visitTaskId);
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
/// <summary>
|
||
/// 打包和匿名化影像 默认是匿名化打包,也可以不匿名化打包
|
||
/// </summary>
|
||
/// <param name="trialId"></param>
|
||
/// <param name="subjectVisitId"></param>
|
||
/// <param name="isDicom"></param>
|
||
/// <param name="isAnonymize"></param>
|
||
/// <returns></returns>
|
||
|
||
public async Task<IResponseOutput> RequestPackageAndAnonymizImage(Guid trialId, Guid subjectVisitId, bool isDicom, bool isAnonymize = true)
|
||
{
|
||
|
||
|
||
var extralConfig = _repository.Where<Trial>(t => t.Id == trialId).Select(t => t.TrialExtraConfigJsonStr).FirstOrDefault() ?? string.Empty;
|
||
|
||
var config = JsonConvert.DeserializeObject<TrialExtraConfig>(extralConfig) ?? new TrialExtraConfig();
|
||
|
||
if (config.IsSupportQCDownloadImage == false)
|
||
{
|
||
throw new BusinessValidationFailedException("该项目不支持QC下载影像");
|
||
}
|
||
|
||
var subjectVisit = await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId);
|
||
|
||
var sujectCode = await _subjectVisitRepository.Where(t => t.Id == subjectVisitId).Select(t => t.Subject.Code).FirstOrDefaultAsync();
|
||
|
||
var packState = isDicom ? subjectVisit.PackState : subjectVisit.NoDicomPackState;
|
||
|
||
if (packState == PackState.WaitPack)
|
||
{
|
||
|
||
if (isDicom)
|
||
{
|
||
subjectVisit.PackState = PackState.Packing;
|
||
HangfireJobHelper.NotImmediatelyOnceOnlyJob<IDownloadAndUploadService>(t => t.PackageAndAnonymizImage(trialId, subjectVisitId, true, isAnonymize), TimeSpan.FromSeconds(1));
|
||
|
||
}
|
||
else
|
||
{
|
||
subjectVisit.NoDicomPackState = PackState.Packing;
|
||
|
||
HangfireJobHelper.NotImmediatelyOnceOnlyJob<IDownloadAndUploadService>(t => t.PackageAndAnonymizImage(trialId, subjectVisitId, false, isAnonymize), TimeSpan.FromSeconds(1));
|
||
}
|
||
|
||
|
||
await _subjectVisitRepository.SaveChangesAsync();
|
||
}
|
||
|
||
return ResponseOutput.Ok(isDicom ? subjectVisit.VisitImageZipPath : subjectVisit.VisitNoDicomImageZipPath,
|
||
new { FileName = $"{sujectCode}_{subjectVisit.VisitName}_ImageStudy_{(isDicom ? "Dicom" : "NoneDicom")}.zip" });
|
||
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 受试者级别所有的影像
|
||
/// 访视级别的影响 传递subjectVisitId
|
||
/// 标准Id是可选的 不同标准有些检查可能有过滤
|
||
/// </summary>
|
||
/// <param name="_subjectRepository"></param>
|
||
/// <param name="inQuery"></param>
|
||
/// <returns></returns>
|
||
public async Task<IResponseOutput> GetSubejectOrVisitZipInfo([FromServices] IRepository<Subject> _subjectRepository, SubejctZipInfoQuery inQuery)
|
||
{
|
||
var isImageFilter = false;
|
||
|
||
var criterionModalitys = string.Empty;
|
||
|
||
if (inQuery.TrialReadingCriterionId != null)
|
||
{
|
||
var criterionInfo = await _repository.Where<ReadingQuestionCriterionTrial>(t => t.Id == inQuery.TrialReadingCriterionId).Select(t => new { t.IsImageFilter, t.CriterionModalitys }).FirstOrDefaultAsync();
|
||
|
||
if (criterionInfo != null)
|
||
{
|
||
isImageFilter = criterionInfo.IsImageFilter;
|
||
criterionModalitys = criterionInfo.CriterionModalitys;
|
||
}
|
||
|
||
}
|
||
|
||
if (inQuery.SubejectVisitId != null)
|
||
{
|
||
var query = from sv in _subjectVisitRepository.Where(t => t.Id == inQuery.SubejectVisitId)
|
||
|
||
|
||
select new
|
||
{
|
||
SubjectCode = sv.Subject.Code,
|
||
VisitName = sv.VisitName,
|
||
StudyList = sv.StudyList.AsQueryable().WhereIf(isImageFilter, t => ("|" + criterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|"))
|
||
.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
|
||
})
|
||
})
|
||
|
||
}),
|
||
|
||
NoneDicomStudyList = sv.NoneDicomStudyList.AsQueryable().WhereIf(isImageFilter, t => ("|" + criterionModalitys + "|").Contains("|" + t.Modality + "|"))
|
||
.Select(nd => new
|
||
{
|
||
nd.Modality,
|
||
nd.StudyCode,
|
||
nd.ImageDate,
|
||
|
||
FileList = nd.NoneDicomFileList.Select(file => new
|
||
{
|
||
file.FileName,
|
||
file.Path,
|
||
file.FileType
|
||
})
|
||
})
|
||
};
|
||
|
||
var result = query.ToList();
|
||
|
||
return ResponseOutput.Ok(result);
|
||
}
|
||
else if (inQuery.SubejctId != null)
|
||
{
|
||
var query = from sv in _subjectRepository.Where(t => t.Id == inQuery.SubejctId).SelectMany(t=>t.SubjectVisitList)
|
||
|
||
|
||
select new
|
||
{
|
||
SubjectCode = sv.Subject.Code,
|
||
VisitName = sv.VisitName,
|
||
StudyList = sv.StudyList.AsQueryable().WhereIf(isImageFilter, t => ("|" + criterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|"))
|
||
.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
|
||
})
|
||
})
|
||
|
||
}),
|
||
|
||
NoneDicomStudyList = sv.NoneDicomStudyList.AsQueryable().WhereIf(isImageFilter, t => ("|" + criterionModalitys + "|").Contains("|" + t.Modality + "|"))
|
||
.Select(nd => new
|
||
{
|
||
nd.Modality,
|
||
nd.StudyCode,
|
||
nd.ImageDate,
|
||
|
||
FileList = nd.NoneDicomFileList.Select(file => new
|
||
{
|
||
file.FileName,
|
||
file.Path,
|
||
file.FileType
|
||
})
|
||
})
|
||
};
|
||
|
||
var result = query.ToList();
|
||
|
||
return ResponseOutput.Ok(result);
|
||
}
|
||
else
|
||
{
|
||
return ResponseOutput.NotOk("不允许 subjectId subjectId 都不传递");
|
||
}
|
||
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 后台任务调用,前端忽略该接口
|
||
/// </summary>
|
||
/// <param name="trialId"></param>
|
||
/// <param name="subjectVisitId"></param>
|
||
/// <param name="isDicom"></param>
|
||
/// <param name="isAnonymize"></param>
|
||
/// <returns></returns>
|
||
public async Task PackageAndAnonymizImage(Guid trialId, Guid subjectVisitId, bool isDicom, bool isAnonymize = true)
|
||
{
|
||
|
||
var subjectVisit = await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId);
|
||
|
||
try
|
||
{
|
||
var addOrUpdateFixedFieldList = new List<SystemAnonymization>();
|
||
|
||
var ircFieldList = new List<SystemAnonymization>();
|
||
|
||
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
|
||
})
|
||
})
|
||
|
||
}),
|
||
|
||
NoneDicomStudyList = sv.NoneDicomStudyList.Select(nd => new
|
||
{
|
||
nd.Modality,
|
||
nd.StudyCode,
|
||
nd.ImageDate,
|
||
|
||
FileList = nd.NoneDicomFileList.Select(file => new
|
||
{
|
||
file.FileName,
|
||
file.Path,
|
||
file.FileType
|
||
})
|
||
})
|
||
};
|
||
|
||
var info = query.FirstOrDefault();
|
||
|
||
if (info != null)
|
||
{
|
||
// 创建一个临时文件夹来存放文件
|
||
string tempFolderPath = Path.Combine(Directory.GetCurrentDirectory(), $"DownloadTemp_{NewId.NextGuid()}");
|
||
Directory.CreateDirectory(tempFolderPath);
|
||
|
||
//dicom 处理
|
||
|
||
if (isDicom)
|
||
{
|
||
// 遍历查询结果
|
||
foreach (var studyInfo in info.StudyList)
|
||
{
|
||
// 遍历 Series
|
||
foreach (var seriesInfo in studyInfo.SeriesList)
|
||
{
|
||
string studyDicomFolderPath = Path.Combine(tempFolderPath, "Dicom",/* $"{info.SubjectCode}_{info.VisitName}",*/ $"{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));
|
||
|
||
//下载到当前目录
|
||
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 zipDicomPath = Path.Combine(Directory.GetCurrentDirectory(), $"{info.SubjectCode}_{info.VisitName}_ImageStudy_Dicom.zip");
|
||
ZipFile.CreateFromDirectory(Path.Combine(tempFolderPath, "Dicom"), zipDicomPath);
|
||
//上传到Oss
|
||
var relativeDicomPath = await _oSSService.UploadToOSSAsync(zipDicomPath, $"download_zip", false);
|
||
|
||
await _subjectVisitRepository.BatchUpdateNoTrackingAsync(t => t.Id == subjectVisitId, u => new SubjectVisit() { PackState = PackState.Packed, VisitImageZipPath = relativeDicomPath });
|
||
|
||
|
||
File.Delete(zipDicomPath);
|
||
}
|
||
else
|
||
{
|
||
var relativeNoneDicomPath = string.Empty;
|
||
|
||
if (info.NoneDicomStudyList.Count() == 1 && info.NoneDicomStudyList.SelectMany(t => t.FileList).Count() == 1)
|
||
{
|
||
relativeNoneDicomPath = info.NoneDicomStudyList.First().FileList.First().Path;
|
||
}
|
||
else
|
||
{
|
||
//非dicom 处理
|
||
|
||
foreach (var noneDicomStudy in info.NoneDicomStudyList)
|
||
{
|
||
string studyNoneDicomFolderPath = Path.Combine(tempFolderPath, "NoneDicom", /*$"{info.SubjectCode}_{info.VisitName}",*/ $"{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));
|
||
|
||
//下载到当前目录
|
||
await _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath);
|
||
}
|
||
}
|
||
|
||
var zipNoneDicomPath = Path.Combine(Directory.GetCurrentDirectory(), $"{info.SubjectCode}_{info.VisitName}_ImageStudy_NoneDicom.zip");
|
||
ZipFile.CreateFromDirectory(Path.Combine(tempFolderPath, "NoneDicom"), zipNoneDicomPath);
|
||
relativeNoneDicomPath = await _oSSService.UploadToOSSAsync(zipNoneDicomPath, $"download_zip", false);
|
||
|
||
File.Delete(zipNoneDicomPath);
|
||
}
|
||
|
||
await _subjectVisitRepository.BatchUpdateNoTrackingAsync(t => t.Id == subjectVisitId, u => new SubjectVisit() { PackState = PackState.Packed, VisitNoDicomImageZipPath = relativeNoneDicomPath });
|
||
|
||
|
||
}
|
||
|
||
//清理文件夹
|
||
Directory.Delete(tempFolderPath, true);
|
||
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
await _subjectVisitRepository.BatchUpdateNoTrackingAsync(t => t.Id == subjectVisitId, u => new SubjectVisit() { PackState = PackState.WaitPack });
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
}
|
||
|
||
}
|
||
}
|