439 lines
21 KiB
C#
439 lines
21 KiB
C#
//--------------------------------------------------------------------
|
|
// 此代码由T4模板自动生成 byzhouhang 20210918
|
|
// 生成时间 2021-12-06 10:54:55
|
|
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
|
|
//--------------------------------------------------------------------
|
|
using IRaCIS.Core.Application.Helper;
|
|
using IRaCIS.Core.Infrastructure;
|
|
using Medallion.Threading;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using System.ComponentModel.DataAnnotations;
|
|
using static ImageHelper;
|
|
|
|
namespace IRaCIS.Core.Application.Contracts
|
|
{
|
|
/// <summary>
|
|
/// NoneDicomStudyService
|
|
/// </summary>
|
|
[ApiExplorerSettings(GroupName = "Image")]
|
|
public class NoneDicomStudyService(IRepository<NoneDicomStudy> _noneDicomStudyRepository,
|
|
IRepository<NoneDicomStudyFile> _noneDicomStudyFileRepository,
|
|
IRepository<Trial> _trialRepository,
|
|
IDistributedLockProvider _distributedLockProvider,
|
|
IRepository<SubjectVisit> _subjectVisitRepository,
|
|
IRepository<VisitTask> _visitTaskRepository, IOSSService _oSSService,
|
|
QCCommon _qCCommon, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, INoneDicomStudyService
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
/// 标注遮盖影像 路径后面加了.MaskNoneDicom_ 就是遮盖的新路径
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
[HttpPost]
|
|
public async Task<IResponseOutput> NoneDicomStudyMaskImage(NoneDicomStudyMaskImageCommand inCommand)
|
|
{
|
|
if (inCommand.NodicomStudyId == null && inCommand.NoneDicomStudyFileIdList == null)
|
|
{
|
|
return ResponseOutput.NotOk("NodicomStudyId and NoneDicomStudyFileIdList can not both be null");
|
|
}
|
|
|
|
|
|
var idPathList = new List<DownloadFileDto>();
|
|
|
|
if (inCommand.NodicomStudyId == null && inCommand.NoneDicomStudyFileIdList != null)
|
|
{
|
|
idPathList = await _noneDicomStudyFileRepository.Where(t => inCommand.NoneDicomStudyFileIdList.Contains(t.Id)).ProjectTo<DownloadFileDto>(_mapper.ConfigurationProvider).ToListAsync();
|
|
|
|
}
|
|
else
|
|
{
|
|
idPathList = await _noneDicomStudyFileRepository.Where(t => inCommand.NodicomStudyId == t.NoneDicomStudyId).ProjectTo<DownloadFileDto>(_mapper.ConfigurationProvider).ToListAsync();
|
|
|
|
}
|
|
|
|
|
|
var errorList = new List<DownloadFileDto>();
|
|
|
|
var okList = new List<DownloadFileDto>();
|
|
|
|
var batchId = Guid.NewGuid();
|
|
|
|
foreach (var item in idPathList)
|
|
{
|
|
var path = item.Path;
|
|
|
|
try
|
|
{
|
|
|
|
// 1. 下载文件
|
|
using var inputStream = new MemoryStream();
|
|
await (await _oSSService.GetStreamFromOSSAsync(path)).CopyToAsync(inputStream);
|
|
inputStream.Position = 0;
|
|
|
|
// 2. 处理图片
|
|
using var outputStream = new MemoryStream();
|
|
await ImageMaskHelper.MaskAsync(inputStream, outputStream, inCommand.MaskRegionList);
|
|
outputStream.Position = 0;
|
|
|
|
|
|
|
|
var prefix = path.Substring(1, path.LastIndexOf('/') - 1);
|
|
|
|
//每次都用一个新的名字
|
|
var maskFileName = string.Empty;
|
|
|
|
if (path.Contains(".MaskNoneDicom_"))
|
|
{
|
|
//清理缓存的里面的遮盖图,多次遮盖同一张图时,清除缓存很重要
|
|
await _oSSService.DeleteFromPrefix(path, true); //清理缓存的里面的遮盖图,多次遮盖同一张图时,清除缓存很重要
|
|
|
|
//本身就是遮盖的图,那么就要要替换guid
|
|
var length = Guid.Empty.ToString().Length + ".MaskNoneDicom_".Length;
|
|
|
|
var restorePath = item.Path[..^length];
|
|
|
|
maskFileName = $"{Path.GetFileName(restorePath)}.MaskNoneDicom_{batchId}";
|
|
}
|
|
else
|
|
{
|
|
//没有遮盖的,直接加上.批次_MaskDicom
|
|
maskFileName = $"{Path.GetFileName(path)}.MaskNoneDicom_{batchId}";
|
|
}
|
|
|
|
|
|
await _oSSService.UploadToOSSAsync(outputStream, prefix, maskFileName, false);
|
|
|
|
var newPath = $"/{prefix}/{maskFileName}";
|
|
|
|
okList.Add(new DownloadFileDto() { Id = item.Id, Path = newPath });
|
|
|
|
await _noneDicomStudyFileRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id, u => new NoneDicomStudyFile() { Path = newPath, IsMasked = true });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
|
|
errorList.Add(new DownloadFileDto() { Id = item.Id, Path = path });
|
|
|
|
Log.Logger.Error(ex, $"NoneDicomStudyMaskImage Error for NoneDicomStudyFileIdList Path:{path} {ex.Message}");
|
|
}
|
|
|
|
}
|
|
|
|
await _noneDicomStudyFileRepository.SaveChangesAsync();
|
|
|
|
return ResponseOutput.Ok(new { OkList = okList, ErrorList = errorList });
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// 撤销遮盖的影像,可以单张,也可以整个检查
|
|
/// </summary>
|
|
/// <param name="inCommand"></param>
|
|
/// <returns></returns>
|
|
[HttpPost]
|
|
public async Task<IResponseOutput> NoneDicomStudyUndoMaskImage(NoneDicomStudyUndoMaskImageCommand inCommand)
|
|
{
|
|
if (inCommand.NodicomStudyId == null && inCommand.NoneDicomStudyFileIdList == null)
|
|
{
|
|
return ResponseOutput.NotOk("NodicomStudyId and NoneDicomStudyFileIdList can not both be null");
|
|
}
|
|
|
|
|
|
var idPathList = new List<DownloadFileDto>();
|
|
|
|
if (inCommand.NodicomStudyId == null && inCommand.NoneDicomStudyFileIdList != null)
|
|
{
|
|
idPathList = await _noneDicomStudyFileRepository.Where(t => inCommand.NoneDicomStudyFileIdList.Contains(t.Id)).ProjectTo<DownloadFileDto>(_mapper.ConfigurationProvider).ToListAsync();
|
|
|
|
}
|
|
else
|
|
{
|
|
idPathList = await _noneDicomStudyFileRepository.Where(t => inCommand.NodicomStudyId == t.NoneDicomStudyId).ProjectTo<DownloadFileDto>(_mapper.ConfigurationProvider).ToListAsync();
|
|
|
|
}
|
|
|
|
|
|
var okList = new List<DownloadFileDto>();
|
|
|
|
foreach (var item in idPathList)
|
|
{
|
|
if (item.Path.Contains(".MaskNoneDicom_"))
|
|
{
|
|
await _oSSService.DeleteFromPrefix(item.Path, true);
|
|
|
|
var length = Guid.Empty.ToString().Length + ".MaskNoneDicom_".Length;
|
|
var newPath = item.Path[..^length];
|
|
|
|
okList.Add(new DownloadFileDto() { Id = item.Id, Path = newPath });
|
|
|
|
await _noneDicomStudyFileRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id, u => new NoneDicomStudyFile() { Path = newPath, IsMasked = false });
|
|
}
|
|
|
|
//if (item.Path.EndsWith("."))
|
|
//{
|
|
// await _oSSService.DeleteFromPrefix(item.Path, true);
|
|
|
|
// var newPath = item.Path[..^1];
|
|
|
|
// okList.Add(new InstanceIdPath() { Id = item.Id, Path = newPath });
|
|
|
|
// await _dicomInstanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id, u => new DicomInstance() { Path = newPath, IsMasked = false });
|
|
//}
|
|
|
|
|
|
}
|
|
|
|
await _noneDicomStudyFileRepository.SaveChangesAsync();
|
|
|
|
return ResponseOutput.Ok(new { OkList = okList });
|
|
|
|
|
|
}
|
|
|
|
|
|
[HttpGet]
|
|
public async Task<IResponseOutput<List<NoneDicomStudyView>>> GetNoneDicomStudyList(
|
|
[FromQuery, NotDefault] Guid subjectVisitId,
|
|
[FromQuery] Guid? nonedicomStudyId,
|
|
[FromQuery] bool isFilterZip,
|
|
[FromQuery] Guid? visitTaskId,
|
|
[FromQuery] bool isReading,
|
|
[FromQuery] bool? isImageSegmentLabel)
|
|
{
|
|
var qcAuditState = await _subjectVisitRepository.Where(s => s.Id == subjectVisitId).Select(t => t.AuditState).FirstOrDefaultAsync();
|
|
|
|
//质控过程中,因为会修改统计数字,但是此时其他人看,应该看到完整的影像
|
|
var isQCFinished = qcAuditState == AuditStateEnum.QCPassed;
|
|
|
|
//质控过程中并且不是IQC时,可以看到删除的(不需要忽略过滤器) 质控中iqc 也需要看到删除的
|
|
var isViewDelete = !isQCFinished;
|
|
|
|
var isFilterIVUSNoneDicom = false;
|
|
|
|
CriterionType? criterionType = null;
|
|
|
|
IQueryable<NoneDicomStudyView> noneDicomStudyQueryable = default;
|
|
if (visitTaskId == null || visitTaskId == Guid.Empty)
|
|
{
|
|
if (visitTaskId == null)
|
|
{
|
|
|
|
//质控过程中,需要忽略过滤质控设置删除的检查,以及设置删除的文件,质控通过后才
|
|
noneDicomStudyQueryable = _noneDicomStudyRepository.Where(t => t.SubjectVisitId == subjectVisitId, ignoreQueryFilters: isViewDelete)
|
|
.WhereIf(nonedicomStudyId != null, t => t.Id == nonedicomStudyId)
|
|
.WhereIf(isReading, t => t.IsReading && t.IsDeleted == false)
|
|
.ProjectTo<NoneDicomStudyView>(_mapper.ConfigurationProvider, new { isFilterZip = isFilterZip, isReading = isReading });
|
|
}
|
|
else
|
|
{
|
|
//靶段标注上传后查看影像
|
|
|
|
noneDicomStudyQueryable = _noneDicomStudyRepository.Where(t => t.SubjectVisitId == subjectVisitId, ignoreQueryFilters: isViewDelete)
|
|
.WhereIf(isReading, t => t.IsReading && t.IsDeleted == false)
|
|
.WhereIf(nonedicomStudyId != null, t => t.Id == nonedicomStudyId)
|
|
.ProjectTo<ImageLabelNoneDicomStudyView>(_mapper.ConfigurationProvider, new { isFilterZip = isFilterZip, isReading = isReading });
|
|
|
|
}
|
|
|
|
|
|
//ivus-在访视的时候visitTaskId==null 需要过滤掉空检查 ,但是在靶段标注的查看访视级别的时候不能过滤
|
|
if (_subjectVisitRepository.Where(t => t.Id == subjectVisitId).SelectMany(t => t.Trial.TrialReadingCriterionList)
|
|
.Where(t => (t.CriterionType == CriterionType.IVUS || t.CriterionType == CriterionType.OCT) && t.IsConfirm).Distinct().Count() == 2
|
|
&& visitTaskId == null)
|
|
{
|
|
isFilterIVUSNoneDicom = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var taskinfo = await _visitTaskRepository.Where(x => x.Id == visitTaskId).Select(t => new { t.BlindSubjectCode, t.TrialReadingCriterionId, t.TrialReadingCriterion.CriterionType, t.TrialReadingCriterion.IsImageFilter, t.TrialReadingCriterion.CriterionModalitys }).FirstNotNullAsync();
|
|
criterionType = taskinfo.CriterionType;
|
|
|
|
if ((taskinfo.CriterionType == CriterionType.IVUS || taskinfo.CriterionType == CriterionType.OCT) && isImageSegmentLabel == false)
|
|
{
|
|
//后处理原始影像预览
|
|
noneDicomStudyQueryable = _noneDicomStudyRepository.Where(t => t.SubjectVisitId == subjectVisitId, ignoreQueryFilters: isViewDelete)
|
|
.WhereIf(isReading, t => t.IsReading && t.IsDeleted == false)
|
|
.WhereIf(nonedicomStudyId != null, t => t.Id == nonedicomStudyId)
|
|
.Where(t => taskinfo.IsImageFilter ? ("|" + taskinfo.CriterionModalitys + "|").Contains("|" + t.Modality + "|") : true)
|
|
.ProjectTo<ImageLabelNoneDicomStudyView>(_mapper.ConfigurationProvider, new { isFilterZip = isFilterZip, visiTaskId = visitTaskId, isReading = isReading });
|
|
}
|
|
else
|
|
{
|
|
noneDicomStudyQueryable = _noneDicomStudyRepository.Where(t => t.TaskNoneDicomFileList.Any(t => t.VisitTaskId == visitTaskId), ignoreQueryFilters: isViewDelete)
|
|
.WhereIf(isReading, t => t.IsReading && t.IsDeleted == false)
|
|
.Where(t => taskinfo.IsImageFilter ? ("|" + taskinfo.CriterionModalitys + "|").Contains("|" + t.Modality + "|") : true)
|
|
.WhereIf(nonedicomStudyId != null, t => t.Id == nonedicomStudyId)
|
|
.ProjectTo<TaskDicomStudyView>(_mapper.ConfigurationProvider, new { isFilterZip = isFilterZip, visiTaskId = visitTaskId, isReading = isReading });
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
var list = await noneDicomStudyQueryable.Where(t => isFilterIVUSNoneDicom ? t.Modality != "IVUS" : true)
|
|
.OrderBy(x => x.ImageDate).ThenBy(x => x.CreateTime).ToListAsync();
|
|
|
|
var config = await _subjectVisitRepository.Where(t => t.Id == subjectVisitId).Select(t => new { t.Trial.ImageFormatList, t.Trial.StudyNameList, t.Trial.IsShowStudyName, AuditState = qcAuditState, CriterionType = criterionType }).FirstOrDefaultAsync();
|
|
return ResponseOutput.Ok(list, config);
|
|
}
|
|
|
|
|
|
[HttpPut]
|
|
public async Task<IResponseOutput> UpdateNoneDicomStudy(NoneDicomEdit noneDicomEdit)
|
|
{
|
|
if (_subjectVisitRepository.Any(t => t.Id == noneDicomEdit.SubjectVisitId && (t.PreliminaryAuditUserId == _userInfo.UserRoleId || t.ReviewAuditUserId == _userInfo.UserRoleId)))
|
|
{
|
|
await _noneDicomStudyRepository.UpdateFromDTOAsync(noneDicomEdit);
|
|
await _noneDicomStudyRepository.SaveChangesAsync();
|
|
}
|
|
|
|
|
|
return ResponseOutput.Ok();
|
|
}
|
|
|
|
[UnitOfWork]
|
|
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
|
public async Task<IResponseOutput<NoneDicomStudyAddReturnDto>> AddOrUpdateNoneDicomStudy(NoneDicomStudyAddOrEdit addOrEditNoneDicomStudy)
|
|
{
|
|
|
|
await _qCCommon.VerifyIsCRCSubmmitAsync(_subjectVisitRepository, _userInfo, addOrEditNoneDicomStudy.SubjectVisitId);
|
|
|
|
if (_trialRepository.Where(t => t.Id == addOrEditNoneDicomStudy.TrialId).Any(t => t.IsVerifyVisitImageDate == true))
|
|
{
|
|
await _qCCommon.VerifyStudyImageDataAsync(_subjectVisitRepository, addOrEditNoneDicomStudy.SubjectId, addOrEditNoneDicomStudy.SubjectVisitId, addOrEditNoneDicomStudy.ImageDate);
|
|
|
|
}
|
|
|
|
|
|
|
|
NoneDicomStudy? optEntity = null;
|
|
var @lock = _distributedLockProvider.CreateLock($"NoneDicomCode");
|
|
|
|
using (await @lock.AcquireAsync())
|
|
{
|
|
if (addOrEditNoneDicomStudy.Id == Guid.Empty || addOrEditNoneDicomStudy.Id == null)
|
|
{
|
|
|
|
if (_subjectVisitRepository.Where(t => t.Id == addOrEditNoneDicomStudy.SubjectVisitId).SelectMany(t => t.Trial.TrialReadingCriterionList)
|
|
.Where(t => (t.CriterionType == CriterionType.IVUS || t.CriterionType == CriterionType.OCT) && t.IsConfirm).Distinct().Count() == 2 && addOrEditNoneDicomStudy.Modality != "OCT")
|
|
{
|
|
throw new BusinessValidationFailedException(_localizer["NoneDicomStudy_OnlyNeedOCT"]);
|
|
}
|
|
|
|
//默认会是0
|
|
var code = await _trialRepository.Where(t => t.Id == addOrEditNoneDicomStudy.TrialId, ignoreQueryFilters: true).Select(x => x.MaxNoneDicomCode).DefaultIfEmpty().MaxAsync();
|
|
|
|
|
|
optEntity = await _noneDicomStudyRepository.InsertFromDTOAsync(addOrEditNoneDicomStudy);
|
|
|
|
optEntity.Code = code + 1;
|
|
|
|
await _trialRepository.BatchUpdateNoTrackingAsync(t => t.Id == addOrEditNoneDicomStudy.TrialId, u => new Trial() { MaxNoneDicomCode = optEntity.Code });
|
|
|
|
optEntity.StudyCode = AppSettings.GetCodeStr(optEntity.Code, nameof(NoneDicomStudy));
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
optEntity = await _noneDicomStudyRepository.UpdateFromDTOAsync(addOrEditNoneDicomStudy);
|
|
}
|
|
|
|
await _noneDicomStudyRepository.SaveChangesAsync();
|
|
}
|
|
|
|
|
|
NoneDicomStudyAddReturnDto noneDicom = new NoneDicomStudyAddReturnDto()
|
|
{
|
|
StudyCode = optEntity.StudyCode,
|
|
Id = optEntity.Id
|
|
};
|
|
return ResponseOutput.Ok(noneDicom);
|
|
|
|
}
|
|
|
|
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
|
[HttpDelete("{trialId:guid}/{subjectVisitId:guid}/{noneDicomStudyId:guid}")]
|
|
public async Task<IResponseOutput> DeleteNoneDicomStudy(Guid noneDicomStudyId, Guid subjectVisitId)
|
|
{
|
|
//提交了 但是IQC同意的时候 是可以删除的 | 普通提交后也不能删除
|
|
|
|
|
|
await _qCCommon.VerifyIsCRCSubmmitAsync(_subjectVisitRepository, _userInfo, subjectVisitId);
|
|
|
|
await _noneDicomStudyRepository.DeleteFromQueryAsync(noneDicomStudyId, ignoreQueryFilter: true);
|
|
|
|
await _noneDicomStudyFileRepository.DeleteFromQueryAsync(t => t.NoneDicomStudyId == noneDicomStudyId, ignoreQueryFilter: true);
|
|
|
|
//确认需求 不删除
|
|
//await _studyMonitorRepository.BatchDeleteNoTrackingAsync(t => t.StudyId == noneDicomStudyId);
|
|
|
|
await _noneDicomStudyRepository.SaveChangesAsync();
|
|
|
|
return ResponseOutput.Ok();
|
|
}
|
|
|
|
|
|
[HttpDelete("{trialId:guid}/{subjectVisitId:guid}/{noneDicomStudyFileId:guid}")]
|
|
[TrialGlobalLimit("AfterStopCannNotOpt")]
|
|
public async Task<IResponseOutput> DeleteNoneDicomStudyFile(Guid noneDicomStudyFileId, Guid subjectVisitId)
|
|
{
|
|
//提交了 但是IQC同意的时候 是可以删除的 | 普通提交后也不能删除
|
|
await _qCCommon.VerifyIsCRCSubmmitAsync(_subjectVisitRepository, _userInfo, subjectVisitId);
|
|
|
|
var noneDicomStudyFile = await _noneDicomStudyFileRepository.FirstOrDefaultAsync(t => t.Id == noneDicomStudyFileId, true);
|
|
|
|
var success = await _noneDicomStudyFileRepository.DeleteAsync(noneDicomStudyFile, true);
|
|
|
|
//维护文件数量数字
|
|
var noneDicomStudy = await _noneDicomStudyRepository.FirstOrDefaultAsync(t => t.Id == noneDicomStudyFile.NoneDicomStudyId, true);
|
|
noneDicomStudy.FileCount = await _noneDicomStudyFileRepository.CountAsync(t => t.NoneDicomStudyId == noneDicomStudyFile.NoneDicomStudyId, true);
|
|
await _noneDicomStudyRepository.SaveChangesAsync();
|
|
|
|
return ResponseOutput.Ok();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 非Dicom检查 文件列表
|
|
/// </summary>
|
|
/// <param name="noneDicomStudyId"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("{noneDicomStudyId:guid}")]
|
|
public async Task<List<NoneDicomStudyFileView>> GetNoneDicomStudyFileList(Guid noneDicomStudyId)
|
|
{
|
|
var qcAuditState = await _noneDicomStudyFileRepository.Where(t => t.NoneDicomStudyId == noneDicomStudyId).Select(t => t.NoneDicomStudy.SubjectVisit.AuditState).FirstOrDefaultAsync();
|
|
|
|
//质控过程中,因为会修改统计数字,但是此时其他人看,应该看到完整的影像
|
|
var isQCFinished = qcAuditState == AuditStateEnum.QCPassed;
|
|
|
|
//质控过程中并且不是IQC时,可以看到删除的(不需要忽略过滤器)
|
|
var isViewDelete = !isQCFinished;
|
|
|
|
return await _noneDicomStudyFileRepository.Where(t => t.NoneDicomStudyId == noneDicomStudyId, ignoreQueryFilters: isViewDelete)
|
|
.ProjectTo<NoneDicomStudyFileView>(_mapper.ConfigurationProvider).OrderBy(t => t.CreateTime).ToListAsync();
|
|
}
|
|
|
|
[HttpGet("{subjectVisitId:guid}")]
|
|
public async Task<List<NoneDicomStudyFileView>> GetVisitNoneDicomStudyFileList(Guid subjectVisitId)
|
|
{
|
|
var qcAuditState = await _subjectVisitRepository.Where(s => s.Id == subjectVisitId).Select(t => t.AuditState).FirstOrDefaultAsync();
|
|
|
|
//质控过程中,因为会修改统计数字,但是此时其他人看,应该看到完整的影像
|
|
var isQCFinished = qcAuditState == AuditStateEnum.QCPassed;
|
|
|
|
//质控过程中并且不是IQC时, 不需要忽略过滤器+质控设置删除的
|
|
var isViewDelete = !isQCFinished;
|
|
|
|
return await _noneDicomStudyFileRepository.Where(t => t.NoneDicomStudy.SubjectVisitId == subjectVisitId, ignoreQueryFilters: isViewDelete)
|
|
.ProjectTo<NoneDicomStudyFileView>(_mapper.ConfigurationProvider).ToListAsync();
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
}
|