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 { /// /// Study /// [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 _logger; public StudyController(IStudyService studyService, IDicomArchiveService dicomArchiveService, IDoctorService doctorService, ILogger logger ) { _studyService = studyService; _dicomArchiveService = dicomArchiveService; _doctorService = doctorService; _logger = logger; } ///// 根据项目Id,受试者Id,获取受试者检查统计(不分页) //[HttpGet, Route("getStudyStatList/{trialId:guid}/{subjectId:guid}/{siteId:guid}")] //public IResponseOutput> GetStudy(Guid trialId, Guid subjectId, Guid siteId) //{ // return ResponseOutput.Ok(_studyService.GetStudyStatList(trialId, subjectId, siteId)); //} [HttpPost, Route("getStudyList")] public IResponseOutput> GetStudyList(StudyQueryDTO queryDto) { return ResponseOutput.Ok(_studyService.GetStudyList(queryDto)); } [HttpPost, Route("getDistributeStudyList")] public IResponseOutput> 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); } /// /// 获取受试者 这次访视 对应的study modality 列表 /// /// /// /// /// /// [HttpPost, Route("getSubjectVisitStudyList/{trialId:guid}/{siteId:guid}/{subjectId:guid}/{subjectVisitId:guid}")] [AllowAnonymous] public IResponseOutput> GetSubjectVisitStudyList(Guid trialId, Guid siteId, Guid subjectId, Guid subjectVisitId) { return ResponseOutput.Ok( _studyService.GetSubjectVisitStudyList(trialId, siteId, subjectId, subjectVisitId)); } /// /// 提示 该项目是否允许上传该study,如果不允许 提示 已经上传到哪个受试者 哪次方式 studyCode是多少 /// /// /// /// /// [HttpGet, Route("verifyStudyAllowUpload/{trialId:guid}/{studyInstanceUid}/{abandonStudyId?}")] [AllowAnonymous] public IResponseOutput VerifyStudyAllowUpload(string studyInstanceUid, Guid trialId, Guid? abandonStudyId) { return ResponseOutput.Ok(_studyService.VerifyStudyAllowUpload(studyInstanceUid, trialId, abandonStudyId)); } /// 归档 //[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 ArchiveStudy(Guid trialId, Guid siteId, Guid subjectId, Guid visitStageId, Guid subjectVisitId, DateTime? SVSTDTC, DateTime? SVENDTC, string comment) public async Task 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(); // 受试者方式计划一次性添加,因为没有了上传时添加逻辑 //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(); 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; } } /// 删除检查 [HttpDelete, Route("deleteStudy/{id:guid}")] [LogFilter] public IResponseOutput DeleteStudy(Guid id) { return _studyService.DeleteStudy(id); } /// 指定资源Id,渲染Dicom检查的Jpeg预览图像 /// Dicom检查的Id [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"); } } /// 指定资源Id,获取Dicom检查信息 /// Dicom检查的Id [HttpGet, Route("item/{studyId:guid}")] public IResponseOutput GetStudyItem(Guid studyId) { return ResponseOutput.Ok(_dicomArchiveService.GetStudyItem(studyId)); } /// 更新Study状态,并保存状态变更信息 /// [HttpPost, Route("updateStudyStatus")] [LogFilter] public IResponseOutput UpdateStudyStatus(StudyStatusDetailCommand studyStatusDetailCommand) { string userName = User.FindFirst("realName").Value; ; return ResponseOutput.Result(_studyService.UpdateStudyStatus(studyStatusDetailCommand, userName)); } /// /// Dicom匿名化 /// /// 需要匿名化的检查Id /// [HttpPost, Route("dicomAnonymize/{studyId:guid}")] [LogFilter] public async Task DicomAnonymize(Guid studyId) { string userName = User.FindFirst("realName").Value; ; return ResponseOutput.Result(await _studyService.DicomAnonymize(studyId, userName)); } /// Study分配 [HttpPost, Route("studyDistribution")] [Obsolete] public IResponseOutput StudyDistribution(StudyReviewerCommand studyReviewerCommand) { string userName = User.FindFirst("realName").Value; var doctorInfo = _doctorService.GetDoctorBasicInfo(studyReviewerCommand.ReviewerId); List studyStatusCommandList = new List(); 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); } /// /// 根据项目Id 获取可选医生列表 /// /// /// [HttpGet, Route("GetReviewerList/{trialId:guid}")] public IResponseOutput> GetReviewerListByTrialId(Guid trialId) { var result = _studyService.GetReviewerListByTrialId(trialId); return ResponseOutput.Ok(result); } /// /// 根据StudyId获取该Study的操作记录,时间倒序 /// /// /// [HttpGet, Route("getStudyStatusDetailList/{studyId:guid}")] public IResponseOutput> GetStudyStatusDetailList(Guid studyId) { var result = _studyService.GetStudyStatusDetailList(studyId); return ResponseOutput.Ok(result); } /// /// 根据TPCode 获取所有的标记 /// /// /// [HttpGet, Route("getImageLabelList/{tpCode}")] [AllowAnonymous] public IResponseOutput> GetImageLabelList(string tpCode) { return ResponseOutput.Ok(_dicomArchiveService.GetImageLabel(tpCode)); } /// /// 保存标记(跟删除合并,每次保存最新的标记),会删除替换之前的标记 /// 外层的TPcode 必须传,里面的标记数组可为空数组,表示删除该Study的所有标记 /// /// /// [HttpPost, Route("saveImageLabelList")] [AllowAnonymous] [LogFilter] public IResponseOutput SaveImageLabelList(ImageLabelCommand imageLabelCommand) { return ResponseOutput.Result(_dicomArchiveService.SaveImageLabelList(imageLabelCommand)); } /// /// 获取某个访视的关联访视 /// 用于获取关联影像(调用之前的接口:/series/list/,根据StudyId,获取访视的序列列表) /// /// /// /// [HttpGet, Route("getRelationVisitList/{visitNum}/{tpCode}")] [AllowAnonymous] public IResponseOutput> GetRelationVisitList(decimal visitNum, string tpCode) { return ResponseOutput.Ok(_studyService.GetRelationVisitList(visitNum, tpCode)); } } }