Compare commits
No commits in common. "9669032e6cb40ae16370d4a2eaa22053deec5650" and "25c5b32fa71bb4ff89bf7e3226fd4fb21ee4d937" have entirely different histories.
9669032e6c
...
25c5b32fa7
|
|
@ -19,7 +19,7 @@
|
||||||
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.1" />
|
<PackageReference Include="fo-dicom.Imaging.ImageSharp" Version="5.2.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
|
||||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||||
<PackageReference Include="Minio" Version="6.0.3" />
|
<PackageReference Include="Minio" Version="6.0.4" />
|
||||||
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
|
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
|
||||||
<TreatAsUsed>true</TreatAsUsed>
|
<TreatAsUsed>true</TreatAsUsed>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.10" />
|
||||||
<PackageReference Include="MimeKit" Version="4.11.0" />
|
<PackageReference Include="MimeKit" Version="4.11.0" />
|
||||||
<PackageReference Include="MiniExcel" Version="1.41.2" />
|
<PackageReference Include="MiniExcel" Version="1.41.2" />
|
||||||
<PackageReference Include="Minio" Version="6.0.3" />
|
<PackageReference Include="Minio" Version="6.0.5" />
|
||||||
<PackageReference Include="MiniWord" Version="0.9.2" />
|
<PackageReference Include="MiniWord" Version="0.9.2" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||||
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
|
<PackageReference Include="My.Extensions.Localization.Json" Version="3.3.0">
|
||||||
|
|
|
||||||
|
|
@ -1118,14 +1118,6 @@
|
||||||
<param name="trialId"></param>
|
<param name="trialId"></param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
<!-- Badly formed XML comment ignored for member "M:IRaCIS.Core.Application.Service.TrialImageDownloadService.TrialImageAddExtralField(System.Guid,IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomInstance},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomStudy},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomSeries})" -->
|
|
||||||
<member name="M:IRaCIS.Core.Application.Service.TrialImageDownloadService.DownloadDeleteTrialImage(System.Guid)">
|
|
||||||
<summary>
|
|
||||||
下载已经删除的影像
|
|
||||||
</summary>
|
|
||||||
<param name="trialId"></param>
|
|
||||||
<returns></returns>
|
|
||||||
</member>
|
|
||||||
<member name="T:IRaCIS.Core.Application.Service.AttachmentService">
|
<member name="T:IRaCIS.Core.Application.Service.AttachmentService">
|
||||||
<summary>
|
<summary>
|
||||||
医生文档关联关系维护
|
医生文档关联关系维护
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,22 @@
|
||||||
using DocumentFormat.OpenXml.EMMA;
|
using DocumentFormat.OpenXml.EMMA;
|
||||||
using DocumentFormat.OpenXml.Office2010.Excel;
|
|
||||||
using DocumentFormat.OpenXml.Office2013.Drawing.ChartStyle;
|
|
||||||
using FellowOakDicom;
|
using FellowOakDicom;
|
||||||
using FellowOakDicom.Imaging;
|
using FellowOakDicom.Imaging;
|
||||||
using FellowOakDicom.Imaging.Render;
|
using FellowOakDicom.Imaging.Render;
|
||||||
using FellowOakDicom.IO.Buffer;
|
using FellowOakDicom.IO.Buffer;
|
||||||
using IRaCIS.Core.Application.Helper;
|
using IRaCIS.Core.Application.Helper;
|
||||||
using IRaCIS.Core.Application.ViewModel;
|
|
||||||
using IRaCIS.Core.Domain.Models;
|
|
||||||
using MassTransit;
|
using MassTransit;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using MiniExcelLibs;
|
|
||||||
using SharpCompress.Common;
|
using SharpCompress.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using static IRaCIS.Core.Domain.Share.StaticData;
|
using static IRaCIS.Core.Domain.Share.StaticData;
|
||||||
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
|
|
||||||
|
|
||||||
namespace IRaCIS.Core.Application.Service
|
namespace IRaCIS.Core.Application.Service
|
||||||
{
|
{
|
||||||
|
|
@ -251,8 +243,7 @@ namespace IRaCIS.Core.Application.Service
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public async Task<IResponseOutput> DownloadAndUploadTrialData(Guid trialId,
|
public async Task<IResponseOutput> DownloadAndUploadTrialData(Guid trialId, [FromServices] IRepository<DicomInstance> _instanceRepository,
|
||||||
[FromServices] IRepository<DicomInstance> _instanceRepository,
|
|
||||||
[FromServices] IRepository<DicomStudy> _studyRepository,
|
[FromServices] IRepository<DicomStudy> _studyRepository,
|
||||||
[FromServices] IRepository<DicomSeries> _seriesRepository)
|
[FromServices] IRepository<DicomSeries> _seriesRepository)
|
||||||
{
|
{
|
||||||
|
|
@ -343,609 +334,6 @@ namespace IRaCIS.Core.Application.Service
|
||||||
return ResponseOutput.Ok();
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue