1833 lines
75 KiB
C#
1833 lines
75 KiB
C#
using Aliyun.OSS;
|
||
using DocumentFormat.OpenXml.EMMA;
|
||
using DocumentFormat.OpenXml.Office.CustomUI;
|
||
using DocumentFormat.OpenXml.Office2010.Excel;
|
||
using DocumentFormat.OpenXml.Office2013.Drawing.ChartStyle;
|
||
using FellowOakDicom;
|
||
using FellowOakDicom.Imaging;
|
||
using FellowOakDicom.Imaging.Render;
|
||
using FellowOakDicom.IO.Buffer;
|
||
using IRaCIS.Core.Application.Helper;
|
||
using IRaCIS.Core.Application.ViewModel;
|
||
using IRaCIS.Core.Domain.Models;
|
||
using IRaCIS.Core.Infrastructure;
|
||
using MassTransit;
|
||
using Medallion.Threading;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using Microsoft.AspNetCore.Hosting;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using MiniExcelLibs;
|
||
using SharpCompress.Common;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Data;
|
||
using System.Diagnostics;
|
||
using System.Linq;
|
||
using System.Runtime.InteropServices;
|
||
using System.Text;
|
||
using System.Text.RegularExpressions;
|
||
using System.Threading.Tasks;
|
||
using System.Web;
|
||
using static IRaCIS.Core.Domain.Share.StaticData;
|
||
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
|
||
|
||
namespace IRaCIS.Core.Application.Service
|
||
{
|
||
|
||
/// <summary>
|
||
/// 项目影像后台下载,不打包
|
||
/// </summary>
|
||
/// <param name="_trialRepository"></param>
|
||
/// <param name="_oSSService"></param>
|
||
[ApiExplorerSettings(GroupName = "Common")]
|
||
public class TrialImageDownloadService(IRepository<Trial> _trialRepository, IOSSService _oSSService, IWebHostEnvironment _hostEnvironment,
|
||
IRepository<DicomStudy> _studyRepository,
|
||
IRepository<DicomSeries> _seriesRepository,
|
||
IRepository<DicomInstance> _instanceRepository) : BaseService
|
||
{
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 后端api swagger 下载项目影像
|
||
/// </summary>
|
||
/// <param name="trialId"></param>
|
||
/// <returns></returns>
|
||
[HttpPost]
|
||
[AllowAnonymous]
|
||
public async Task<IResponseOutput> DownloadTrialImage(Guid trialId)
|
||
{
|
||
//var subjectCodeList = new List<string>() { "05002", "07006", "07026" };
|
||
var downloadInfo = _trialRepository.Where(t => t.Id == trialId).Select(t => new
|
||
{
|
||
t.ResearchProgramNo,
|
||
|
||
VisitList = t.SubjectVisitList
|
||
//.Where(t=>subjectCodeList.Contains(t.Subject.Code))
|
||
.Select(sv => new
|
||
{
|
||
TrialSiteCode = sv.TrialSite.TrialSiteCode,
|
||
SubjectCode = sv.Subject.Code,
|
||
VisitName = sv.VisitName,
|
||
StudyList = sv.StudyList.Select(u => new
|
||
{
|
||
u.PatientId,
|
||
u.StudyTime,
|
||
u.StudyCode,
|
||
|
||
SeriesList = u.SeriesList.Where(t => t.IsReading).Select(z => new
|
||
{
|
||
z.Modality,
|
||
|
||
InstancePathList = z.DicomInstanceList.Where(t => t.IsReading).Select(k => new
|
||
{
|
||
k.Path
|
||
}).ToList()
|
||
})
|
||
|
||
}).ToList(),
|
||
|
||
NoneDicomStudyList = sv.NoneDicomStudyList.Where(t => t.IsReading).Select(nd => new
|
||
{
|
||
nd.Modality,
|
||
nd.StudyCode,
|
||
nd.ImageDate,
|
||
|
||
FileList = nd.NoneDicomFileList.Where(t => t.IsReading).Select(file => new
|
||
{
|
||
file.FileName,
|
||
file.Path,
|
||
file.FileType
|
||
}).ToList()
|
||
}).ToList()
|
||
}).ToList()
|
||
|
||
}).FirstOrDefault();
|
||
|
||
|
||
var count = downloadInfo.VisitList.SelectMany(t => t.NoneDicomStudyList).SelectMany(t => t.FileList).Count();
|
||
var count2 = downloadInfo.VisitList.SelectMany(t => t.StudyList).SelectMany(t => t.SeriesList).SelectMany(t => t.InstancePathList).Count();
|
||
|
||
Console.WriteLine($"下载总数量:{count}+{count2}={count + count2}");
|
||
|
||
if (downloadInfo != null)
|
||
{
|
||
var downloadJobs = new List<Func<Task>>();
|
||
|
||
var rootFolder = @"E:\DownloadImage";
|
||
|
||
//var rootFolder = FileStoreHelper.GetDonwnloadImageFolder(_hostEnvironment);
|
||
|
||
// 获取无效字符(系统定义的)
|
||
string invalidChars = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
|
||
|
||
// 用正则表达式替换所有非法字符为下划线或空字符
|
||
string pattern = $"[{Regex.Escape(invalidChars)}]";
|
||
|
||
var regexNo = Regex.Replace(downloadInfo.ResearchProgramNo, pattern, "_");
|
||
|
||
// 创建一个临时文件夹来存放文件
|
||
string trialFolderPath = Path.Combine(rootFolder, $"{regexNo}_{NewId.NextGuid()}");
|
||
Directory.CreateDirectory(trialFolderPath);
|
||
|
||
foreach (var visitItem in downloadInfo.VisitList)
|
||
{
|
||
if (visitItem.StudyList.Count() == 0 && visitItem.NoneDicomStudyList.Count() == 0)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
#region 处理 中心,受试者dicom non-dicom 文件夹层级
|
||
|
||
var siteFolderPath = Path.Combine(trialFolderPath, visitItem.TrialSiteCode);
|
||
if (!Directory.Exists(siteFolderPath))
|
||
{
|
||
Directory.CreateDirectory(siteFolderPath);
|
||
}
|
||
|
||
#endregion
|
||
|
||
|
||
foreach (var studyInfo in visitItem.StudyList)
|
||
{
|
||
// 遍历 Series
|
||
foreach (var seriesInfo in studyInfo.SeriesList)
|
||
{
|
||
string studyDicomFolderPath = Path.Combine(siteFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}_DICOM", $"{studyInfo.StudyCode}_{studyInfo.StudyTime?.ToString("yyyy-MM-dd")}_{seriesInfo.Modality}");
|
||
|
||
// 创建 影像 文件夹
|
||
Directory.CreateDirectory(studyDicomFolderPath);
|
||
|
||
// 遍历 InstancePathList
|
||
foreach (var instanceInfo in seriesInfo.InstancePathList)
|
||
{
|
||
// 复制文件到相应的文件夹
|
||
string destinationPath = Path.Combine(studyDicomFolderPath, Path.GetFileName(instanceInfo.Path));
|
||
|
||
|
||
//加入到下载任务里
|
||
downloadJobs.Add(() => _oSSService.DownLoadFromOSSAsync(instanceInfo.Path, destinationPath));
|
||
|
||
//下载到当前目录
|
||
//await _oSSService.DownLoadFromOSSAsync(instanceInfo.Path, destinationPath);
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
|
||
foreach (var noneDicomStudy in visitItem.NoneDicomStudyList)
|
||
{
|
||
string studyNoneDicomFolderPath = Path.Combine(siteFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}_Non-DICOM", $"{noneDicomStudy.StudyCode}_{noneDicomStudy.ImageDate.ToString("yyyy-MM-dd")}_{noneDicomStudy.Modality}");
|
||
|
||
Directory.CreateDirectory(studyNoneDicomFolderPath);
|
||
|
||
foreach (var file in noneDicomStudy.FileList)
|
||
{
|
||
string destinationPath = Path.Combine(studyNoneDicomFolderPath, Path.GetFileName(file.FileName));
|
||
|
||
//加入到下载任务里
|
||
downloadJobs.Add(() => _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath));
|
||
//下载到当前目录
|
||
//await _oSSService.DownLoadFromOSSAsync(HttpUtility.UrlDecode(file.Path), destinationPath);
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
|
||
#region 异步方式处理
|
||
|
||
int totalCount = downloadJobs.Count;
|
||
int downloadedCount = 0;
|
||
|
||
foreach (var job in downloadJobs)
|
||
{
|
||
try
|
||
{
|
||
await job();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"下载失败: {ex.Message}");
|
||
}
|
||
|
||
downloadedCount++;
|
||
|
||
// 每处理50个,输出一次进度(或最后一个时也输出)
|
||
if (downloadedCount % 50 == 0 || downloadedCount == totalCount)
|
||
{
|
||
Console.WriteLine($"已下载 {downloadedCount} / {totalCount} 个文件,完成 {(downloadedCount * 100.0 / totalCount):F2}%");
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 多线程测试
|
||
|
||
//const int batchSize = 15;
|
||
//int totalCount = downloadJobs.Count;
|
||
//int downloadedCount = 0;
|
||
|
||
//for (int i = 0; i < downloadJobs.Count; i += batchSize)
|
||
//{
|
||
// var batch = downloadJobs.Skip(i).Take(batchSize).Select(job => job());
|
||
|
||
// await Task.WhenAll(batch);
|
||
|
||
// downloadedCount += batch.Count();
|
||
|
||
// Console.WriteLine($"已下载 {downloadedCount} / {totalCount} 个文件,完成 {(downloadedCount * 100.0 / totalCount):F2}%");
|
||
//}
|
||
#endregion
|
||
|
||
}
|
||
|
||
return ResponseOutput.Ok();
|
||
|
||
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 下载影像 维护dir信息 并回传到OSS
|
||
/// </summary>
|
||
/// <param name="trialId"></param>
|
||
/// <returns></returns>
|
||
[HttpGet]
|
||
[AllowAnonymous]
|
||
public async Task<IResponseOutput> DownloadAndUploadTrialData(Guid trialId,
|
||
[FromServices] IRepository<DicomInstance> _instanceRepository,
|
||
[FromServices] IRepository<DicomStudy> _studyRepository,
|
||
[FromServices] IRepository<DicomSeries> _seriesRepository)
|
||
{
|
||
var list = await _instanceRepository.Where(t => t.TrialId == trialId && t.SubjectVisitId == Guid.Parse("01000000-0a00-0242-bd20-08dcce543ded") && t.DicomStudy.ModalityForEdit == "IVUS")
|
||
.Select(t => new { t.SeriesId, t.StudyId, t.Id, t.Path }).ToListAsync();
|
||
|
||
int totalCount = list.Count;
|
||
int dealCount = 0;
|
||
foreach (var item in list)
|
||
{
|
||
|
||
var stream = await _oSSService.GetStreamFromOSSAsync(item.Path);
|
||
|
||
var dicomFile = DicomFile.Open(stream);
|
||
|
||
// 获取 Pixel Data 标签
|
||
var pixelData = DicomPixelData.Create(dicomFile.Dataset);
|
||
|
||
//获取像素是否为封装形式
|
||
var syntax = dicomFile.Dataset.InternalTransferSyntax;
|
||
|
||
//对于封装像素的文件做转换
|
||
if (syntax.IsEncapsulated)
|
||
{
|
||
// 创建一个新的片段序列
|
||
var newFragments = new DicomOtherByteFragment(DicomTag.PixelData);
|
||
// 获取每帧数据并封装为单独的片段
|
||
for (int n = 0; n < pixelData.NumberOfFrames; n++)
|
||
{
|
||
var frameData = pixelData.GetFrame(n);
|
||
newFragments.Fragments.Add(new MemoryByteBuffer(frameData.Data));
|
||
}
|
||
// 替换原有的片段序列
|
||
dicomFile.Dataset.AddOrUpdate(newFragments);
|
||
}
|
||
|
||
|
||
#region 获取dir信息 维护数据库三个表数据
|
||
|
||
var dirInfo = DicomDIRHelper.ReadDicomDIRInfo(dicomFile);
|
||
|
||
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id,
|
||
u => new DicomInstance()
|
||
{
|
||
IsEncapsulated = syntax.IsEncapsulated,
|
||
TransferSytaxUID = dirInfo.TransferSytaxUID,
|
||
SOPClassUID = dirInfo.SOPClassUID,
|
||
MediaStorageSOPClassUID = dirInfo.MediaStorageSOPClassUID,
|
||
MediaStorageSOPInstanceUID = dirInfo.MediaStorageSOPInstanceUID
|
||
}, false);
|
||
|
||
await _seriesRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.SeriesId,
|
||
u => new DicomSeries()
|
||
{
|
||
DicomSeriesDate = dirInfo.DicomSeriesDate,
|
||
DicomSeriesTime = dirInfo.DicomSeriesTime,
|
||
}, false);
|
||
|
||
await _studyRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.StudyId,
|
||
u => new DicomStudy()
|
||
{
|
||
DicomStudyDate = dirInfo.DicomStudyDate,
|
||
DicomStudyTime = dirInfo.DicomStudyTime
|
||
}, false);
|
||
|
||
#endregion
|
||
|
||
|
||
|
||
//保存到内存流
|
||
using var memoryStream = new MemoryStream();
|
||
dicomFile.Save(memoryStream);
|
||
memoryStream.Position = 0;
|
||
|
||
//获取原始目录 和文件名
|
||
var folder = item.Path.Substring(0, item.Path.LastIndexOf('/')).TrimStart('/');
|
||
var fileName = Path.GetFileName(item.Path);
|
||
|
||
//dicomFile.Save($"download_{Guid.NewGuid()}");
|
||
|
||
await _oSSService.UploadToOSSAsync(memoryStream, folder, fileName, false);
|
||
|
||
dealCount++;
|
||
|
||
Console.WriteLine($"{DateTime.Now}已下载 {dealCount} / {totalCount} 个文件,完成 {(dealCount * 100.0 / totalCount):F2}%");
|
||
}
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 维护dir 需求新增的字段
|
||
|
||
/// </summary>
|
||
/// <param name="trialId"></param>
|
||
/// <param name="_instanceRepository"></param>
|
||
/// <param name="_studyRepository"></param>
|
||
/// <param name="_seriesRepository"></param>
|
||
/// <returns></returns>
|
||
public async Task<IResponseOutput> TrialImageAddExtralField(Guid trialId,
|
||
[FromServices] IRepository<DicomInstance> _instanceRepository,
|
||
[FromServices] IRepository<DicomStudy> _studyRepository,
|
||
[FromServices] IRepository<DicomSeries> _seriesRepository)
|
||
{
|
||
// UPDATE DicomStudy
|
||
//SET DicomStudyDate = CONVERT(char(8), StudyTime, 112), --yyyyMMdd
|
||
// DicomStudyTime = REPLACE(CONVERT(char(8), StudyTime, 108), ':', ''); --HHmmss
|
||
// where DicomStudyDate = ''
|
||
|
||
|
||
//instance 找到传输语法为空的,然后分组
|
||
var seriesList = _instanceRepository.Where(t => t.TrialId == trialId && t.TransferSytaxUID == "")
|
||
//按照序列 和 NumberOfFrames 分组
|
||
.GroupBy(t => new { t.NumberOfFrames, t.SeriesId })
|
||
// 每个分组 取数据最小的一条
|
||
.Select(g => new { g.Key.SeriesId, g.Key.NumberOfFrames, g.OrderBy(t => t.FileSize).First().Path }).ToList();
|
||
|
||
foreach (var item in seriesList)
|
||
{
|
||
var stream = await _oSSService.GetStreamFromOSSAsync(item.Path);
|
||
|
||
var dicomFile = DicomFile.Open(stream);
|
||
|
||
var pixelData = DicomPixelData.Create(dicomFile.Dataset);
|
||
|
||
//获取像素是否为封装形式
|
||
var syntax = dicomFile.Dataset.InternalTransferSyntax;
|
||
|
||
//读取需要维护的值
|
||
var transferSyntaxUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty);
|
||
var mediaStorageSOPClassUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty);
|
||
var mediaStorageSOPInstanceUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty);
|
||
var sOPClassUID = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty);
|
||
|
||
//维护序列层级四个字段 后再用sql 维护study series 时间拆分 和 MediaStorageSOPInstanceUID
|
||
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.SeriesId == item.SeriesId, t => new DicomInstance()
|
||
{
|
||
IsEncapsulated = syntax.IsEncapsulated,
|
||
TransferSytaxUID = transferSyntaxUID,
|
||
MediaStorageSOPClassUID = mediaStorageSOPClassUID,
|
||
SOPClassUID = sOPClassUID,
|
||
});
|
||
|
||
}
|
||
|
||
return ResponseOutput.Ok();
|
||
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 下载已经删除的影像
|
||
/// </summary>
|
||
/// <param name="trialId"></param>
|
||
/// <returns></returns>
|
||
[HttpPost]
|
||
[AllowAnonymous]
|
||
public async Task<IResponseOutput> DownloadDeleteTrialImage(Guid trialId)
|
||
{
|
||
trialId = Guid.Parse("01000000-ac13-0242-6397-08dcd2d2a091");
|
||
|
||
var downloadInfo = _trialRepository.Where(t => t.Id == trialId, ignoreQueryFilters: true).Select(t => new
|
||
{
|
||
t.ResearchProgramNo,
|
||
|
||
VisitList = t.SubjectVisitList
|
||
.Select(sv => new
|
||
{
|
||
TrialSiteCode = sv.TrialSite.TrialSiteCode,
|
||
SubjectCode = sv.Subject.Code,
|
||
VisitName = sv.VisitName,
|
||
StudyList = sv.StudyList.Select(u => new
|
||
{
|
||
u.PatientId,
|
||
u.StudyTime,
|
||
u.StudyCode,
|
||
|
||
SeriesList = u.SeriesList.Select(z => new
|
||
{
|
||
z.Modality,
|
||
z.SeriesNumber,
|
||
|
||
InstancePathList = z.DicomInstanceList.Where(t => t.IsDeleted == true || t.DicomSerie.IsDeleted == true || t.IsReading == false || t.DicomSerie.IsReading == false).Select(k => new
|
||
{
|
||
k.Path,
|
||
k.FileSize
|
||
}).ToList()
|
||
})
|
||
|
||
}).ToList()
|
||
}).ToList()
|
||
|
||
}).FirstOrDefault();
|
||
|
||
|
||
var filesizes = downloadInfo.VisitList.SelectMany(t => t.StudyList).SelectMany(t => t.SeriesList).SelectMany(t => t.InstancePathList).Sum(t => t.FileSize);
|
||
var count2 = downloadInfo.VisitList.SelectMany(t => t.StudyList).SelectMany(t => t.SeriesList).SelectMany(t => t.InstancePathList).Count();
|
||
|
||
Console.WriteLine($"下载总数量:{count2},总大小{filesizes}");
|
||
|
||
if (downloadInfo != null)
|
||
{
|
||
var downloadJobs = new List<(string Path, Func<Task> Job)>();
|
||
|
||
var rootFolder = @"E:\DownloadImage";
|
||
|
||
//var rootFolder = FileStoreHelper.GetDonwnloadImageFolder(_hostEnvironment);
|
||
|
||
// 获取无效字符(系统定义的)
|
||
string invalidChars = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
|
||
|
||
// 用正则表达式替换所有非法字符为下划线或空字符
|
||
string pattern = $"[{Regex.Escape(invalidChars)}]";
|
||
|
||
var regexNo = Regex.Replace(downloadInfo.ResearchProgramNo, pattern, "_");
|
||
|
||
// 创建一个临时文件夹来存放文件
|
||
string trialFolderPath = Path.Combine(rootFolder, $"{regexNo}_{NewId.NextGuid()}");
|
||
Directory.CreateDirectory(trialFolderPath);
|
||
|
||
foreach (var visitItem in downloadInfo.VisitList)
|
||
{
|
||
if (visitItem.StudyList.Count() == 0)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
#region 处理 中心,受试者dicom non-dicom 文件夹层级
|
||
|
||
//var siteFolderPath = Path.Combine(trialFolderPath, visitItem.TrialSiteCode);
|
||
//if (!Directory.Exists(siteFolderPath))
|
||
//{
|
||
// Directory.CreateDirectory(siteFolderPath);
|
||
//}
|
||
|
||
#endregion
|
||
|
||
|
||
foreach (var studyInfo in visitItem.StudyList)
|
||
{
|
||
// 遍历 Series
|
||
foreach (var seriesInfo in studyInfo.SeriesList)
|
||
{
|
||
string studyDicomFolderPath = Path.Combine(trialFolderPath, $"{visitItem.SubjectCode}_{visitItem.VisitName}", $"{studyInfo.StudyCode}_{studyInfo.StudyTime?.ToString("yyyy-MM-dd")}", $"{seriesInfo.SeriesNumber}");
|
||
|
||
// 创建 影像 文件夹
|
||
Directory.CreateDirectory(studyDicomFolderPath);
|
||
|
||
// 遍历 InstancePathList
|
||
foreach (var instanceInfo in seriesInfo.InstancePathList)
|
||
{
|
||
// 复制文件到相应的文件夹
|
||
string destinationPath = Path.Combine(studyDicomFolderPath, Path.GetFileName(instanceInfo.Path));
|
||
|
||
|
||
//加入到下载任务里
|
||
downloadJobs.Add((instanceInfo.Path, () => _oSSService.DownLoadFromOSSAsync(instanceInfo.Path, destinationPath)));
|
||
|
||
//下载到当前目录
|
||
//await _oSSService.DownLoadFromOSSAsync(instanceInfo.Path, destinationPath);
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
}
|
||
|
||
#region 异步方式处理
|
||
|
||
int totalCount = downloadJobs.Count;
|
||
int downloadedCount = 0;
|
||
|
||
// 在 trialFolderPath 下面放一个失败记录文件
|
||
string failedLogPath = Path.Combine(trialFolderPath, "failed_downloads.txt");
|
||
|
||
// 确保文件存在(如果之前有就清空)
|
||
File.WriteAllText(failedLogPath, "");
|
||
|
||
foreach (var job in downloadJobs)
|
||
{
|
||
try
|
||
{
|
||
await job.Job();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
|
||
|
||
string errorMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 下载失败: {job.Path}, 错误: {ex.Message}\r\n";
|
||
|
||
Console.WriteLine(errorMessage);
|
||
|
||
await File.AppendAllTextAsync(failedLogPath, errorMessage);
|
||
}
|
||
|
||
downloadedCount++;
|
||
|
||
// 每处理50个,输出一次进度(或最后一个时也输出)
|
||
if (downloadedCount % 50 == 0 || downloadedCount == totalCount)
|
||
{
|
||
Console.WriteLine($"已下载 {downloadedCount} / {totalCount} 个文件,完成 {(downloadedCount * 100.0 / totalCount):F2}%");
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 多线程测试
|
||
|
||
//const int batchSize = 15;
|
||
//int totalCount = downloadJobs.Count;
|
||
//int downloadedCount = 0;
|
||
|
||
//for (int i = 0; i < downloadJobs.Count; i += batchSize)
|
||
//{
|
||
// var batch = downloadJobs.Skip(i).Take(batchSize).Select(job => job());
|
||
|
||
// await Task.WhenAll(batch);
|
||
|
||
// downloadedCount += batch.Count();
|
||
|
||
// Console.WriteLine($"已下载 {downloadedCount} / {totalCount} 个文件,完成 {(downloadedCount * 100.0 / totalCount):F2}%");
|
||
//}
|
||
#endregion
|
||
|
||
}
|
||
|
||
return ResponseOutput.Ok();
|
||
|
||
|
||
}
|
||
|
||
|
||
[AllowAnonymous]
|
||
public async Task<IResponseOutput> ReadDicomDataWriteDB([FromServices] IRepository<DicomInstance> _instanceRepository)
|
||
{
|
||
var testPath = @"E:\WXT001";
|
||
var path = @"E:\WXT001";
|
||
|
||
var files = Directory.GetFiles(testPath, "*", SearchOption.AllDirectories)
|
||
// 只要没有后缀(Windows 显示类型是 .file)
|
||
.Where(f => string.IsNullOrEmpty(Path.GetExtension(f)))
|
||
.Where(f => Guid.TryParse(Path.GetFileNameWithoutExtension(f), out _))
|
||
.ToList();
|
||
|
||
Console.WriteLine($"找到 {files.Count} 个 DICOM 文件");
|
||
|
||
int total = files.Count;
|
||
int processed = 0;
|
||
double lastPercent = 0;
|
||
|
||
var options = new ParallelOptions { MaxDegreeOfParallelism = 12 };
|
||
|
||
// 输出文件路径
|
||
var outputFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info.txt");
|
||
|
||
var outputErrorFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info_error.txt");
|
||
|
||
// 用并发安全的写法(锁保护)
|
||
var fileLock = new object();
|
||
|
||
foreach (var file in files)
|
||
{
|
||
try
|
||
{
|
||
var id = Guid.Parse(Path.GetFileNameWithoutExtension(file));
|
||
var dicomFile = DicomFile.Open(file);
|
||
|
||
var dataset = dicomFile.Dataset;
|
||
var fileMeta = dicomFile.FileMetaInfo;
|
||
var syntax = dataset.InternalTransferSyntax;
|
||
|
||
|
||
|
||
//单位 设备 PatientId Visit 检查UId 帧数
|
||
|
||
var stationName = dataset.GetSingleValueOrDefault(DicomTag.StationName, string.Empty);
|
||
|
||
var institutionName = dataset.GetSingleValueOrDefault(DicomTag.InstitutionName, string.Empty);
|
||
|
||
var manufacturer = dataset.GetSingleValueOrDefault(DicomTag.Manufacturer, string.Empty);
|
||
|
||
//PatientID TrialCode_SubjectCode
|
||
//var patientID = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
|
||
|
||
//SubjectCode
|
||
var clinicalTrialSubjectID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialSubjectID, string.Empty);
|
||
//访视visitNum
|
||
var clinicalTrialTimePointID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialTimePointID, string.Empty);
|
||
var studyInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty);
|
||
var seriesInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty);
|
||
var sOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty);
|
||
|
||
var numberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1);
|
||
|
||
|
||
// 传输语法
|
||
var transferSyntaxUID = fileMeta.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty);
|
||
var sOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty);
|
||
var mediaStorageSOPClassUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty);
|
||
|
||
var mediaStorageSOPInstanceUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty);
|
||
|
||
|
||
// 拼接一行 CSV 格式
|
||
var line = string.Join(",",
|
||
id,
|
||
stationName,
|
||
institutionName,
|
||
manufacturer,
|
||
clinicalTrialSubjectID,
|
||
clinicalTrialTimePointID,
|
||
studyInstanceUID,
|
||
seriesInstanceUID,
|
||
sOPInstanceUID,
|
||
numberOfFrames,
|
||
transferSyntaxUID,
|
||
sOPClassUID,
|
||
mediaStorageSOPClassUID,
|
||
mediaStorageSOPInstanceUID
|
||
|
||
);
|
||
|
||
|
||
await File.AppendAllTextAsync(outputFile, line + Environment.NewLine);
|
||
|
||
await _instanceRepository.BatchUpdateNoTrackingAsync(
|
||
t => t.Id == id,
|
||
t => new DicomInstance
|
||
{
|
||
IsEncapsulated = syntax.IsEncapsulated,
|
||
TransferSytaxUID = transferSyntaxUID,
|
||
MediaStorageSOPClassUID = mediaStorageSOPClassUID,
|
||
SOPClassUID = sOPClassUID,
|
||
MediaStorageSOPInstanceUID = mediaStorageSOPInstanceUID
|
||
}, false);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var errorMsg = $"{DateTime.Now}❌ {file} 解析失败: {ex.Message}";
|
||
Console.WriteLine(errorMsg);
|
||
|
||
await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine);
|
||
}
|
||
finally
|
||
{
|
||
var done = Interlocked.Increment(ref processed);
|
||
double percent = done * 100.0 / total;
|
||
|
||
// 只在进度提升 >= 1% 时打印
|
||
if (percent - lastPercent >= 5.0 || done == total)
|
||
{
|
||
lastPercent = percent;
|
||
Console.WriteLine($"{DateTime.Now} 进度: {done}/{total} ({percent:F2}%)");
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
#region 维护已经下载本地的数据
|
||
|
||
[AllowAnonymous]
|
||
public async Task<IResponseOutput> ReadExcelData([FromServices] IRepository<DicomInstance> _instanceRepository)
|
||
{
|
||
var rows = await MiniExcel.QueryAsync<DicomSOPInfo>(@"C:\Users\hang\Desktop\维护数据读取.xlsx");
|
||
|
||
rows = rows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList();
|
||
|
||
int total = rows.Count();
|
||
int processed = 0;
|
||
double lastPercent = 0;
|
||
|
||
var outputErrorFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info_error.txt");
|
||
|
||
foreach (var item in rows)
|
||
{
|
||
|
||
|
||
try
|
||
{
|
||
await _instanceRepository.BatchUpdateNoTrackingAsync(
|
||
t => t.Id == item.InstanceId,
|
||
t => new DicomInstance
|
||
{
|
||
|
||
IsEncapsulated = item.IsEncapsulated,
|
||
TransferSytaxUID = item.TransferSyntaxUID,
|
||
MediaStorageSOPClassUID = item.MediaStorageSOPClassUID,
|
||
SOPClassUID = item.SOPClassUID,
|
||
MediaStorageSOPInstanceUID = item.MediaStorageSOPInstanceUID
|
||
}, false);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
|
||
var errorMsg = $"{item.InstanceId} {DateTime.Now} 更新失败: {ex.Message}";
|
||
Console.WriteLine(errorMsg);
|
||
|
||
await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine);
|
||
}
|
||
finally
|
||
{
|
||
processed++;
|
||
double percent = processed * 100.0 / total;
|
||
|
||
// 每提升 5% 或完成时输出
|
||
if (percent - lastPercent >= 2.0 || processed == total)
|
||
{
|
||
lastPercent = percent;
|
||
Console.WriteLine($"{DateTime.Now} 进度: {processed}/{total} ({percent:F2}%)");
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
public class DicomSOPInfo
|
||
{
|
||
public Guid InstanceId { get; set; }
|
||
|
||
|
||
public string TransferSyntaxUID { get; set; }
|
||
|
||
public string SOPClassUID { get; set; }
|
||
|
||
public string MediaStorageSOPClassUID { get; set; }
|
||
|
||
public string MediaStorageSOPInstanceUID { get; set; }
|
||
|
||
public bool IsEncapsulated => DicomTransferSyntax.Lookup(DicomUID.Parse(TransferSyntaxUID)).IsEncapsulated;
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
[AllowAnonymous]
|
||
public async Task<IResponseOutput> ReadExcelImageDataInstanceIsReading([FromServices] IRepository<DicomInstance> _instanceRepository,
|
||
[FromServices] IRepository<DicomSeries> _seriesRepository,
|
||
[FromServices] IRepository<DicomStudy> _studyRepository)
|
||
{
|
||
|
||
var trialId = Guid.Parse("01000000-ac13-0242-6397-08dcd2d2a091");
|
||
|
||
var rows = await MiniExcel.QueryAsync<DicomSOPInstanceInfo>(@"C:\Users\hang\Desktop\instanceReading.xlsx");
|
||
|
||
rows = rows.Where(t => !string.IsNullOrEmpty(t.SopInstanceUid) && t.SopInstanceUid.Length > 15).ToList();
|
||
|
||
var outputErrorFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info_error.txt");
|
||
|
||
|
||
//foreach (var batch in rows.Chunk(20))
|
||
//{
|
||
// var sopUids = batch.Select(x => x.SopInstanceUid).ToList();
|
||
|
||
// try
|
||
// {
|
||
// await _instanceRepository.BatchUpdateNoTrackingAsync(
|
||
// t => sopUids.Contains(t.SopInstanceUid) && t.TrialId == trialId,
|
||
// t => new DicomInstance
|
||
// {
|
||
// IsReading = true,
|
||
// IsDeleted = false
|
||
// }, false);
|
||
|
||
|
||
// await _seriesRepository.BatchUpdateNoTrackingAsync(
|
||
// t => t.DicomInstanceList.Any(t => sopUids.Contains(t.SopInstanceUid)) && t.TrialId == trialId,
|
||
// t => new DicomSeries
|
||
// {
|
||
// IsReading = true,
|
||
// IsDeleted = false
|
||
// }, false);
|
||
// }
|
||
// catch (Exception ex)
|
||
// {
|
||
// var errorMsg = $"{string.Join(",", sopUids)} {DateTime.Now} 批量更新失败: {ex.Message}";
|
||
// Console.WriteLine(errorMsg);
|
||
// await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine);
|
||
// }
|
||
//}
|
||
|
||
//找到该项目的检查,实时统计数量,并且回更数据库
|
||
|
||
var studyList = _studyRepository.Where(t => t.TrialId == trialId && (t.SeriesCount != t.SeriesList.Count() || t.InstanceCount != t.InstanceList.Count()))
|
||
.Select(t => new
|
||
{
|
||
t.Id,
|
||
t.StudyCode,
|
||
DBSeriesCount = t.SeriesCount,
|
||
DBInstanceCount = t.InstanceCount,
|
||
|
||
ActrualSeriesCount = t.SeriesList.Count(),
|
||
|
||
ActrualInstanceCount = t.InstanceList.Count(),
|
||
|
||
}).ToList();
|
||
|
||
|
||
var seriesList = _seriesRepository.Where(t => t.TrialId == trialId && t.InstanceCount != t.DicomInstanceList.Count())
|
||
.Select(t => new
|
||
{
|
||
SeriesId = t.Id,
|
||
t.DicomStudy.StudyCode,
|
||
DBInstanceCount = t.InstanceCount,
|
||
ActrualInstanceCount = t.DicomInstanceList.Count(),
|
||
|
||
}).ToList();
|
||
|
||
|
||
await File.AppendAllTextAsync(outputErrorFile, studyList.ToJsonStr() + Environment.NewLine);
|
||
|
||
await File.AppendAllTextAsync(outputErrorFile, seriesList.ToJsonStr() + Environment.NewLine);
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
public class DicomSOPInstanceInfo
|
||
{
|
||
public string SopInstanceUid { get; set; }
|
||
|
||
|
||
}
|
||
|
||
#endregion
|
||
|
||
|
||
#region 通过Excel 读取未下载的,边下载边维护数据
|
||
|
||
[AllowAnonymous]
|
||
public async Task<IResponseOutput> WriteNeedDealData([FromServices] IRepository<DicomInstance> _instanceRepository)
|
||
{
|
||
|
||
#region 获取差集数据
|
||
//var rows = await MiniExcel.QueryAsync<DicomSOPInfo>(@"C:\Users\hang\Desktop\维护数据读取.xlsx");
|
||
|
||
//rows = rows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList();
|
||
|
||
//var allRows = await MiniExcel.QueryAsync<NeedDealInstanceInfo>(@"C:\Users\hang\Desktop\AllData.xlsx");
|
||
|
||
//allRows = allRows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList();
|
||
|
||
//var needDealRows = allRows.Where(t => !rows.Select(c => c.InstanceId).Contains(t.InstanceId)).ToList();
|
||
|
||
//var outputFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info.txt");
|
||
|
||
//foreach (var item in needDealRows)
|
||
//{
|
||
// var line = string.Join(",", item.InstanceId, item.Path);
|
||
|
||
// await File.AppendAllTextAsync(outputFile, line + Environment.NewLine);
|
||
//}
|
||
|
||
#endregion
|
||
|
||
var folder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment);
|
||
|
||
var needDealRows = await MiniExcel.QueryAsync<NeedDealInstanceInfo>(Path.Combine(folder, "needDownload.xlsx"));
|
||
|
||
needDealRows = needDealRows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList();
|
||
|
||
|
||
|
||
var outputFile = Path.Combine(folder, $"{Guid.NewGuid()}_dicom_info.txt");
|
||
|
||
var outputErrorFile = Path.Combine(folder, $"{Guid.NewGuid()}_dicom_info_error.txt");
|
||
|
||
int total = needDealRows.Count();
|
||
|
||
Console.WriteLine($"需要处理数量{total}");
|
||
|
||
int processed = 0;
|
||
double lastPercent = 0;
|
||
|
||
foreach (var item in needDealRows)
|
||
{
|
||
|
||
try
|
||
{
|
||
await using var stream = await _oSSService.GetStreamFromOSSAsync(item.Path);
|
||
|
||
var dicomFile = DicomFile.Open(stream);
|
||
|
||
var dataset = dicomFile.Dataset;
|
||
var fileMeta = dicomFile.FileMetaInfo;
|
||
|
||
var pixelData = DicomPixelData.Create(dicomFile.Dataset);
|
||
|
||
//获取像素是否为封装形式
|
||
var syntax = dicomFile.Dataset.InternalTransferSyntax;
|
||
|
||
var stationName = dataset.GetSingleValueOrDefault(DicomTag.StationName, string.Empty);
|
||
|
||
var institutionName = dataset.GetSingleValueOrDefault(DicomTag.InstitutionName, string.Empty);
|
||
|
||
var manufacturer = dataset.GetSingleValueOrDefault(DicomTag.Manufacturer, string.Empty);
|
||
|
||
//PatientID TrialCode_SubjectCode
|
||
//var patientID = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
|
||
|
||
//SubjectCode
|
||
var clinicalTrialSubjectID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialSubjectID, string.Empty);
|
||
//访视visitNum
|
||
var clinicalTrialTimePointID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialTimePointID, string.Empty);
|
||
var studyInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty);
|
||
var seriesInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty);
|
||
var sOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty);
|
||
|
||
var numberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1);
|
||
|
||
|
||
// 传输语法
|
||
var transferSyntaxUID = fileMeta.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty);
|
||
var sOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty);
|
||
var mediaStorageSOPClassUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty);
|
||
|
||
var mediaStorageSOPInstanceUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty);
|
||
|
||
|
||
var line = string.Join(",",
|
||
item.InstanceId,
|
||
stationName,
|
||
institutionName,
|
||
manufacturer,
|
||
clinicalTrialSubjectID,
|
||
clinicalTrialTimePointID,
|
||
studyInstanceUID,
|
||
seriesInstanceUID,
|
||
sOPInstanceUID,
|
||
numberOfFrames,
|
||
transferSyntaxUID,
|
||
sOPClassUID,
|
||
mediaStorageSOPClassUID,
|
||
mediaStorageSOPInstanceUID
|
||
|
||
);
|
||
|
||
|
||
await File.AppendAllTextAsync(outputFile, line + Environment.NewLine);
|
||
|
||
//维护序列层级四个字段 后再用sql 维护study series 时间拆分 和 MediaStorageSOPInstanceUID
|
||
await _instanceRepository.BatchUpdateNoTrackingAsync(
|
||
t => t.Id == item.InstanceId,
|
||
t => new DicomInstance
|
||
{
|
||
IsEncapsulated = syntax.IsEncapsulated,
|
||
TransferSytaxUID = transferSyntaxUID,
|
||
MediaStorageSOPClassUID = mediaStorageSOPClassUID,
|
||
SOPClassUID = sOPClassUID,
|
||
MediaStorageSOPInstanceUID = mediaStorageSOPInstanceUID
|
||
}, false);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
|
||
var errorMsg = $"{DateTime.Now} ❌ 失败: {ex.Message} | InstanceId={item.InstanceId}, Path={item.Path}";
|
||
|
||
Console.WriteLine(errorMsg);
|
||
|
||
await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine);
|
||
}
|
||
finally
|
||
{
|
||
processed++;
|
||
double percent = processed * 100.0 / total;
|
||
|
||
// 每提升 5% 或完成时输出
|
||
if (percent - lastPercent >= 2.0 || processed == total)
|
||
{
|
||
lastPercent = percent;
|
||
Console.WriteLine($"{DateTime.Now} 进度: {processed}/{total} ({percent:F2}%)");
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
public class NeedDealInstanceInfo
|
||
{
|
||
public Guid InstanceId { get; set; }
|
||
|
||
public string Path { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取该项目的数据,进行维护
|
||
/// </summary>
|
||
/// <param name="_instanceRepository"></param>
|
||
/// <param name="trialId"></param>
|
||
/// <returns></returns>
|
||
[AllowAnonymous]
|
||
public async Task<IResponseOutput> WriteTrialNeedDealData([FromServices] IRepository<DicomInstance> _instanceRepository, Guid trialId)
|
||
{
|
||
|
||
#region 获取差集数据
|
||
//var rows = await MiniExcel.QueryAsync<DicomSOPInfo>(@"C:\Users\hang\Desktop\维护数据读取.xlsx");
|
||
|
||
//rows = rows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList();
|
||
|
||
//var allRows = await MiniExcel.QueryAsync<NeedDealInstanceInfo>(@"C:\Users\hang\Desktop\AllData.xlsx");
|
||
|
||
//allRows = allRows.Where(t => !string.IsNullOrEmpty(t.InstanceId.ToString())).ToList();
|
||
|
||
//var needDealRows = allRows.Where(t => !rows.Select(c => c.InstanceId).Contains(t.InstanceId)).ToList();
|
||
|
||
//var outputFile = Path.Combine(@"D:\dicomWrite", $"{Guid.NewGuid()}_dicom_info.txt");
|
||
|
||
//foreach (var item in needDealRows)
|
||
//{
|
||
// var line = string.Join(",", item.InstanceId, item.Path);
|
||
|
||
// await File.AppendAllTextAsync(outputFile, line + Environment.NewLine);
|
||
//}
|
||
|
||
#endregion
|
||
|
||
var folder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment);
|
||
|
||
var needDealRows = _instanceRepository.Where(t => t.TrialId == trialId && t.TransferSytaxUID == "").Select(t => new NeedDealInstanceInfo() { InstanceId = t.Id, Path = t.Path });
|
||
|
||
|
||
|
||
var outputFile = Path.Combine(folder, $"{Guid.NewGuid()}_dicom_info.txt");
|
||
|
||
var outputErrorFile = Path.Combine(folder, $"{Guid.NewGuid()}_dicom_info_error.txt");
|
||
|
||
int total = needDealRows.Count();
|
||
|
||
Console.WriteLine($"需要处理数量{total}");
|
||
|
||
int processed = 0;
|
||
double lastPercent = 0;
|
||
|
||
foreach (var item in needDealRows)
|
||
{
|
||
|
||
try
|
||
{
|
||
await using var stream = await _oSSService.GetStreamFromOSSAsync(item.Path);
|
||
|
||
var dicomFile = DicomFile.Open(stream);
|
||
|
||
var dataset = dicomFile.Dataset;
|
||
var fileMeta = dicomFile.FileMetaInfo;
|
||
|
||
var pixelData = DicomPixelData.Create(dicomFile.Dataset);
|
||
|
||
//获取像素是否为封装形式
|
||
var syntax = dicomFile.Dataset.InternalTransferSyntax;
|
||
|
||
var stationName = dataset.GetSingleValueOrDefault(DicomTag.StationName, string.Empty);
|
||
|
||
var institutionName = dataset.GetSingleValueOrDefault(DicomTag.InstitutionName, string.Empty);
|
||
|
||
var manufacturer = dataset.GetSingleValueOrDefault(DicomTag.Manufacturer, string.Empty);
|
||
|
||
//PatientID TrialCode_SubjectCode
|
||
//var patientID = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
|
||
|
||
//SubjectCode
|
||
var clinicalTrialSubjectID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialSubjectID, string.Empty);
|
||
//访视visitNum
|
||
var clinicalTrialTimePointID = dataset.GetSingleValueOrDefault(DicomTag.ClinicalTrialTimePointID, string.Empty);
|
||
var studyInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty);
|
||
var seriesInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty);
|
||
var sOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty);
|
||
|
||
var numberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 1);
|
||
|
||
|
||
// 传输语法
|
||
var transferSyntaxUID = fileMeta.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty);
|
||
var sOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty);
|
||
var mediaStorageSOPClassUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty);
|
||
|
||
var mediaStorageSOPInstanceUID = fileMeta.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty);
|
||
|
||
|
||
var line = string.Join(",",
|
||
item.InstanceId,
|
||
stationName,
|
||
institutionName,
|
||
manufacturer,
|
||
clinicalTrialSubjectID,
|
||
clinicalTrialTimePointID,
|
||
studyInstanceUID,
|
||
seriesInstanceUID,
|
||
sOPInstanceUID,
|
||
numberOfFrames,
|
||
transferSyntaxUID,
|
||
sOPClassUID,
|
||
mediaStorageSOPClassUID,
|
||
mediaStorageSOPInstanceUID
|
||
|
||
);
|
||
|
||
|
||
await File.AppendAllTextAsync(outputFile, line + Environment.NewLine);
|
||
|
||
//维护序列层级四个字段 后再用sql 维护study series 时间拆分 和 MediaStorageSOPInstanceUID
|
||
await _instanceRepository.BatchUpdateNoTrackingAsync(
|
||
t => t.Id == item.InstanceId,
|
||
t => new DicomInstance
|
||
{
|
||
IsEncapsulated = syntax.IsEncapsulated,
|
||
TransferSytaxUID = transferSyntaxUID,
|
||
MediaStorageSOPClassUID = mediaStorageSOPClassUID,
|
||
SOPClassUID = sOPClassUID,
|
||
MediaStorageSOPInstanceUID = mediaStorageSOPInstanceUID
|
||
}, false);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
|
||
var errorMsg = $"{DateTime.Now} ❌ 失败: {ex.Message} | InstanceId={item.InstanceId}, Path={item.Path}";
|
||
|
||
Console.WriteLine(errorMsg);
|
||
|
||
await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine);
|
||
}
|
||
finally
|
||
{
|
||
processed++;
|
||
double percent = processed * 100.0 / total;
|
||
|
||
// 每提升 5% 或完成时输出
|
||
if (percent - lastPercent >= 2.0 || processed == total)
|
||
{
|
||
lastPercent = percent;
|
||
Console.WriteLine($"{DateTime.Now} 进度: {processed}/{total} ({percent:F2}%)");
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
#endregion
|
||
|
||
|
||
#region oss 下载删除影像 ,并且恢复数据
|
||
[AllowAnonymous]
|
||
public async Task<IResponseOutput> RestoreDBOSSDate(
|
||
[FromServices] IOSSService _oSSService, [FromServices] IWebHostEnvironment _hostEnvironment, [FromServices] IRepository<DicomStudy> _studyRepository)
|
||
{
|
||
var folder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment);
|
||
|
||
var outputFile = Path.Combine(folder, $"{Guid.NewGuid()}_deleteKey_info.txt");
|
||
var outputFile2 = Path.Combine(folder, $"{Guid.NewGuid()}_deleteKeyExport_info.txt");
|
||
|
||
var outputErrorFile = Path.Combine(folder, $"{Guid.NewGuid()}_deleteKeyerror.txt");
|
||
var outputErrorFile2 = Path.Combine(folder, $"{Guid.NewGuid()}_deleteKeyerrorStudy.txt");
|
||
|
||
|
||
var aliConfig = _oSSService.ObjectStoreServiceOptions.AliyunOSS;
|
||
|
||
var tempToken = _oSSService.GetObjectStoreTempToken();
|
||
|
||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
|
||
tempToken.AliyunOSS.AccessKeyId,
|
||
tempToken.AliyunOSS.AccessKeySecret,
|
||
tempToken.AliyunOSS.SecurityToken);
|
||
|
||
|
||
var allVersions = new List<ObjectVersionSummary>();
|
||
var allDeleteMarkers = new List<DeleteMarkerSummary>();
|
||
|
||
var request = new ListObjectVersionsRequest(tempToken.AliyunOSS.BucketName)
|
||
{
|
||
Prefix = "01000000-ac13-0242-6397-08dcd2d2a091/Image",
|
||
//Prefix = "01000000-ac13-0242-6397-08dcd2d2a091/Image/08dd9c04-c1b2-c2da-0242-ac1301000000/01000000-ac13-0242-235b-08dd9c04c1b3",
|
||
MaxKeys = 1000,
|
||
};
|
||
|
||
ObjectVersionList result;
|
||
do
|
||
{
|
||
|
||
result = _ossClient.ListObjectVersions(request);
|
||
|
||
if (result.ObjectVersionSummaries != null)
|
||
allVersions.AddRange(result.ObjectVersionSummaries);
|
||
|
||
if (result.DeleteMarkerSummaries != null)
|
||
allDeleteMarkers.AddRange(result.DeleteMarkerSummaries);
|
||
|
||
request.KeyMarker = result.NextKeyMarker;
|
||
request.VersionIdMarker = result.NextVersionIdMarker;
|
||
|
||
} while (result.IsTruncated);
|
||
|
||
Console.WriteLine($"共找到 {allDeleteMarkers.Count} 个删除标记");
|
||
|
||
|
||
|
||
int total = allDeleteMarkers.Count;
|
||
|
||
int processed = 0;
|
||
double lastPercent = 0;
|
||
|
||
|
||
// 按 Key 分组,找每个删除标记前的最近版本
|
||
var versionsByKey = allVersions
|
||
.GroupBy(v => v.Key)
|
||
.ToDictionary(g => g.Key, g => g.OrderByDescending(x => x.LastModified).ToList());
|
||
|
||
foreach (var del in allDeleteMarkers)
|
||
{
|
||
#region 防止阿里云过期
|
||
if (tempToken.AliyunOSS.Expiration.AddSeconds(10) <= DateTime.Now)
|
||
{
|
||
tempToken = _oSSService.GetObjectStoreTempToken();
|
||
|
||
_ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
|
||
tempToken.AliyunOSS.AccessKeyId,
|
||
tempToken.AliyunOSS.AccessKeySecret,
|
||
tempToken.AliyunOSS.SecurityToken);
|
||
}
|
||
|
||
#endregion
|
||
|
||
if (!versionsByKey.TryGetValue(del.Key, out var versions))
|
||
continue; // 没有历史版本无法恢复
|
||
|
||
var prevVersion = versions.FirstOrDefault(v => v.LastModified < del.LastModified);
|
||
if (prevVersion == null)
|
||
continue; // 没找到可恢复版本
|
||
|
||
if (Path.GetExtension(prevVersion.Key).IsNotNullOrEmpty())
|
||
{
|
||
continue;//不是dicom 文件
|
||
}
|
||
|
||
|
||
try
|
||
{
|
||
//await File.AppendAllTextAsync(outputFile, $"{prevVersion.Key},{prevVersion.VersionId}" + Environment.NewLine);
|
||
|
||
var getReq = new GetObjectRequest(tempToken.AliyunOSS.BucketName, prevVersion.Key)
|
||
{
|
||
VersionId = prevVersion.VersionId
|
||
};
|
||
|
||
using (var getResult = _ossClient.GetObject(getReq))
|
||
using (var memStream = new MemoryStream())
|
||
{
|
||
// 先把 OSS 流复制到内存流
|
||
getResult.Content.CopyTo(memStream);
|
||
memStream.Position = 0;
|
||
|
||
// 读取 DICOM 信息
|
||
var dicomFile = DicomFile.Open(memStream);
|
||
var studyInstanceUID = dicomFile.Dataset.GetString(DicomTag.StudyInstanceUID);
|
||
|
||
var findInfo = await _studyRepository.Where(t => t.StudyInstanceUid == studyInstanceUID && t.TrialId == Guid.Parse("01000000-ac13-0242-6397-08dcd2d2a091"))
|
||
.Select(t => new { t.StudyInstanceUid, t.Subject.Code, t.SubjectVisit.VisitName, t.SubjectId, t.SubjectVisitId }).FirstOrDefaultAsync();
|
||
|
||
if (findInfo != null)
|
||
{
|
||
|
||
// 再保存到另一个路径(可以使用 fo-dicom 保存)
|
||
|
||
var fileName = Path.GetFileNameWithoutExtension(prevVersion.Key);
|
||
var anotherPath = Path.Combine(folder, findInfo.Code, findInfo.VisitName, studyInstanceUID, fileName);
|
||
// 去掉 folder 部分,得到相对路径
|
||
var relativePath = Path.GetRelativePath(folder, anotherPath);
|
||
Directory.CreateDirectory(Path.GetDirectoryName(anotherPath));
|
||
dicomFile.Save(anotherPath);
|
||
|
||
await File.AppendAllTextAsync(outputFile2, $"{findInfo.SubjectId},{findInfo.SubjectVisitId},{prevVersion.Key},{prevVersion.VersionId},{relativePath},{findInfo.Code},{findInfo.VisitName},{findInfo.StudyInstanceUid},{fileName}" + Environment.NewLine);
|
||
}
|
||
else
|
||
{
|
||
await File.AppendAllTextAsync(outputErrorFile2, $"{studyInstanceUID},{prevVersion.Key},{prevVersion.VersionId}" + Environment.NewLine);
|
||
}
|
||
|
||
//Console.WriteLine($"读取到 studyInstanceUID: {studyInstanceUID}");
|
||
|
||
//var localPath = Path.Combine(folder, prevVersion.Key.Trim('/').Replace('/', Path.DirectorySeparatorChar));
|
||
//Directory.CreateDirectory(Path.GetDirectoryName(localPath));
|
||
//// 保存到原本路径
|
||
//memStream.Position = 0;
|
||
//using (var fs = File.Create(localPath))
|
||
//{
|
||
// memStream.CopyTo(fs);
|
||
//}
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
//Console.WriteLine($"✅ 下载成功: {prevVersion.Key} (version={prevVersion.VersionId})");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"❌ 下载失败: {prevVersion.Key}, 错误: {ex.Message}");
|
||
|
||
await File.AppendAllTextAsync(outputErrorFile, $"{prevVersion.Key},{prevVersion.VersionId}" + Environment.NewLine);
|
||
}
|
||
finally
|
||
{
|
||
processed++;
|
||
double percent = processed * 100.0 / total;
|
||
|
||
// 每提升 5% 或完成时输出
|
||
if (percent - lastPercent >= 2.0 || processed == total)
|
||
{
|
||
lastPercent = percent;
|
||
Console.WriteLine($"{DateTime.Now} 进度: {processed}/{total} ({percent:F2}%)");
|
||
}
|
||
}
|
||
|
||
|
||
// 使用 CopyObject 把历史版本拷贝为最新版本(恢复)
|
||
//var copyReq = new CopyObjectRequest
|
||
//{
|
||
// Bucket = bucketName,
|
||
// Key = prevVersion.Key,
|
||
// SourceBucket = bucketName,
|
||
// SourceKey = prevVersion.Key,
|
||
// SourceVersionId = prevVersion.VersionId
|
||
//};
|
||
|
||
//try
|
||
//{
|
||
// var copyResult = client.CopyObject(copyReq);
|
||
// Console.WriteLine($"✅ 恢复成功: {prevVersion.Key} -> newVersionId={copyResult.VersionId}");
|
||
//}
|
||
//catch (Exception ex)
|
||
//{
|
||
// Console.WriteLine($"❌ 恢复失败: {prevVersion.Key}, 错误: {ex.Message}");
|
||
//}
|
||
}
|
||
|
||
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
|
||
|
||
public async Task<IResponseOutput> OSSDeleteReStorre([FromServices] IOSSService _oSSService, [FromServices] IWebHostEnvironment _hostEnvironment)
|
||
{
|
||
var aliConfig = _oSSService.ObjectStoreServiceOptions.AliyunOSS;
|
||
|
||
var tempToken = _oSSService.GetObjectStoreTempToken();
|
||
|
||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
|
||
tempToken.AliyunOSS.AccessKeyId,
|
||
tempToken.AliyunOSS.AccessKeySecret,
|
||
tempToken.AliyunOSS.SecurityToken);
|
||
|
||
|
||
var allVersions = new List<ObjectVersionSummary>();
|
||
var allDeleteMarkers = new List<DeleteMarkerSummary>();
|
||
|
||
var request = new ListObjectVersionsRequest(tempToken.AliyunOSS.BucketName)
|
||
{
|
||
Prefix = "test-delete-restore",
|
||
//Prefix = "01000000-ac13-0242-6397-08dcd2d2a091/Image/08dd9c04-c1b2-c2da-0242-ac1301000000/01000000-ac13-0242-235b-08dd9c04c1b3",
|
||
MaxKeys = 1000,
|
||
};
|
||
|
||
ObjectVersionList result;
|
||
do
|
||
{
|
||
|
||
result = _ossClient.ListObjectVersions(request);
|
||
|
||
if (result.ObjectVersionSummaries != null)
|
||
allVersions.AddRange(result.ObjectVersionSummaries);
|
||
|
||
if (result.DeleteMarkerSummaries != null)
|
||
allDeleteMarkers.AddRange(result.DeleteMarkerSummaries);
|
||
|
||
request.KeyMarker = result.NextKeyMarker;
|
||
request.VersionIdMarker = result.NextVersionIdMarker;
|
||
|
||
} while (result.IsTruncated);
|
||
|
||
Console.WriteLine($"共找到 {allDeleteMarkers.Count} 个删除标记");
|
||
|
||
var versionsByKey = allVersions
|
||
.GroupBy(v => v.Key)
|
||
.ToDictionary(g => g.Key, g => g.OrderByDescending(x => x.LastModified).ToList());
|
||
|
||
|
||
foreach (var del in allDeleteMarkers)
|
||
{
|
||
#region 防止阿里云过期
|
||
if (tempToken.AliyunOSS.Expiration.AddSeconds(10) <= DateTime.Now)
|
||
{
|
||
tempToken = _oSSService.GetObjectStoreTempToken();
|
||
|
||
_ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
|
||
tempToken.AliyunOSS.AccessKeyId,
|
||
tempToken.AliyunOSS.AccessKeySecret,
|
||
tempToken.AliyunOSS.SecurityToken);
|
||
}
|
||
|
||
#endregion
|
||
|
||
if (!versionsByKey.TryGetValue(del.Key, out var versions))
|
||
continue; // 没有历史版本无法恢复
|
||
|
||
var prevVersion = versions.FirstOrDefault(v => v.LastModified < del.LastModified);
|
||
if (prevVersion == null)
|
||
continue; // 没找到可恢复版本
|
||
|
||
|
||
|
||
|
||
// 创建 CopyObject 请求
|
||
// 先用构造函数指定源和目标
|
||
var copyReq = new CopyObjectRequest(
|
||
sourceBucketName: tempToken.AliyunOSS.BucketName,
|
||
sourceKey: prevVersion.Key,
|
||
destinationBucketName: tempToken.AliyunOSS.BucketName,
|
||
destinationKey: prevVersion.Key // 覆盖到同名 Key,达到“恢复”的效果
|
||
);
|
||
|
||
// 再设置版本号
|
||
copyReq.SourceVersionId = prevVersion.VersionId;
|
||
|
||
|
||
|
||
try
|
||
{
|
||
var copyResult = _ossClient.CopyObject(copyReq);
|
||
Console.WriteLine($"✅ 恢复成功: {prevVersion.Key}, 新版本ID={copyResult.VersionId}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"❌ 恢复失败: {prevVersion.Key}, 错误: {ex.Message}");
|
||
}
|
||
|
||
|
||
}
|
||
|
||
return ResponseOutput.Ok();
|
||
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取excel 恢复oss 数据,读取dicom 恢复序列和Instance
|
||
/// </summary>
|
||
/// <param name="_oSSService"></param>
|
||
/// <param name="_hostEnvironment"></param>
|
||
/// <returns></returns>
|
||
[AllowAnonymous]
|
||
public async Task<IResponseOutput> ReadExcelReStorreOSSDeleteDataAndDBData([FromServices] IOSSService _oSSService, [FromServices] IWebHostEnvironment _hostEnvironment, string subjectCode)
|
||
{
|
||
var folder = FileStoreHelper.GetIRaCISRootDataFolder(_hostEnvironment);
|
||
|
||
var trialId = Guid.Parse("01000000-ac13-0242-6397-08dcd2d2a091");
|
||
|
||
var rows = await MiniExcel.QueryAsync<RestoreOSSDeleteDataDTO>(Path.Combine(folder, "删除恢复.xlsx"));
|
||
|
||
var restoreRows = await MiniExcel.QueryAsync<RestoreInstanceInfo>(Path.Combine(folder, "删除需要恢复的数据.xlsx"));
|
||
|
||
rows = rows.Where(t => t.Key.IsNotNullOrEmpty()).WhereIf(subjectCode.IsNotNullOrEmpty(), t => t.SubjectCode == subjectCode.TrimStart('0')).ToList();
|
||
|
||
restoreRows = restoreRows.Where(t => t.SOPInstanceUID.IsNotNullOrEmpty()).WhereIf(subjectCode.IsNotNullOrEmpty(), t => t.SubjectCode == subjectCode).ToList();
|
||
|
||
Console.WriteLine($"恢复数量: {restoreRows.Count()}");
|
||
|
||
int total = rows.Count();
|
||
int processed = 0;
|
||
double lastPercent = 0;
|
||
|
||
var outputErrorFile = Path.Combine(folder, $"{Guid.NewGuid()}_dicom_info_error.txt");
|
||
|
||
var aliConfig = _oSSService.ObjectStoreServiceOptions.AliyunOSS;
|
||
|
||
var tempToken = _oSSService.GetObjectStoreTempToken();
|
||
|
||
var _ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
|
||
tempToken.AliyunOSS.AccessKeyId,
|
||
tempToken.AliyunOSS.AccessKeySecret,
|
||
tempToken.AliyunOSS.SecurityToken);
|
||
|
||
|
||
var restoreCount = 0;
|
||
foreach (var item in rows)
|
||
{
|
||
//不包含该subject的忽略
|
||
if (!restoreRows.Any(t => t.SubjectCode.TrimStart('0') == item.SubjectCode))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
try
|
||
{
|
||
|
||
//根据本地文件匹配studyInstanceUid 不匹配忽略
|
||
|
||
var localPath = Path.Combine(folder, item.RelativePath);
|
||
|
||
|
||
#region 读取本地dicom ,判断数据库是否存在该序列
|
||
|
||
var dicomFilePath = Path.Combine(folder, item.RelativePath);
|
||
|
||
|
||
var dicomFile = await DicomFile.OpenAsync(dicomFilePath);
|
||
|
||
string sopInstanceUid = dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty);
|
||
|
||
|
||
if (!restoreRows.Any(t => t.SOPInstanceUID == sopInstanceUid))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
restoreCount++;
|
||
|
||
Console.WriteLine($"恢复SOPInstanceUID{sopInstanceUid},当前数量{restoreCount}");
|
||
|
||
await ArchiveDicomFileAsync(dicomFile.Dataset, trialId, item.SubjectId, item.SubjectVisitId);
|
||
|
||
#endregion
|
||
|
||
|
||
#region 防止阿里云过期
|
||
if (tempToken.AliyunOSS.Expiration.AddSeconds(10) <= DateTime.Now)
|
||
{
|
||
tempToken = _oSSService.GetObjectStoreTempToken();
|
||
|
||
_ossClient = new OssClient(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint,
|
||
tempToken.AliyunOSS.AccessKeyId,
|
||
tempToken.AliyunOSS.AccessKeySecret,
|
||
tempToken.AliyunOSS.SecurityToken);
|
||
}
|
||
|
||
#endregion
|
||
|
||
|
||
// 创建 CopyObject 请求
|
||
// 先用构造函数指定源和目标
|
||
var copyReq = new CopyObjectRequest(
|
||
sourceBucketName: tempToken.AliyunOSS.BucketName,
|
||
sourceKey: item.Key,
|
||
destinationBucketName: tempToken.AliyunOSS.BucketName,
|
||
destinationKey: item.Key // 覆盖到同名 Key,达到“恢复”的效果
|
||
);
|
||
|
||
// 再设置版本号
|
||
copyReq.SourceVersionId = item.VersionId;
|
||
|
||
var copyResult = _ossClient.CopyObject(copyReq);
|
||
|
||
}
|
||
|
||
catch (Exception ex)
|
||
{
|
||
|
||
var errorMsg = $"❌ 恢复失败: {item.Key}, 错误: {ex.Message}";
|
||
Console.WriteLine(errorMsg);
|
||
|
||
await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine);
|
||
}
|
||
finally
|
||
{
|
||
processed++;
|
||
double percent = processed * 100.0 / total;
|
||
|
||
if (percent - lastPercent >= 2.0 || processed == total)
|
||
{
|
||
lastPercent = percent;
|
||
Console.WriteLine($"{DateTime.Now} 进度: {processed}/{total} ({percent:F2}%)");
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
await _studyRepository.SaveChangesAsync();
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
public class RestoreOSSDeleteDataDTO
|
||
{
|
||
public Guid SubjectId { get; set; }
|
||
|
||
public Guid SubjectVisitId { get; set; }
|
||
|
||
public string Key { get; set; }
|
||
|
||
public string VersionId { get; set; }
|
||
|
||
public string RelativePath { get; set; }
|
||
|
||
public string SubjectCode { get; set; }
|
||
public string FileName { get; set; }
|
||
|
||
#endregion
|
||
}
|
||
|
||
public class RestoreInstanceInfo
|
||
{
|
||
public string SubjectCode { get; set; }
|
||
|
||
public string VisitName { get; set; }
|
||
|
||
public string SOPInstanceUID { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 单个文件接收 归档
|
||
/// </summary>
|
||
/// <param name="dataset"></param>
|
||
/// <param name="trialId"></param>
|
||
/// <param name="subjectId"></param>
|
||
/// <param name="subjectVisitId"></param>
|
||
/// <returns></returns>
|
||
/// <exception cref="NotImplementedException"></exception>
|
||
public async Task ArchiveDicomFileAsync(DicomDataset dataset, Guid trialId, Guid subjectId, Guid subjectVisitId)
|
||
{
|
||
string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID);
|
||
string seriesInstanceUid = dataset.GetString(DicomTag.SeriesInstanceUID);
|
||
string sopInstanceUid = dataset.GetString(DicomTag.SOPInstanceUID);
|
||
|
||
Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, trialId.ToString());
|
||
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, trialId.ToString());
|
||
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, trialId.ToString());
|
||
|
||
var isSeriesNeedAdd = false;
|
||
var isInstanceNeedAdd = false;
|
||
|
||
|
||
var findStudy = await _studyRepository.FirstOrDefaultAsync(t => t.Id == studyId);
|
||
var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
|
||
var findInstance = await _instanceRepository.FirstOrDefaultAsync(t => t.Id == instanceId);
|
||
|
||
|
||
|
||
if (findSerice == null)
|
||
{
|
||
isSeriesNeedAdd = true;
|
||
|
||
findSerice = new DicomSeries
|
||
{
|
||
|
||
|
||
|
||
Id = seriesId,
|
||
StudyId = findStudy.Id,
|
||
|
||
StudyInstanceUid = findStudy.StudyInstanceUid,
|
||
SeriesInstanceUid = seriesInstanceUid,
|
||
SeriesNumber = dataset.GetSingleValueOrDefault(DicomTag.SeriesNumber, 1),
|
||
//SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, DateTime.Now).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, DateTime.Now).TimeOfDay),
|
||
//SeriesTime = DateTime.TryParse(dataset.GetSingleValue<string>(DicomTag.SeriesDate) + dataset.GetSingleValue<string>(DicomTag.SeriesTime), out DateTime dt) ? dt : null,
|
||
SeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.SeriesDate).Add(dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.SeriesTime).TimeOfDay),
|
||
|
||
DicomSeriesDate = dataset.GetSingleValueOrDefault(DicomTag.SeriesDate, string.Empty),
|
||
DicomSeriesTime = dataset.GetSingleValueOrDefault(DicomTag.SeriesTime, string.Empty),
|
||
|
||
Modality = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
|
||
Description = dataset.GetSingleValueOrDefault(DicomTag.SeriesDescription, string.Empty),
|
||
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
|
||
|
||
ImagePositionPatient = dataset.GetSingleValueOrDefault(DicomTag.ImagePositionPatient, string.Empty),
|
||
ImageOrientationPatient = dataset.GetSingleValueOrDefault(DicomTag.ImageOrientationPatient, string.Empty),
|
||
BodyPartExamined = dataset.GetSingleValueOrDefault(DicomTag.BodyPartExamined, string.Empty),
|
||
SequenceName = dataset.GetSingleValueOrDefault(DicomTag.SequenceName, string.Empty),
|
||
ProtocolName = dataset.GetSingleValueOrDefault(DicomTag.ProtocolName, string.Empty),
|
||
ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
|
||
|
||
AcquisitionTime = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionTime, string.Empty),
|
||
AcquisitionNumber = dataset.GetSingleValueOrDefault(DicomTag.AcquisitionNumber, string.Empty),
|
||
TriggerTime = dataset.GetSingleValueOrDefault(DicomTag.TriggerTime, string.Empty),
|
||
|
||
TrialId = trialId,
|
||
SubjectId = subjectId,
|
||
SubjectVisitId = subjectVisitId,
|
||
|
||
InstanceCount = 0
|
||
};
|
||
|
||
++findStudy.SeriesCount;
|
||
}
|
||
|
||
|
||
var transferSyntaxUID = dataset.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty);
|
||
|
||
var isEncapsulated = false;
|
||
if (transferSyntaxUID.IsNotNullOrEmpty())
|
||
{
|
||
isEncapsulated = DicomTransferSyntax.Lookup(DicomUID.Parse(transferSyntaxUID)).IsEncapsulated;
|
||
}
|
||
|
||
if (findInstance == null)
|
||
{
|
||
isInstanceNeedAdd = true;
|
||
findInstance = new DicomInstance
|
||
{
|
||
Id = instanceId,
|
||
StudyId = findStudy.Id,
|
||
SeriesId = findSerice.Id,
|
||
StudyInstanceUid = findStudy.StudyInstanceUid,
|
||
SeriesInstanceUid = findSerice.SeriesInstanceUid,
|
||
|
||
TrialId = trialId,
|
||
SubjectId = subjectId,
|
||
SubjectVisitId = subjectVisitId,
|
||
|
||
|
||
|
||
SopInstanceUid = sopInstanceUid,
|
||
SOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty),
|
||
MediaStorageSOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty),
|
||
TransferSytaxUID = transferSyntaxUID,
|
||
MediaStorageSOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty),
|
||
IsEncapsulated = isEncapsulated,
|
||
|
||
InstanceNumber = dataset.GetSingleValueOrDefault(DicomTag.InstanceNumber, 1),
|
||
InstanceTime = dataset.GetSingleValueOrDefault(DicomTag.ContentDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.ContentDate).Add(dataset.GetSingleValueOrDefault(DicomTag.ContentTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.ContentTime).TimeOfDay),
|
||
|
||
CPIStatus = false,
|
||
ImageRows = dataset.GetSingleValueOrDefault(DicomTag.Rows, 0),
|
||
ImageColumns = dataset.GetSingleValueOrDefault(DicomTag.Columns, 0),
|
||
SliceLocation = dataset.GetSingleValueOrDefault(DicomTag.SliceLocation, 0),
|
||
|
||
SliceThickness = dataset.GetSingleValueOrDefault(DicomTag.SliceThickness, string.Empty),
|
||
NumberOfFrames = dataset.GetSingleValueOrDefault(DicomTag.NumberOfFrames, 0),
|
||
PixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.PixelSpacing, string.Empty),
|
||
ImagerPixelSpacing = dataset.GetSingleValueOrDefault(DicomTag.ImagerPixelSpacing, string.Empty),
|
||
FrameOfReferenceUID = dataset.GetSingleValueOrDefault(DicomTag.FrameOfReferenceUID, string.Empty),
|
||
WindowCenter = dataset.GetSingleValueOrDefault(DicomTag.WindowCenter, string.Empty),
|
||
WindowWidth = dataset.GetSingleValueOrDefault(DicomTag.WindowWidth, string.Empty),
|
||
};
|
||
|
||
++findStudy.InstanceCount;
|
||
++findSerice.InstanceCount;
|
||
}
|
||
|
||
|
||
if (isSeriesNeedAdd)
|
||
{
|
||
await _seriesRepository.AddAsync(findSerice);
|
||
}
|
||
if (isInstanceNeedAdd)
|
||
{
|
||
await _instanceRepository.AddAsync(findInstance);
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
} |