irc-netcore-api/IRaCIS.Core.Application/Service/ImageAndDoc/NoneDicomStudyService.cs

439 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

//--------------------------------------------------------------------
// 此代码由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();
}
}
}