irc-netcore-api/IRaCIS.Core.Application/Service/Common/TrialImageDownloadService.cs

951 lines
38 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

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

using DocumentFormat.OpenXml.EMMA;
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 MassTransit;
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.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) : 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();
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);
}
}
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;
}
#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}";
Console.WriteLine(errorMsg);
await File.AppendAllTextAsync(outputErrorFile, errorMsg + Environment.NewLine);
}
finally
{
processed++;
double percent = processed * 100.0 / total;
// 每提升 5% 或完成时输出
if (percent - lastPercent >= 5.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; }
}
#endregion
}
}