Merge branch 'Uat_IRC_Net10' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Uat_IRC_Net10
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -1080,12 +1080,15 @@ public static class DicomSortHelper
|
|||||||
IEnumerable<T> source,
|
IEnumerable<T> source,
|
||||||
Func<T, string?> ippSelector,
|
Func<T, string?> ippSelector,
|
||||||
Func<T, string?> iopSelector,
|
Func<T, string?> iopSelector,
|
||||||
Func<T, int?> instanceNumberSelector)
|
Func<T, int?> instanceNumberSelector,string seriesDescription)
|
||||||
{
|
{
|
||||||
var list = source.ToList();
|
var list = source.ToList();
|
||||||
if (list.Count < 2)
|
if (list.Count < 2)
|
||||||
return list;
|
return list;
|
||||||
|
|
||||||
|
if(seriesDescription.Contains("Dixon", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return list.OrderBy(instanceNumberSelector).ToList();
|
||||||
|
|
||||||
var first = list[0];
|
var first = list[0];
|
||||||
|
|
||||||
var reference = ParseVector(ippSelector(first));
|
var reference = ParseVector(ippSelector(first));
|
||||||
|
|||||||
@@ -1787,7 +1787,7 @@
|
|||||||
<param name="_trialRepository"></param>
|
<param name="_trialRepository"></param>
|
||||||
<param name="_oSSService"></param>
|
<param name="_oSSService"></param>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:IRaCIS.Core.Application.Service.TrialImageDownloadService.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Trial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectVisit},IRaCIS.Core.Application.Helper.IOSSService,Microsoft.AspNetCore.Hosting.IWebHostEnvironment,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomStudy},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomSeries},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomInstance})">
|
<member name="M:IRaCIS.Core.Application.Service.TrialImageDownloadService.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Trial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectVisit},IRaCIS.Core.Application.Helper.IOSSService,Microsoft.AspNetCore.Hosting.IWebHostEnvironment,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomStudy},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomSeries},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomInstance},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TaskInstance},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.VisitTask})">
|
||||||
<summary>
|
<summary>
|
||||||
项目影像后台下载,不打包
|
项目影像后台下载,不打包
|
||||||
</summary>
|
</summary>
|
||||||
@@ -10621,6 +10621,16 @@
|
|||||||
裁判结果的备注
|
裁判结果的备注
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.GetJudgeFormOutDto.TriggerJudgeQuestionIds">
|
||||||
|
<summary>
|
||||||
|
本次生成裁判的触发问题(包含自动计算题追溯到的源问题)
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.JudgeFormQuestionDto.IsTriggerJudge">
|
||||||
|
<summary>
|
||||||
|
是否为本次生成裁判的触发问题
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.GlobalVisitJudgeQuestion.ShowOrder">
|
<member name="P:IRaCIS.Core.Application.Service.Reading.Dto.GlobalVisitJudgeQuestion.ShowOrder">
|
||||||
<summary>
|
<summary>
|
||||||
排序
|
排序
|
||||||
@@ -15395,6 +15405,11 @@
|
|||||||
获取裁判表单
|
获取裁判表单
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="M:IRaCIS.Core.Application.Service.ReadingImageTaskService.GetTriggerJudgeQuestionIds(System.Collections.Generic.List{System.Guid},System.Collections.Generic.List{IRaCIS.Core.Domain.Models.ReadingQuestionTrial},System.Decimal,IRaCIS.Core.Domain.Models.JudgeRule)">
|
||||||
|
<summary>
|
||||||
|
重新按当前访视的裁判规则计算实际触发裁判的问题。
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="M:IRaCIS.Core.Application.Service.ReadingImageTaskService.SetJudgeForm(IRaCIS.Core.Application.Service.Reading.Dto.SetJudgeFormInDto)">
|
<member name="M:IRaCIS.Core.Application.Service.ReadingImageTaskService.SetJudgeForm(IRaCIS.Core.Application.Service.Reading.Dto.SetJudgeFormInDto)">
|
||||||
<summary>
|
<summary>
|
||||||
编辑裁判表单
|
编辑裁判表单
|
||||||
@@ -15464,7 +15479,7 @@
|
|||||||
<param name="visiTaskId"></param>
|
<param name="visiTaskId"></param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:IRaCIS.Core.Application.Service.ReadingImageTaskService.ComputeJudgeResult(System.Collections.Generic.List{IRaCIS.Core.Application.Service.Reading.Dto.GroupTaskAnswerDto},System.Guid)">
|
<member name="M:IRaCIS.Core.Application.Service.ReadingImageTaskService.ComputeJudgeResult(System.Collections.Generic.List{IRaCIS.Core.Application.Service.Reading.Dto.GroupTaskAnswerDto},System.Guid,System.Collections.Generic.HashSet{System.Guid},System.Boolean)">
|
||||||
<summary>
|
<summary>
|
||||||
计算返回的结果 为True表示不相等
|
计算返回的结果 为True表示不相等
|
||||||
</summary>
|
</summary>
|
||||||
@@ -16795,7 +16810,7 @@
|
|||||||
<param name="regions"></param>
|
<param name="regions"></param>
|
||||||
<param name="options"></param>
|
<param name="options"></param>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:IRaCIS.Core.Application.Helper.DicomSortHelper.SortSlices``1(System.Collections.Generic.IEnumerable{``0},System.Func{``0,System.String},System.Func{``0,System.String},System.Func{``0,System.Nullable{System.Int32}})">
|
<member name="M:IRaCIS.Core.Application.Helper.DicomSortHelper.SortSlices``1(System.Collections.Generic.IEnumerable{``0},System.Func{``0,System.String},System.Func{``0,System.String},System.Func{``0,System.Nullable{System.Int32}},System.String)">
|
||||||
<summary>
|
<summary>
|
||||||
DICOM Slice 排序(IPP + IOP)
|
DICOM Slice 排序(IPP + IOP)
|
||||||
自动 fallback InstanceNumber
|
自动 fallback InstanceNumber
|
||||||
@@ -17788,17 +17803,17 @@
|
|||||||
</member>
|
</member>
|
||||||
<member name="F:IRaCIS.Core.Application.ViewModel.AccessToDialogueEnum.Question">
|
<member name="F:IRaCIS.Core.Application.ViewModel.AccessToDialogueEnum.Question">
|
||||||
<summary>
|
<summary>
|
||||||
����
|
质疑
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="F:IRaCIS.Core.Application.ViewModel.AccessToDialogueEnum.Consistency">
|
<member name="F:IRaCIS.Core.Application.ViewModel.AccessToDialogueEnum.Consistency">
|
||||||
<summary>
|
<summary>
|
||||||
һ���Ժ˲�
|
一致性核查
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="T:IRaCIS.Core.Application.ViewModel.CopyFrontAuditConfigItemDto">
|
<member name="T:IRaCIS.Core.Application.ViewModel.CopyFrontAuditConfigItemDto">
|
||||||
<summary>
|
<summary>
|
||||||
����
|
复制
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="T:IRaCIS.Core.Application.ViewModel.SystemNoticeView">
|
<member name="T:IRaCIS.Core.Application.ViewModel.SystemNoticeView">
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ namespace IRaCIS.Core.Application.Service
|
|||||||
public class TrialImageDownloadService(IRepository<Trial> _trialRepository, IRepository<SubjectVisit> _subjectVisitRepository, IOSSService _oSSService, IWebHostEnvironment _hostEnvironment,
|
public class TrialImageDownloadService(IRepository<Trial> _trialRepository, IRepository<SubjectVisit> _subjectVisitRepository, IOSSService _oSSService, IWebHostEnvironment _hostEnvironment,
|
||||||
IRepository<DicomStudy> _studyRepository,
|
IRepository<DicomStudy> _studyRepository,
|
||||||
IRepository<DicomSeries> _seriesRepository,
|
IRepository<DicomSeries> _seriesRepository,
|
||||||
IRepository<DicomInstance> _instanceRepository) : BaseService
|
IRepository<DicomInstance> _instanceRepository,
|
||||||
|
IRepository<TaskInstance> _taskInstanceRepository,
|
||||||
|
IRepository<VisitTask> _visitTaskRepository
|
||||||
|
) : BaseService
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
@@ -491,6 +494,325 @@ namespace IRaCIS.Core.Application.Service
|
|||||||
return ResponseOutput.Ok();
|
return ResponseOutput.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<IResponseOutput> DownloadExcelTrialTaskImageZIPStream(Guid trialId)
|
||||||
|
{
|
||||||
|
var trialInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.ResearchProgramNo }).FirstOrDefault();
|
||||||
|
|
||||||
|
#region 设置目录
|
||||||
|
|
||||||
|
var rootFolder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment);
|
||||||
|
Directory.CreateDirectory(rootFolder);
|
||||||
|
|
||||||
|
// 获取无效字符(系统定义的)
|
||||||
|
string invalidChars = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
|
||||||
|
|
||||||
|
// 用正则表达式替换所有非法字符为下划线或空字符
|
||||||
|
string pattern = $"[{Regex.Escape(invalidChars)}]";
|
||||||
|
|
||||||
|
var regexNo = Regex.Replace(trialInfo.ResearchProgramNo, pattern, "_");
|
||||||
|
|
||||||
|
// 创建一个临时文件夹来存放文件
|
||||||
|
string trialFolderPath = Path.Combine(rootFolder, $"{regexNo}");//_{NewId.NextGuid()}
|
||||||
|
Directory.CreateDirectory(trialFolderPath);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
var oldVisits = MiniExcel.Query<SubjectTaskExcel>(Path.Combine(rootFolder, "Old.xlsx")).ToList();
|
||||||
|
|
||||||
|
var downloadTasks = MiniExcel.Query<SubjectTaskExcel>(Path.Combine(rootFolder, "download.xlsx")).ToList().Where(t => t.SubjectCode.IsNotNullOrEmpty() && t.TaskName.IsNotNullOrEmpty() && t.TaskBlindName.IsNotNullOrEmpty()).ToList();
|
||||||
|
|
||||||
|
downloadTasks = downloadTasks.Where(t => !oldVisits.Any(old => old.SubjectCode == t.SubjectCode && old.TaskBlindName == t.TaskBlindName && old.TaskName.Trim().ToLower() == t.TaskName.Trim().ToLower())).ToList();
|
||||||
|
|
||||||
|
var visitIndex = 0;
|
||||||
|
var skipCount = 0;
|
||||||
|
foreach (var downloadTask in downloadTasks)
|
||||||
|
{
|
||||||
|
visitIndex++;
|
||||||
|
var downloadInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new
|
||||||
|
{
|
||||||
|
t.ResearchProgramNo,
|
||||||
|
t.TrialCode,
|
||||||
|
|
||||||
|
TaskList = t.VisitTaskList.Where(t => t.TaskState == TaskState.Effect && t.DoctorUser.UserName == downloadTask.UserName
|
||||||
|
&& t.IsAnalysisCreate == false)
|
||||||
|
.Where(t => t.TaskName.Trim() == downloadTask.TaskName.Trim()
|
||||||
|
&& t.Subject.Code.Trim() == downloadTask.SubjectCode.Trim()
|
||||||
|
&& t.TaskBlindName.Trim() == downloadTask.TaskBlindName.Trim()
|
||||||
|
)
|
||||||
|
.Select(vt => new
|
||||||
|
{
|
||||||
|
VisitTaskId = vt.Id,
|
||||||
|
TrialSiteCode = vt.SourceSubjectVisit.TrialSite.TrialSiteCode,
|
||||||
|
SubjectCode = vt.Subject.Code,
|
||||||
|
UserName = vt.DoctorUser.UserName,
|
||||||
|
vt.TaskName,
|
||||||
|
vt.TaskBlindName,
|
||||||
|
VisitName = vt.SourceSubjectVisit.VisitName,
|
||||||
|
VisitNum = vt.SourceSubjectVisit.VisitNum,
|
||||||
|
StudyList = vt.TaskStudyList.Select(u => new
|
||||||
|
{
|
||||||
|
StudyId = u.Id,
|
||||||
|
u.PatientId,
|
||||||
|
u.StudyTime,
|
||||||
|
u.StudyCode,
|
||||||
|
u.StudyInstanceUid,
|
||||||
|
u.StudyDIRPath,
|
||||||
|
|
||||||
|
SeriesList = u.SeriesList.Select(z => new
|
||||||
|
{
|
||||||
|
z.Modality,
|
||||||
|
|
||||||
|
InstancePathList = z.InstanceList.Select(k => new
|
||||||
|
{
|
||||||
|
InstanceId = k.Id,
|
||||||
|
k.Path,
|
||||||
|
k.IsEncapsulated,
|
||||||
|
k.NumberOfFrames,
|
||||||
|
}).ToList()
|
||||||
|
})
|
||||||
|
|
||||||
|
}).ToList(),
|
||||||
|
|
||||||
|
NoneDicomStudyList = vt.SourceSubjectVisit.NoneDicomStudyList.Where(t => t.IsReading && t.Modality != "OCT").Select(nd => new
|
||||||
|
{
|
||||||
|
nd.Modality,
|
||||||
|
nd.StudyCode,
|
||||||
|
nd.ImageDate,
|
||||||
|
|
||||||
|
FileList = nd.TaskNoneDicomFileList.Where(t => t.IsReading && t.VisitTaskId == vt.Id).Select(file => new
|
||||||
|
{
|
||||||
|
file.FileName,
|
||||||
|
file.Path,
|
||||||
|
file.FileType
|
||||||
|
}).ToList()
|
||||||
|
}).ToList()
|
||||||
|
}).OrderBy(t => t.SubjectCode).ThenBy(t => t.VisitNum).ToList()
|
||||||
|
|
||||||
|
}).FirstOrDefault();
|
||||||
|
|
||||||
|
if (downloadInfo == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 排除已经下载的
|
||||||
|
|
||||||
|
var logFilePath = Path.Combine(rootFolder, $"{trialId}_{regexNo}_download_log.csv");
|
||||||
|
|
||||||
|
if (File.Exists(logFilePath))
|
||||||
|
{
|
||||||
|
var existVisits = MiniExcel.Query<SubjectTaskExcel>(logFilePath, configuration: new MiniExcelLibs.Csv.CsvConfiguration()
|
||||||
|
{
|
||||||
|
StreamReaderFunc = (stream) => new StreamReader(stream, Encoding.GetEncoding("gb2312"))
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
if (existVisits.Any(old => old.TaskName == downloadTask.TaskName
|
||||||
|
&& old.SubjectCode == downloadTask.SubjectCode
|
||||||
|
&& old.TaskBlindName.Trim().ToLower() == downloadTask.TaskBlindName.Trim().ToLower()
|
||||||
|
&& old.UserName.Trim().ToLower() == downloadTask.UserName.Trim().ToLower()
|
||||||
|
))
|
||||||
|
{
|
||||||
|
Log.Logger.Warning($"[{visitIndex}] Excel显示已下载,跳过当前:{downloadTask.SubjectCode} {downloadTask.TaskName} {downloadTask.TaskBlindName} {downloadTask.UserName}");
|
||||||
|
skipCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
foreach (var taskItem in downloadInfo.TaskList)
|
||||||
|
{
|
||||||
|
if (taskItem.StudyList.Count() == 0 && taskItem.NoneDicomStudyList.SelectMany(t=>t.FileList).Count() == 0)
|
||||||
|
{
|
||||||
|
Log.Logger.Warning($"[{visitIndex}]查询无检查,跳过当前访视:{downloadTask.SubjectCode} {downloadTask.TaskName} {downloadTask.TaskBlindName} {downloadTask.UserName}");
|
||||||
|
skipCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
#region 导出访视
|
||||||
|
|
||||||
|
var zipItems = new List<ZipItem>();
|
||||||
|
|
||||||
|
var visitFolderName = $"{taskItem.SubjectCode}_{taskItem.TaskBlindName.Trim()}";
|
||||||
|
|
||||||
|
#region DICOM
|
||||||
|
|
||||||
|
foreach (var studyInfo in taskItem.StudyList)
|
||||||
|
{
|
||||||
|
var dirDic = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
var studyFolderName = $"{studyInfo.StudyCode}_{studyInfo.StudyTime:yyyy-MM-dd}_{string.Join('_', studyInfo.SeriesList.Select(t => t.Modality).Distinct())}";
|
||||||
|
|
||||||
|
#region DICOMDIR
|
||||||
|
|
||||||
|
|
||||||
|
if (!_taskInstanceRepository.Where(t => taskItem.VisitTaskId == t.VisitTaskId).Any(c => c.TransferSytaxUID == string.Empty))
|
||||||
|
{
|
||||||
|
var list = _visitTaskRepository.Where(t => t.Id == taskItem.VisitTaskId).SelectMany(t => t.TaskStudyList)
|
||||||
|
.SelectMany(t => t.InstanceList.Where(t => t.StudyId == studyInfo.StudyId))
|
||||||
|
.Select(t => new StudyDIRInfo()
|
||||||
|
{
|
||||||
|
|
||||||
|
DicomStudyId = t.TaskStudy.Id,
|
||||||
|
|
||||||
|
PatientId = downloadInfo.TrialCode + "-" + t.TaskStudy.Subject.Code,
|
||||||
|
PatientName = t.TaskStudy.PatientName,
|
||||||
|
PatientBirthDate = t.TaskStudy.PatientBirthDate,
|
||||||
|
PatientSex = t.TaskStudy.PatientSex,
|
||||||
|
|
||||||
|
StudyInstanceUid = t.StudyInstanceUid,
|
||||||
|
StudyId = t.TaskStudy.StudyId,
|
||||||
|
DicomStudyDate = t.TaskStudy.DicomStudyDate,
|
||||||
|
DicomStudyTime = t.TaskStudy.DicomStudyTime,
|
||||||
|
AccessionNumber = t.TaskStudy.AccessionNumber,
|
||||||
|
|
||||||
|
StudyDescription = t.TaskStudy.Description,
|
||||||
|
|
||||||
|
SeriesInstanceUid = t.TaskSeries.SeriesInstanceUid,
|
||||||
|
Modality = t.TaskSeries.Modality,
|
||||||
|
DicomSeriesDate = t.TaskSeries.DicomSeriesDate,
|
||||||
|
DicomSeriesTime = t.TaskSeries.DicomSeriesTime,
|
||||||
|
SeriesNumber = t.TaskSeries.SeriesNumber,
|
||||||
|
SeriesDescription = t.TaskSeries.Description,
|
||||||
|
|
||||||
|
InstanceId = t.Id,
|
||||||
|
SopInstanceUid = t.SopInstanceUid,
|
||||||
|
SOPClassUID = t.SOPClassUID,
|
||||||
|
InstanceNumber = t.InstanceNumber,
|
||||||
|
MediaStorageSOPClassUID = t.MediaStorageSOPClassUID,
|
||||||
|
MediaStorageSOPInstanceUID = t.MediaStorageSOPInstanceUID,
|
||||||
|
TransferSytaxUID = t.TransferSytaxUID,
|
||||||
|
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
foreach (var group in list.GroupBy(t => new { t.StudyInstanceUid, t.DicomStudyId }))
|
||||||
|
{
|
||||||
|
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
|
||||||
|
await DicomDIRHelper.GenerateStudyDIR(group.ToList(), dirDic, outputStream: ms);
|
||||||
|
|
||||||
|
var dicomDirBytes = ms.ToArray();
|
||||||
|
|
||||||
|
zipItems.Add(new ZipItem
|
||||||
|
{
|
||||||
|
ZipEntryPath = $"{visitFolderName}/{studyFolderName}/DICOMDIR",
|
||||||
|
|
||||||
|
CustomWriter = async entryStream =>
|
||||||
|
{
|
||||||
|
await entryStream.WriteAsync(dicomDirBytes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var seriesInfo in studyInfo.SeriesList)
|
||||||
|
{
|
||||||
|
foreach (var instanceInfo in seriesInfo.InstancePathList)
|
||||||
|
{
|
||||||
|
zipItems.Add(new ZipItem
|
||||||
|
{
|
||||||
|
OssPath = instanceInfo.Path,
|
||||||
|
|
||||||
|
IsEncapsulated = instanceInfo.IsEncapsulated,
|
||||||
|
|
||||||
|
ZipEntryPath = $"{visitFolderName}/" + $"{studyFolderName}/" + $"IMAGE/" + $"{dirDic[instanceInfo.InstanceId.ToString()]}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region NoneDicom
|
||||||
|
|
||||||
|
foreach (var study in taskItem.NoneDicomStudyList)
|
||||||
|
{
|
||||||
|
var studyFolderName = $"{study.StudyCode}_" + $"{study.ImageDate:yyyy-MM-dd}_" + $"{study.Modality}";
|
||||||
|
|
||||||
|
foreach (var file in study.FileList)
|
||||||
|
{
|
||||||
|
zipItems.Add(new ZipItem
|
||||||
|
{
|
||||||
|
OssPath = HttpUtility.UrlDecode(file.Path),
|
||||||
|
|
||||||
|
ZipEntryPath = $"{visitFolderName}/" + $"{studyFolderName}/" + $"{Path.GetFileName(file.FileName)}"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region zip
|
||||||
|
|
||||||
|
|
||||||
|
var zipPath = Path.Combine(trialFolderPath, visitFolderName + ".zip");
|
||||||
|
|
||||||
|
Log.Logger.Warning($"[{visitIndex}] {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 开始打包下载访视:{visitFolderName}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await CreateVisitZipAsync(zipPath, zipItems);
|
||||||
|
|
||||||
|
//Log.Logger.Warning($"zip exists={File.Exists(zipPath)} size={new FileInfo(zipPath).Length}");
|
||||||
|
|
||||||
|
Log.Logger.Warning($"[{visitIndex}] {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 下载完成:{visitFolderName}");
|
||||||
|
|
||||||
|
DownloadLogger.TaskWrite(logFilePath, taskItem.SubjectCode, taskItem.TaskName, taskItem.TaskBlindName, taskItem.UserName, "Success");
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Logger.Warning($"出现异常{ex}自动删除压缩包:{visitFolderName}");
|
||||||
|
//如果有异常,删除失败的压缩包
|
||||||
|
if (File.Exists(zipPath))
|
||||||
|
{
|
||||||
|
File.Delete(zipPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Logger.Error(ex, $"导出失败:{downloadTask.SubjectCode} {downloadTask.TaskName} {downloadTask.TaskBlindName} {downloadTask.UserName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseOutput.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
@@ -542,6 +864,53 @@ namespace IRaCIS.Core.Application.Service
|
|||||||
writer.WriteLine(line);
|
writer.WriteLine(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void TaskWrite(
|
||||||
|
string logFilePath,
|
||||||
|
string subjectCode,
|
||||||
|
string taskName,
|
||||||
|
string taskBlindName,
|
||||||
|
string userName,
|
||||||
|
string? message = null)
|
||||||
|
{
|
||||||
|
bool fileExists = File.Exists(logFilePath);
|
||||||
|
|
||||||
|
// 一次性打开文件流
|
||||||
|
using var stream = new FileStream(
|
||||||
|
logFilePath,
|
||||||
|
FileMode.Append,
|
||||||
|
FileAccess.Write,
|
||||||
|
FileShare.ReadWrite);
|
||||||
|
|
||||||
|
// 首次创建时写入 BOM
|
||||||
|
if (!fileExists)
|
||||||
|
{
|
||||||
|
var bom = new UTF8Encoding(true).GetPreamble();
|
||||||
|
stream.Write(bom, 0, bom.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
using var writer = new StreamWriter(stream, new UTF8Encoding(false));
|
||||||
|
|
||||||
|
// 写入表头
|
||||||
|
if (!fileExists)
|
||||||
|
{
|
||||||
|
writer.WriteLine("Time,SubjectCode,TaskName,TaskBlindName,UserName,Message");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入数据
|
||||||
|
string line = string.Join(",",
|
||||||
|
[
|
||||||
|
Escape(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")),
|
||||||
|
subjectCode,
|
||||||
|
taskName,
|
||||||
|
taskBlindName,
|
||||||
|
userName,
|
||||||
|
Escape(message)
|
||||||
|
]);
|
||||||
|
|
||||||
|
writer.WriteLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
private static string Escape(string? value)
|
private static string Escape(string? value)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(value))
|
if (string.IsNullOrEmpty(value))
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace IRaCIS.Core.Application.Services
|
|||||||
{
|
{
|
||||||
[ApiExplorerSettings(GroupName = "Image")]
|
[ApiExplorerSettings(GroupName = "Image")]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public class InstanceService(IRepository<DicomInstance> _instanceRepository,
|
public class InstanceService(IRepository<DicomInstance> _instanceRepository, IRepository<DicomSeries> _seriesRepository,
|
||||||
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IWebHostEnvironment _hostEnvironment) : BaseService, IInstanceService
|
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IWebHostEnvironment _hostEnvironment) : BaseService, IInstanceService
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -29,12 +29,15 @@ namespace IRaCIS.Core.Application.Services
|
|||||||
.OrderBy(s => s.InstanceNumber).ThenBy(s => s.InstanceTime).ThenBy(s => s.CreateTime)
|
.OrderBy(s => s.InstanceNumber).ThenBy(s => s.InstanceTime).ThenBy(s => s.CreateTime)
|
||||||
.ProjectTo<DicomInstanceDTO>(_mapper.ConfigurationProvider).ToListAsync();
|
.ProjectTo<DicomInstanceDTO>(_mapper.ConfigurationProvider).ToListAsync();
|
||||||
|
|
||||||
|
var des = _seriesRepository.Where(t => t.Id == seriesId).Select(t => t.Description).FirstOrDefault();
|
||||||
|
|
||||||
// ⭐ DICOM 空间排序(带兜底)
|
// ⭐ DICOM 空间排序(带兜底)
|
||||||
var sorted = DicomSortHelper.SortSlices(
|
var sorted = DicomSortHelper.SortSlices(
|
||||||
list,
|
list,
|
||||||
x => x.ImagePositionPatient,
|
x => x.ImagePositionPatient,
|
||||||
x => x.ImageOrientationPatient,
|
x => x.ImageOrientationPatient,
|
||||||
x => x.InstanceNumber
|
x => x.InstanceNumber,
|
||||||
|
des
|
||||||
);
|
);
|
||||||
|
|
||||||
var seriesInfo = await _instanceRepository.Where(s => s.SeriesId == seriesId).Select(t => new
|
var seriesInfo = await _instanceRepository.Where(s => s.SeriesId == seriesId).Select(t => new
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ namespace IRaCIS.Core.Application.Services
|
|||||||
instances,
|
instances,
|
||||||
x => x.ImagePositionPatient,
|
x => x.ImagePositionPatient,
|
||||||
x => x.ImageOrientationPatient,
|
x => x.ImageOrientationPatient,
|
||||||
x => x.InstanceNumber
|
x => x.InstanceNumber,
|
||||||
|
series.Description
|
||||||
);
|
);
|
||||||
|
|
||||||
series.InstanceInfoList = sorted.Select(k =>
|
series.InstanceInfoList = sorted.Select(k =>
|
||||||
@@ -104,13 +105,24 @@ namespace IRaCIS.Core.Application.Services
|
|||||||
.WhereIf(isReading == true, t => t.IsReading == true)
|
.WhereIf(isReading == true, t => t.IsReading == true)
|
||||||
.OrderBy(t => t.SeriesId).ThenBy(t => t.InstanceNumber)
|
.OrderBy(t => t.SeriesId).ThenBy(t => t.InstanceNumber)
|
||||||
.ThenBy(s => s.InstanceTime).ThenBy(s => s.CreateTime)
|
.ThenBy(s => s.InstanceTime).ThenBy(s => s.CreateTime)
|
||||||
.Select(t => new { t.SeriesId, t.Id, t.Path, t.NumberOfFrames, t.InstanceNumber, t.HtmlPath, t.IsReading, t.IsDeleted, t.FileSize ,t.IsMasked}).ToListAsync();//.GroupBy(u => u.SeriesId);
|
.Select(t => new { t.SeriesId, t.Id, t.Path, t.NumberOfFrames, t.InstanceNumber, t.HtmlPath, t.IsReading, t.IsDeleted, t.FileSize ,t.IsMasked, t.ImagePositionPatient, t.ImageOrientationPatient }).ToListAsync();//.GroupBy(u => u.SeriesId);
|
||||||
|
|
||||||
|
|
||||||
foreach (var series in seriesList)
|
foreach (var series in seriesList)
|
||||||
{
|
{
|
||||||
|
|
||||||
series.InstanceInfoList = instanceList.Where(t => t.SeriesId == series.Id).OrderBy(t => t.InstanceNumber).Select(k =>
|
var instances = instanceList.Where(x => x.SeriesId == series.Id);
|
||||||
|
|
||||||
|
// ⭐ DICOM 空间排序(带兜底)
|
||||||
|
var sorted = DicomSortHelper.SortSlices(
|
||||||
|
instances,
|
||||||
|
x => x.ImagePositionPatient,
|
||||||
|
x => x.ImageOrientationPatient,
|
||||||
|
x => x.InstanceNumber,
|
||||||
|
series.Description
|
||||||
|
);
|
||||||
|
|
||||||
|
series.InstanceInfoList = sorted.Select(k =>
|
||||||
new InstanceBasicInfo()
|
new InstanceBasicInfo()
|
||||||
{
|
{
|
||||||
Id = k.Id,
|
Id = k.Id,
|
||||||
|
|||||||
@@ -292,7 +292,8 @@ namespace IRaCIS.Core.Application.Services
|
|||||||
instances,
|
instances,
|
||||||
x => x.ImagePositionPatient,
|
x => x.ImagePositionPatient,
|
||||||
x => x.ImageOrientationPatient,
|
x => x.ImageOrientationPatient,
|
||||||
x => x.InstanceNumber
|
x => x.InstanceNumber,
|
||||||
|
series.Description
|
||||||
);
|
);
|
||||||
|
|
||||||
series.InstanceInfoList = sorted.Select(k =>
|
series.InstanceInfoList = sorted.Select(k =>
|
||||||
@@ -629,7 +630,8 @@ namespace IRaCIS.Core.Application.Services
|
|||||||
instances,
|
instances,
|
||||||
x => x.ImagePositionPatient,
|
x => x.ImagePositionPatient,
|
||||||
x => x.ImageOrientationPatient,
|
x => x.ImageOrientationPatient,
|
||||||
x => x.InstanceNumber
|
x => x.InstanceNumber,
|
||||||
|
series.Description
|
||||||
);
|
);
|
||||||
|
|
||||||
series.InstanceInfoList = sorted.Select(k =>
|
series.InstanceInfoList = sorted.Select(k =>
|
||||||
@@ -682,7 +684,8 @@ namespace IRaCIS.Core.Application.Services
|
|||||||
series.InstanceInfoList,
|
series.InstanceInfoList,
|
||||||
x => x.ImagePositionPatient,
|
x => x.ImagePositionPatient,
|
||||||
x => x.ImageOrientationPatient,
|
x => x.ImageOrientationPatient,
|
||||||
x => x.InstanceNumber
|
x => x.InstanceNumber,
|
||||||
|
series.Description
|
||||||
);
|
);
|
||||||
|
|
||||||
series.InstanceInfoList = sorted;
|
series.InstanceInfoList = sorted;
|
||||||
|
|||||||
@@ -295,6 +295,21 @@ namespace IRaCIS.Core.Application.Service
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class SubjectTaskExcel
|
||||||
|
{
|
||||||
|
|
||||||
|
public string SubjectCode { get; set; }
|
||||||
|
|
||||||
|
public string TaskName { get; set; }
|
||||||
|
|
||||||
|
public string TaskBlindName { get; set; }
|
||||||
|
|
||||||
|
public string UserName { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 访视数据模型
|
/// 访视数据模型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user