537 lines
23 KiB
C#
537 lines
23 KiB
C#
using IRaCIS.Application.Interfaces;
|
||
using IRaCIS.Application.ViewModels;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using IRaCIS.Core.Application.Contracts.RequestAndResponse;
|
||
using IRaCIS.Core.Application.Contracts.Dicom.DTO;
|
||
using Microsoft.Net.Http.Headers;
|
||
using Microsoft.AspNetCore.WebUtilities;
|
||
using System.Threading.Tasks;
|
||
using IRaCIS.Core.Application.Contracts.Dicom;
|
||
using System.IO;
|
||
using System.IO.Compression;
|
||
using IRaCIS.Core.API.Filter;
|
||
using Microsoft.AspNetCore.Http.Features;
|
||
using IRaCIS.Core.Application.Dicom;
|
||
using IRaCIS.Core.Domain.Share;
|
||
using System.Linq;
|
||
using System.Threading;
|
||
using Microsoft.AspNetCore.Http;
|
||
using IRaCIS.Api.Filter;
|
||
using NLog;
|
||
using Microsoft.Extensions.Logging;
|
||
using System.Text;
|
||
|
||
namespace IRaCIS.Api.Controllers
|
||
{
|
||
/// <summary>
|
||
/// Study
|
||
/// </summary>
|
||
[Route("study")]
|
||
[ApiController, Authorize, ApiExplorerSettings(GroupName = "Image")]
|
||
public class StudyController : ControllerBase
|
||
{
|
||
private readonly IStudyService _studyService;
|
||
private readonly IDicomArchiveService _dicomArchiveService;
|
||
private readonly IDoctorService _doctorService;
|
||
private static readonly FormOptions _defaultFormOptions = new FormOptions();
|
||
private readonly ILogger<StudyController> _logger;
|
||
public StudyController(IStudyService studyService,
|
||
IDicomArchiveService dicomArchiveService, IDoctorService doctorService,
|
||
ILogger<StudyController> logger
|
||
)
|
||
{
|
||
_studyService = studyService;
|
||
_dicomArchiveService = dicomArchiveService;
|
||
_doctorService = doctorService;
|
||
_logger = logger;
|
||
}
|
||
|
||
///// <summary> 根据项目Id,受试者Id,获取受试者检查统计(不分页)</summary>
|
||
//[HttpGet, Route("getStudyStatList/{trialId:guid}/{subjectId:guid}/{siteId:guid}")]
|
||
//public IResponseOutput<IEnumerable<StudyStatDTO>> GetStudy(Guid trialId, Guid subjectId, Guid siteId)
|
||
//{
|
||
// return ResponseOutput.Ok(_studyService.GetStudyStatList(trialId, subjectId, siteId));
|
||
//}
|
||
|
||
[HttpPost, Route("getStudyList")]
|
||
public IResponseOutput<PageOutput<StudyDTO>> GetStudyList(StudyQueryDTO queryDto)
|
||
{
|
||
return ResponseOutput.Ok(_studyService.GetStudyList(queryDto));
|
||
}
|
||
|
||
[HttpPost, Route("getDistributeStudyList")]
|
||
public IResponseOutput<PageOutput<DistributeReviewerStudyStatusDTO>> GetDistributeStudyList(
|
||
StudyStatusQueryDTO studyStatusQueryDto)
|
||
{
|
||
return ResponseOutput.Ok(_studyService.GetDistributeStudyList(studyStatusQueryDto));
|
||
}
|
||
|
||
|
||
[HttpPost, Route("updateStudyBinding")]
|
||
//[LogFilter]
|
||
public IResponseOutput UpdateStudyBinding(StudyEditCommand studyEditCommand)
|
||
{
|
||
|
||
if (studyEditCommand.SubjectVisitId == Guid.Empty)
|
||
{
|
||
studyEditCommand.SubjectVisitId = _studyService.AddSubjectVisit(studyEditCommand.SubjectId, studyEditCommand.SiteId,
|
||
studyEditCommand.VisitStageId, studyEditCommand.SVSTDTC, studyEditCommand.SVENDTC);
|
||
}
|
||
else
|
||
{
|
||
_studyService.UpdateSubjectVisit(studyEditCommand.SubjectVisitId, studyEditCommand.SVSTDTC,
|
||
studyEditCommand.SVENDTC);
|
||
|
||
}
|
||
return ResponseOutput.Ok( _studyService.UpdateStudyBinding(studyEditCommand).Result);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取受试者 这次访视 对应的study modality 列表
|
||
/// </summary>
|
||
/// <param name="trialId"></param>
|
||
/// <param name="siteId"></param>
|
||
/// <param name="subjectId"></param>
|
||
/// <param name="subjectVisitId"></param>
|
||
/// <returns></returns>
|
||
[HttpPost, Route("getSubjectVisitStudyList/{trialId:guid}/{siteId:guid}/{subjectId:guid}/{subjectVisitId:guid}")]
|
||
[AllowAnonymous]
|
||
public IResponseOutput<List<SubjectVisitStudyDTO>> GetSubjectVisitStudyList(Guid trialId, Guid siteId, Guid subjectId,
|
||
Guid subjectVisitId)
|
||
{
|
||
return ResponseOutput.Ok(
|
||
_studyService.GetSubjectVisitStudyList(trialId, siteId, subjectId, subjectVisitId));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 提示 该项目是否允许上传该study,如果不允许 提示 已经上传到哪个受试者 哪次方式 studyCode是多少
|
||
/// </summary>
|
||
/// <param name="studyInstanceUid"></param>
|
||
/// <param name="trialId"></param>
|
||
/// <param name="abandonStudyId"></param>
|
||
/// <returns></returns>
|
||
[HttpGet, Route("verifyStudyAllowUpload/{trialId:guid}/{studyInstanceUid}/{abandonStudyId?}")]
|
||
[AllowAnonymous]
|
||
public IResponseOutput<VerifyStudyUploadResult> VerifyStudyAllowUpload(string studyInstanceUid, Guid trialId, Guid? abandonStudyId)
|
||
{
|
||
return ResponseOutput.Ok(_studyService.VerifyStudyAllowUpload(studyInstanceUid, trialId, abandonStudyId));
|
||
}
|
||
|
||
/// <summary> 归档</summary>
|
||
//[HttpPost, Route("archiveStudy/{trialId:guid}/{siteId:guid}/{subjectId:guid}/{visitStageId:guid}/{subjectVisitId:guid}/{SVSTDTC?}/{SVENDTC?}/{comment}")]
|
||
[HttpPost, Route("archiveStudy")]
|
||
[DisableFormValueModelBinding]
|
||
[DisableRequestSizeLimit]
|
||
[LogFilter]
|
||
|
||
//public async Task<IResponseOutput> ArchiveStudy(Guid trialId, Guid siteId, Guid subjectId, Guid visitStageId, Guid subjectVisitId, DateTime? SVSTDTC, DateTime? SVENDTC, string comment)
|
||
|
||
public async Task<IResponseOutput> ArchiveStudy([FromForm] ArchiveStudyCommand archiveStudyCommand)
|
||
{
|
||
|
||
//处理 受试者访视没有记录 系统自动添加
|
||
|
||
#region url方式
|
||
//if (subjectVisitId == Guid.Empty)
|
||
//{
|
||
// subjectVisitId = _studyService.AddSubjectVisit(subjectId, visitStageId);
|
||
//}
|
||
|
||
//string userName = User.FindFirst("realName").Value;
|
||
//StudyCommand addtionalInfo = new StudyCommand
|
||
//{
|
||
// SiteId = siteId,
|
||
// SubjectId = subjectId,
|
||
// TrialId = trialId,
|
||
// SubjectVisitId = subjectVisitId,
|
||
|
||
//};
|
||
|
||
//var savedInfo = _studyService.GetSaveToDicomInfo(addtionalInfo);
|
||
////加入comment
|
||
//savedInfo.Comment = comment;
|
||
|
||
#endregion
|
||
|
||
try
|
||
{
|
||
|
||
|
||
#region post body 方式 subjectVisitId
|
||
|
||
|
||
|
||
////var subjectVisitStudyOldList = new List<SubjectVisitStudyDTO>();
|
||
// 受试者方式计划一次性添加,因为没有了上传时添加逻辑
|
||
//if (archiveStudyCommand.SubjectVisitId == Guid.Empty)
|
||
//{
|
||
// archiveStudyCommand.SubjectVisitId = _studyService.AddSubjectVisit(archiveStudyCommand.SubjectId, archiveStudyCommand.SiteId,
|
||
// archiveStudyCommand.VisitStageId.Value, archiveStudyCommand.SVSTDTC, archiveStudyCommand.SVENDTC);
|
||
//}
|
||
//else
|
||
//{
|
||
//_studyService.UpdateSubjectVisit(archiveStudyCommand.SubjectVisitId, archiveStudyCommand.SVSTDTC,
|
||
// archiveStudyCommand.SVENDTC);
|
||
|
||
//#region 重传和上传放在一起 暂不用
|
||
////处理 重传 在归档前 事先查出 这次访视已经有的study列表
|
||
////subjectVisitStudyOldList = _studyService.GetSubjectVisitStudyList(archiveStudyCommand.TrialId,
|
||
//// archiveStudyCommand.SiteId, archiveStudyCommand.SubjectId, archiveStudyCommand.SubjectVisitId);
|
||
//#endregion
|
||
|
||
//}
|
||
|
||
|
||
|
||
var userName = User.FindFirst("realName").Value;
|
||
StudyCommand addtionalInfo = new StudyCommand
|
||
{
|
||
SiteId = archiveStudyCommand.SiteId,
|
||
SubjectId = archiveStudyCommand.SubjectId,
|
||
TrialId = archiveStudyCommand.TrialId,
|
||
SubjectVisitId = archiveStudyCommand.SubjectVisitId,
|
||
|
||
};
|
||
|
||
var savedInfo = _studyService.GetSaveToDicomInfo(addtionalInfo);
|
||
|
||
|
||
#endregion
|
||
|
||
|
||
var archiveResult = new DicomArchiveResult();
|
||
var archivedStudyIds = new List<Guid>();
|
||
|
||
var boundary = HeaderUtilities.RemoveQuotes(MediaTypeHeaderValue.Parse(Request.ContentType).Boundary).Value;
|
||
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
|
||
var section = await reader.ReadNextSectionAsync();
|
||
while (section != null)
|
||
{
|
||
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
|
||
if (hasContentDispositionHeader)
|
||
{
|
||
string fileName = contentDisposition.FileName.Value;
|
||
try
|
||
{
|
||
//采用post方式 这里多加一个判断 过滤其他参数
|
||
if (string.IsNullOrEmpty(section.ContentType))
|
||
{
|
||
break;
|
||
}
|
||
|
||
string mediaType = section.ContentType;
|
||
|
||
if (mediaType.Contains("zip"))
|
||
{
|
||
var partStream = section.Body;
|
||
using (var zipArchive = new ZipArchive(partStream, ZipArchiveMode.Read))
|
||
{
|
||
foreach (var entry in zipArchive.Entries)
|
||
{
|
||
if (entry.FullName.EndsWith("/")) continue;
|
||
|
||
try
|
||
{
|
||
++archiveResult.ReceivedFileCount;
|
||
|
||
using (var memoryStream = new MemoryStream())
|
||
{
|
||
await section.Body.CopyToAsync(memoryStream);
|
||
|
||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||
DicomInstanceDTO instance = await _dicomArchiveService.ArchiveDicomStreamAsync(memoryStream, savedInfo);
|
||
if (!archivedStudyIds.Contains(instance.StudyId))
|
||
archivedStudyIds.Add(instance.StudyId);
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
archiveResult.ErrorFiles.Add($"{fileName}/{entry.FullName}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
++archiveResult.ReceivedFileCount;
|
||
|
||
if (mediaType.Contains("octet-stream"))
|
||
{
|
||
using (var memoryStream = new MemoryStream())
|
||
{
|
||
await section.Body.CopyToAsync(memoryStream);
|
||
memoryStream.Seek(0, SeekOrigin.Begin);
|
||
|
||
DicomInstanceDTO instance = await _dicomArchiveService.ArchiveDicomStreamAsync(memoryStream, savedInfo);
|
||
if (!archivedStudyIds.Contains(instance.StudyId))
|
||
archivedStudyIds.Add(instance.StudyId);
|
||
}
|
||
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
Console.WriteLine(e);
|
||
archiveResult.ErrorFiles.Add(fileName);
|
||
}
|
||
}
|
||
|
||
section = await reader.ReadNextSectionAsync();
|
||
}
|
||
|
||
if (archivedStudyIds.Count > 0)
|
||
{
|
||
//更新受试者访视信息 及上传状态
|
||
_studyService.UpdateSubjectVisit(archiveStudyCommand.SubjectVisitId, archiveStudyCommand.SVSTDTC,
|
||
archiveStudyCommand.SVENDTC);
|
||
|
||
//更新受试者最新的访视状态,访视号最大的
|
||
_studyService.UpdateSubjectLatestInfo(archiveStudyCommand.SubjectId, archiveStudyCommand.TrialId);
|
||
|
||
|
||
|
||
archiveResult.ArchivedDicomStudies = _dicomArchiveService.GetArchivedStudyList(archivedStudyIds);
|
||
|
||
//归档完成,插入记录表
|
||
|
||
foreach (var archivedDicomStudy in archiveResult.ArchivedDicomStudies)
|
||
{
|
||
var seriesModalityList = _dicomArchiveService.GetSeriesList(archivedDicomStudy.Id).Select(u => u.Modality).Distinct();
|
||
StringBuilder sb = new StringBuilder();
|
||
foreach (var item in seriesModalityList)
|
||
{
|
||
sb.Append(item).Append(",");
|
||
}
|
||
string ModaliyStr = sb.ToString();
|
||
if (ModaliyStr.Length > 0)
|
||
{
|
||
ModaliyStr = ModaliyStr.Substring(0, ModaliyStr.Length - 1);
|
||
}
|
||
|
||
string comment = string.Empty;// 上传或重传完成之后,更新Comment信息
|
||
if (!string.IsNullOrWhiteSpace(archiveStudyCommand.Comment))
|
||
{
|
||
comment = archiveStudyCommand.Comment;
|
||
}
|
||
_studyService.UpdateStudyModaliyAndComment(archivedDicomStudy.Id, ModaliyStr,comment);
|
||
|
||
|
||
|
||
//重传 并且不是同一份文件
|
||
if (archiveStudyCommand.AbandonStudyId != null &&
|
||
archiveStudyCommand.AbandonStudyId != Guid.Empty && archiveStudyCommand.AbandonStudyId != archivedDicomStudy.Id)
|
||
{
|
||
|
||
//更新状态为废弃
|
||
_studyService.UpdateStudyStatus(new StudyStatusDetailCommand
|
||
{
|
||
StudyId = archiveStudyCommand.AbandonStudyId.Value,
|
||
Status = (int)StudyStatus.Abandon,
|
||
Note = string.Empty
|
||
}, userName);
|
||
|
||
|
||
|
||
//此时归档的新的Study 初始状态设置为 已重传完成
|
||
_studyService.UpdateStudyStatus(new StudyStatusDetailCommand
|
||
{
|
||
StudyId = archivedDicomStudy.Id,
|
||
Status = (int)StudyStatus.QAReuploaded,
|
||
Note = string.Empty
|
||
}, userName);
|
||
|
||
//更新新得study code 为废弃的
|
||
_studyService.UpdateReUploadNewStudyCode(archiveStudyCommand.AbandonStudyId.Value, archivedDicomStudy.Id);
|
||
|
||
}//重传 是同一份文件
|
||
else if (archiveStudyCommand.AbandonStudyId != null &&
|
||
archiveStudyCommand.AbandonStudyId != Guid.Empty && archiveStudyCommand.AbandonStudyId == archivedDicomStudy.Id)
|
||
{
|
||
//此时归档的新的Study 初始状态设置为 已重传完成
|
||
_studyService.UpdateStudyStatus(new StudyStatusDetailCommand
|
||
{
|
||
StudyId = archivedDicomStudy.Id,
|
||
Status = (int)StudyStatus.QAReuploaded,
|
||
Note = string.Empty
|
||
}, userName);
|
||
}//上传
|
||
else
|
||
{
|
||
_studyService.UpdateStudyStatus(new StudyStatusDetailCommand
|
||
{
|
||
StudyId = archivedDicomStudy.Id,
|
||
Status = (int)StudyStatus.Uploaded,
|
||
Note = string.Empty
|
||
}, userName);
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
return ResponseOutput.Ok(archiveResult);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex.Message);
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary> 删除检查</summary>
|
||
[HttpDelete, Route("deleteStudy/{id:guid}")]
|
||
[LogFilter]
|
||
public IResponseOutput DeleteStudy(Guid id)
|
||
{
|
||
return _studyService.DeleteStudy(id);
|
||
}
|
||
|
||
/// <summary> 指定资源Id,渲染Dicom检查的Jpeg预览图像 </summary>
|
||
/// <param name="studyId"> Dicom检查的Id </param>
|
||
[HttpGet, Route("preview/{studyId:guid}")]
|
||
[AllowAnonymous]
|
||
public FileContentResult GetStudyPreview(Guid studyId)
|
||
{
|
||
string path = _dicomArchiveService.GetStudyPreview(studyId);
|
||
|
||
using (var sw = DicomRenderingHelper.RenderPreviewJpeg(path))
|
||
{
|
||
var bytes = new byte[sw.Length];
|
||
sw.Read(bytes, 0, bytes.Length);
|
||
sw.Close();
|
||
return new FileContentResult(bytes, "image/jpeg");
|
||
}
|
||
}
|
||
|
||
/// <summary> 指定资源Id,获取Dicom检查信息 </summary>
|
||
/// <param name="studyId"> Dicom检查的Id </param>
|
||
[HttpGet, Route("item/{studyId:guid}")]
|
||
public IResponseOutput<DicomStudyDTO> GetStudyItem(Guid studyId)
|
||
{
|
||
return ResponseOutput.Ok(_dicomArchiveService.GetStudyItem(studyId));
|
||
}
|
||
|
||
/// <summary> 更新Study状态,并保存状态变更信息 </summary>
|
||
/// <param name="studyStatusDetailCommand"></param>
|
||
[HttpPost, Route("updateStudyStatus")]
|
||
[LogFilter]
|
||
public IResponseOutput UpdateStudyStatus(StudyStatusDetailCommand studyStatusDetailCommand)
|
||
{
|
||
string userName = User.FindFirst("realName").Value; ;
|
||
return ResponseOutput.Result(_studyService.UpdateStudyStatus(studyStatusDetailCommand, userName));
|
||
}
|
||
/// <summary>
|
||
/// Dicom匿名化
|
||
/// </summary>
|
||
/// <param name="studyId">需要匿名化的检查Id</param>
|
||
/// <returns></returns>
|
||
[HttpPost, Route("dicomAnonymize/{studyId:guid}")]
|
||
[LogFilter]
|
||
public async Task<IResponseOutput> DicomAnonymize(Guid studyId)
|
||
{
|
||
string userName = User.FindFirst("realName").Value; ;
|
||
return ResponseOutput.Result(await _studyService.DicomAnonymize(studyId, userName));
|
||
}
|
||
|
||
/// <summary> Study分配 </summary>
|
||
[HttpPost, Route("studyDistribution")]
|
||
[Obsolete]
|
||
public IResponseOutput StudyDistribution(StudyReviewerCommand studyReviewerCommand)
|
||
{
|
||
string userName = User.FindFirst("realName").Value;
|
||
var doctorInfo = _doctorService.GetDoctorBasicInfo(studyReviewerCommand.ReviewerId);
|
||
List<StudyStatusDetailCommand> studyStatusCommandList = new List<StudyStatusDetailCommand>();
|
||
foreach (var study in studyReviewerCommand.StudyList)
|
||
{
|
||
var studyStatus = new StudyStatusDetailCommand
|
||
{
|
||
StudyId = study.StudyId,
|
||
Status = (int)StudyStatus.Distributed,
|
||
Note = doctorInfo.LastName + " " + doctorInfo.FirstName
|
||
};
|
||
studyStatusCommandList.Add(studyStatus);
|
||
}
|
||
|
||
var result = _studyService.DistributeStudy(studyReviewerCommand);
|
||
_studyService.UpdateStudyStatus(studyStatusCommandList, userName);
|
||
return ResponseOutput.Result(result);
|
||
}
|
||
|
||
[Obsolete]
|
||
[HttpPost, Route("editStudyReviewer")]
|
||
public IResponseOutput EditStudyReviewer(StudyReviewerEditCommand studyReviewerEditCommand)
|
||
{
|
||
return _studyService.EditStudyReviewer(studyReviewerEditCommand);
|
||
|
||
}
|
||
/// <summary>
|
||
/// 根据项目Id 获取可选医生列表
|
||
/// </summary>
|
||
/// <param name="trialId"></param>
|
||
/// <returns></returns>
|
||
[HttpGet, Route("GetReviewerList/{trialId:guid}")]
|
||
public IResponseOutput<List<ReviewerDistributionDTO>> GetReviewerListByTrialId(Guid trialId)
|
||
{
|
||
var result = _studyService.GetReviewerListByTrialId(trialId);
|
||
return ResponseOutput.Ok(result);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据StudyId获取该Study的操作记录,时间倒序
|
||
/// </summary>
|
||
/// <param name="studyId"></param>
|
||
/// <returns></returns>
|
||
[HttpGet, Route("getStudyStatusDetailList/{studyId:guid}")]
|
||
public IResponseOutput<List<StudyStatusDetailDTO>> GetStudyStatusDetailList(Guid studyId)
|
||
{
|
||
var result = _studyService.GetStudyStatusDetailList(studyId);
|
||
return ResponseOutput.Ok(result);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据TPCode 获取所有的标记
|
||
/// </summary>
|
||
/// <param name="tpCode"></param>
|
||
/// <returns></returns>
|
||
[HttpGet, Route("getImageLabelList/{tpCode}")]
|
||
[AllowAnonymous]
|
||
public IResponseOutput<IEnumerable<ImageLabelDTO>> GetImageLabelList(string tpCode)
|
||
{
|
||
return ResponseOutput.Ok(_dicomArchiveService.GetImageLabel(tpCode));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存标记(跟删除合并,每次保存最新的标记),会删除替换之前的标记
|
||
/// 外层的TPcode 必须传,里面的标记数组可为空数组,表示删除该Study的所有标记
|
||
/// </summary>
|
||
/// <param name="imageLabelCommand"></param>
|
||
/// <returns></returns>
|
||
[HttpPost, Route("saveImageLabelList")]
|
||
[AllowAnonymous]
|
||
[LogFilter]
|
||
public IResponseOutput SaveImageLabelList(ImageLabelCommand imageLabelCommand)
|
||
{
|
||
return ResponseOutput.Result(_dicomArchiveService.SaveImageLabelList(imageLabelCommand));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取某个访视的关联访视
|
||
/// 用于获取关联影像(调用之前的接口:/series/list/,根据StudyId,获取访视的序列列表)
|
||
/// </summary>
|
||
/// <param name="visitNum"></param>
|
||
/// <param name="tpCode"></param>
|
||
/// <returns></returns>
|
||
[HttpGet, Route("getRelationVisitList/{visitNum}/{tpCode}")]
|
||
[AllowAnonymous]
|
||
public IResponseOutput<IEnumerable<RelationVisitDTO>> GetRelationVisitList(decimal visitNum, string tpCode)
|
||
{
|
||
return ResponseOutput.Ok(_studyService.GetRelationVisitList(visitNum, tpCode));
|
||
}
|
||
|
||
|
||
|
||
}
|
||
}
|