dir 增加字段
continuous-integration/drone/push Build is passing Details

Test_IRC_Net8
hang 2025-08-08 14:29:29 +08:00
parent eafceeb5d1
commit 875a3668c1
10 changed files with 20167 additions and 76 deletions

View File

@ -61,77 +61,89 @@ namespace IRaCIS.Core.Application.Helper
foreach (var item in list.OrderBy(t => t.SeriesNumber).ThenBy(t => t.InstanceNumber)) foreach (var item in list.OrderBy(t => t.SeriesNumber).ThenBy(t => t.InstanceNumber))
{ {
var dicomUid = DicomUID.Enumerate().FirstOrDefault(uid => uid.UID == item.TransferSytaxUID); var dicomUid = DicomUID.Enumerate().FirstOrDefault(uid => uid.UID == item.TransferSytaxUID);
var ts = DicomTransferSyntax.Query(dicomUid);
var dataset = new DicomDataset(ts) if (dicomUid != null)
{ {
{ DicomTag.PatientID, item.PatientId ?? string.Empty }, var ts = DicomTransferSyntax.Query(dicomUid);
{ DicomTag.PatientName, item.PatientName ?? string.Empty },
{ DicomTag.PatientBirthDate, item.PatientBirthDate ?? string.Empty },
{ DicomTag.PatientSex, item.PatientSex ?? string.Empty },
{ DicomTag.StudyInstanceUID, item.StudyInstanceUid ?? string.Empty }, var dataset = new DicomDataset(ts)
{ DicomTag.StudyID, item.StudyId ?? string.Empty }, {
{ DicomTag.StudyDate, item.DicomStudyDate ?? string.Empty }, { DicomTag.PatientID, item.PatientId ?? string.Empty },
{ DicomTag.StudyTime, item.DicomStudyTime ?? string.Empty }, { DicomTag.PatientName, item.PatientName ?? string.Empty },
{ DicomTag.AccessionNumber, item.AccessionNumber ?? string.Empty }, { DicomTag.PatientBirthDate, item.PatientBirthDate ?? string.Empty },
{ DicomTag.StudyDescription, item.StudyDescription ?? string.Empty }, { DicomTag.PatientSex, item.PatientSex ?? string.Empty },
{ DicomTag.SeriesInstanceUID, item.SeriesInstanceUid ?? string.Empty }, { DicomTag.StudyInstanceUID, item.StudyInstanceUid ?? string.Empty },
{ DicomTag.Modality, item.Modality ?? string.Empty }, { DicomTag.StudyID, item.StudyId ?? string.Empty },
{ DicomTag.SeriesDate, item.DicomSeriesDate ?? string.Empty }, { DicomTag.StudyDate, item.DicomStudyDate ?? string.Empty },
{ DicomTag.SeriesTime, item.DicomSeriesTime ?? string.Empty }, { DicomTag.StudyTime, item.DicomStudyTime ?? string.Empty },
{ DicomTag.SeriesNumber, item.SeriesNumber.ToString() ?? string.Empty }, { DicomTag.AccessionNumber, item.AccessionNumber ?? string.Empty },
{ DicomTag.SeriesDescription, item.SeriesDescription ?? string.Empty }, { DicomTag.StudyDescription, item.StudyDescription ?? string.Empty },
{ DicomTag.SOPInstanceUID, item.SopInstanceUid ?? string.Empty }, { DicomTag.SeriesInstanceUID, item.SeriesInstanceUid ?? string.Empty },
{ DicomTag.SOPClassUID, item.SOPClassUID ?? string.Empty }, { DicomTag.Modality, item.Modality ?? string.Empty },
{ DicomTag.InstanceNumber, item.InstanceNumber.ToString() ?? string.Empty }, { DicomTag.SeriesDate, item.DicomSeriesDate ?? string.Empty },
{ DicomTag.MediaStorageSOPClassUID, item.MediaStorageSOPClassUID ?? string.Empty }, { DicomTag.SeriesTime, item.DicomSeriesTime ?? string.Empty },
{ DicomTag.MediaStorageSOPInstanceUID, item.MediaStorageSOPInstanceUID ?? string.Empty }, { DicomTag.SeriesNumber, item.SeriesNumber.ToString() ?? string.Empty },
{ DicomTag.TransferSyntaxUID, item.TransferSytaxUID ?? string.Empty }, { DicomTag.SeriesDescription, item.SeriesDescription ?? string.Empty },
};
var dicomFile = new DicomFile(dataset); { DicomTag.SOPInstanceUID, item.SopInstanceUid ?? string.Empty },
{ DicomTag.SOPClassUID, item.SOPClassUID ?? string.Empty },
{ DicomTag.InstanceNumber, item.InstanceNumber.ToString() ?? string.Empty },
{ DicomTag.MediaStorageSOPClassUID, item.MediaStorageSOPClassUID ?? string.Empty },
{ DicomTag.MediaStorageSOPInstanceUID, item.MediaStorageSOPInstanceUID ?? string.Empty },
{ DicomTag.TransferSyntaxUID, item.TransferSytaxUID ?? string.Empty },
};
// 文件名递增格式IM_00001, IM_00002, ... var dicomFile = new DicomFile(dataset);
string filename = $@"IMAGE/IM_{index:D5}"; // :D5 表示补足5位
mappings.Add($"{filename} => {item.InstanceId}"); // 文件名递增格式IM_00001, IM_00002, ...
string filename = $@"IMAGE/IM_{index:D5}"; // :D5 表示补足5位
mappings.Add($"{filename} => {item.InstanceId}");
dic.Add(item.InstanceId.ToString(), filename);
dicomDir.AddFile(dicomFile, filename);
}
dic.Add(item.InstanceId.ToString(), filename);
dicomDir.AddFile(dicomFile, filename);
} }
#region 写入临时路径 //有实际的文件
var tempFilePath = Path.GetTempFileName(); if (mappings.Count > 0)
// 保存 DICOMDIR 到临时文件 不能直接写入到流种
await dicomDir.SaveAsync(tempFilePath);
using (var memoryStream = new MemoryStream(File.ReadAllBytes(tempFilePath)))
{ {
// 重置流位置 #region 写入临时路径
memoryStream.Position = 0; var tempFilePath = Path.GetTempFileName();
await _oSSService.UploadToOSSAsync(memoryStream, ossFolder, "DICOMDIR", false); // 保存 DICOMDIR 到临时文件 不能直接写入到流种
await dicomDir.SaveAsync(tempFilePath);
using (var memoryStream = new MemoryStream(File.ReadAllBytes(tempFilePath)))
{
// 重置流位置
memoryStream.Position = 0;
await _oSSService.UploadToOSSAsync(memoryStream, ossFolder, "DICOMDIR", false);
}
//清理临时文件
File.Delete(tempFilePath);
#endregion
#region 映射上传
// 将映射写入内存流
var mappingText = string.Join(Environment.NewLine, mappings);
await using var mappingStream = new MemoryStream(Encoding.UTF8.GetBytes(mappingText));
await _oSSService.UploadToOSSAsync(mappingStream, ossFolder, $"Download_{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}", false);
#endregion
} }
//清理临时文件
File.Delete(tempFilePath);
#endregion
#region 映射上传
// 将映射写入内存流
var mappingText = string.Join(Environment.NewLine, mappings);
await using var mappingStream = new MemoryStream(Encoding.UTF8.GetBytes(mappingText));
await _oSSService.UploadToOSSAsync(mappingStream, ossFolder, $"Download_{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}", false);
#endregion
return dic; return dic;
} }
@ -142,6 +154,7 @@ namespace IRaCIS.Core.Application.Helper
{ {
var dataset = dicomFile.Dataset; var dataset = dicomFile.Dataset;
var info = new StudyDIRInfo var info = new StudyDIRInfo
{ {
PatientId = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty), PatientId = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
@ -166,8 +179,10 @@ namespace IRaCIS.Core.Application.Helper
SopInstanceUid = dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty), SopInstanceUid = dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty),
SOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty), SOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.SOPClassUID, string.Empty),
InstanceNumber = dataset.GetSingleValueOrDefault(DicomTag.InstanceNumber, 1), InstanceNumber = dataset.GetSingleValueOrDefault(DicomTag.InstanceNumber, 1),
MediaStorageSOPClassUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty),
MediaStorageSOPInstanceUID = dataset.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty), MediaStorageSOPClassUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.MediaStorageSOPClassUID, string.Empty),
MediaStorageSOPInstanceUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.MediaStorageSOPInstanceUID, string.Empty),
TransferSytaxUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty) TransferSytaxUID = dicomFile.FileMetaInfo.GetSingleValueOrDefault(DicomTag.TransferSyntaxUID, string.Empty)
}; };

View File

@ -1,5 +1,8 @@
using DocumentFormat.OpenXml.EMMA; using DocumentFormat.OpenXml.EMMA;
using FellowOakDicom; using FellowOakDicom;
using FellowOakDicom.Imaging;
using FellowOakDicom.Imaging.Render;
using FellowOakDicom.IO.Buffer;
using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.Helper;
using MassTransit; using MassTransit;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -244,17 +247,86 @@ namespace IRaCIS.Core.Application.Service
[FromServices] IRepository<DicomStudy> _studyRepository, [FromServices] IRepository<DicomStudy> _studyRepository,
[FromServices] IRepository<DicomSeries> _seriesRepository) [FromServices] IRepository<DicomSeries> _seriesRepository)
{ {
var list = await _instanceRepository.Where(t => t.TrialId == trialId) var list = await _instanceRepository.Where(t => t.TrialId == trialId && t.SubjectVisitId == Guid.Parse("01000000-0a00-0242-bd20-08dcce543ded"))
.Select(t => new { t.SeriesId, t.StudyId, t.Id, t.Path }).ToListAsync(); .Select(t => new { t.SeriesId, t.StudyId, t.Id, t.Path }).ToListAsync();
int totalCount = list.Count;
int dealCount = 0;
foreach (var item in list) foreach (var item in list)
{ {
dealCount++;
var stream = await _oSSService.GetStreamFromOSSAsync(item.Path); var stream = await _oSSService.GetStreamFromOSSAsync(item.Path);
var dicomFile = DicomFile.Open(stream); 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); var dirInfo = DicomDIRHelper.ReadDicomDIRInfo(dicomFile);
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == item.Id,
u => new DicomInstance()
{
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);
await _oSSService.UploadToOSSAsync(memoryStream, folder, fileName, false);
dealCount++;
Console.WriteLine($"{DateTime.Now}已下载 {dealCount} / {totalCount} 个文件,完成 {(dealCount * 100.0 / totalCount):F2}%");
} }
return ResponseOutput.Ok(); return ResponseOutput.Ok();

View File

@ -1,4 +1,5 @@
using IRaCIS.Core.Application.Service.ImageAndDoc.DTO; using FellowOakDicom;
using IRaCIS.Core.Application.Service.ImageAndDoc.DTO;
using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Domain.Share;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
@ -330,6 +331,7 @@ namespace IRaCIS.Core.Application.Contracts
public List<AddOrUpdateSeriesDto> SeriesList { get; set; } public List<AddOrUpdateSeriesDto> SeriesList { get; set; }
} }
public class AddOrUpdateSeriesDto public class AddOrUpdateSeriesDto
@ -402,7 +404,10 @@ namespace IRaCIS.Core.Application.Contracts
public string TransferSytaxUID { get; set; } public string TransferSytaxUID { get; set; }
public string mediaStorageSOPInstanceUID { get; set; } public string MediaStorageSOPInstanceUID { get; set; }
public bool IsEncapsulated => DicomTransferSyntax.Lookup(DicomUID.Parse(TransferSytaxUID)).IsEncapsulated;
} }
public class CRCUploadTaskQuery public class CRCUploadTaskQuery

View File

@ -32,7 +32,7 @@ public class DicomInstance : BaseFullDeleteAuditEntity, IEntitySeqId
public int ImageRows { get; set; } public int ImageRows { get; set; }
public string ImagerPixelSpacing { get; set; } = null!; public string ImagerPixelSpacing { get; set; } = null!;
public int InstanceNumber { get; set; } public int InstanceNumber { get; set; }
@ -44,7 +44,7 @@ public class DicomInstance : BaseFullDeleteAuditEntity, IEntitySeqId
[StringLength(1000)] [StringLength(1000)]
public string Path { get; set; } = null!; public string Path { get; set; } = null!;
public string PixelSpacing { get; set; } = null!; public string PixelSpacing { get; set; } = null!;
public Guid SeqId { get; set; } public Guid SeqId { get; set; }
@ -55,7 +55,7 @@ public class DicomInstance : BaseFullDeleteAuditEntity, IEntitySeqId
public int SliceLocation { get; set; } public int SliceLocation { get; set; }
public string SliceThickness { get; set; } = null!; public string SliceThickness { get; set; } = null!;
public string SopInstanceUid { get; set; } = null!; public string SopInstanceUid { get; set; } = null!;
@ -70,10 +70,10 @@ public class DicomInstance : BaseFullDeleteAuditEntity, IEntitySeqId
public Guid TrialId { get; set; } public Guid TrialId { get; set; }
public string WindowCenter { get; set; } = null!; public string WindowCenter { get; set; } = null!;
public string WindowWidth { get; set; } = null!; public string WindowWidth { get; set; } = null!;
public bool IsReading { get; set; } = true; public bool IsReading { get; set; } = true;
@ -90,5 +90,7 @@ public class DicomInstance : BaseFullDeleteAuditEntity, IEntitySeqId
public string MediaStorageSOPInstanceUID { get; set; } public string MediaStorageSOPInstanceUID { get; set; }
public bool IsEncapsulated { get; set; }
#endregion #endregion
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IRaCIS.Core.Infra.EFCore.Migrations
{
/// <inheritdoc />
public partial class addIsEncapsulated : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsEncapsulated",
table: "DicomInstance",
type: "bit",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsEncapsulated",
table: "DicomInstance");
}
}
}

View File

@ -930,6 +930,9 @@ namespace IRaCIS.Core.Infra.EFCore.Migrations
b.Property<bool>("IsDeleted") b.Property<bool>("IsDeleted")
.HasColumnType("bit"); .HasColumnType("bit");
b.Property<bool>("IsEncapsulated")
.HasColumnType("bit");
b.Property<bool>("IsReading") b.Property<bool>("IsReading")
.HasColumnType("bit"); .HasColumnType("bit");

View File

@ -58,7 +58,7 @@ namespace IRaCIS.Core.Infra.EFCore
Task<bool> BatchDeleteNoTrackingAsync(Expression<Func<TEntity, bool>> deleteFilter); Task<bool> BatchDeleteNoTrackingAsync(Expression<Func<TEntity, bool>> deleteFilter);
/// <summary>批量更新相当于原生sql 没用EF跟踪方式所有查询出来再更新 浪费性能)</summary> /// <summary>批量更新相当于原生sql 没用EF跟踪方式所有查询出来再更新 浪费性能)</summary>
Task<bool> BatchUpdateNoTrackingAsync(Expression<Func<TEntity, bool>> where, Expression<Func<TEntity, TEntity>> updateFactory); Task<bool> BatchUpdateNoTrackingAsync(Expression<Func<TEntity, bool>> where, Expression<Func<TEntity, TEntity>> updateFactory, bool isAutoIncludeTimeAndUser = true);
Task<bool> ExecuteUpdateAsync(Expression<Func<TEntity, bool>> where, Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> setPropertyCalls); Task<bool> ExecuteUpdateAsync(Expression<Func<TEntity, bool>> where, Expression<Func<SetPropertyCalls<TEntity>, SetPropertyCalls<TEntity>>> setPropertyCalls);
@ -77,7 +77,7 @@ namespace IRaCIS.Core.Infra.EFCore
#endregion #endregion
void MarkAsModified<TFrom>(TFrom entity, string propertyName); void MarkAsModified<TFrom>(TFrom entity, string propertyName);
} }

View File

@ -157,7 +157,7 @@ namespace IRaCIS.Core.Infra.EFCore
/// <summary>批量更新相当于原生sql 没用EF跟踪方式所有查询出来再更新 浪费性能)</summary> /// <summary>批量更新相当于原生sql 没用EF跟踪方式所有查询出来再更新 浪费性能)</summary>
public static async Task<bool> BatchUpdateNoTrackingAsync<T>(this IRaCISDBContext _dbContext, Expression<Func<T, bool>> where, Expression<Func<T, T>> updateFactory, Guid updateUserId) where T : Entity public static async Task<bool> BatchUpdateNoTrackingAsync<T>(this IRaCISDBContext _dbContext, Expression<Func<T, bool>> where, Expression<Func<T, T>> updateFactory, Guid updateUserId, bool isAutoIncludeTimeAndUser = true) where T : Entity
{ {
if (where == null) throw new ArgumentNullException(nameof(where)); if (where == null) throw new ArgumentNullException(nameof(where));
@ -202,15 +202,17 @@ namespace IRaCIS.Core.Infra.EFCore
if (typeof(IAuditUpdate).IsAssignableFrom(typeof(T))) if (typeof(IAuditUpdate).IsAssignableFrom(typeof(T)))
{ {
if (isAutoIncludeTimeAndUser)
if (!hasPropNameList.Contains(nameof(IAuditUpdate.UpdateTime)))
{ {
fieldValues.Add(nameof(IAuditUpdate.UpdateTime), DateTime.Now); if (!hasPropNameList.Contains(nameof(IAuditUpdate.UpdateTime)))
} {
fieldValues.Add(nameof(IAuditUpdate.UpdateTime), DateTime.Now);
}
if (!hasPropNameList.Contains(nameof(IAuditUpdate.UpdateUserId))) if (!hasPropNameList.Contains(nameof(IAuditUpdate.UpdateUserId)))
{ {
fieldValues.Add(nameof(IAuditUpdate.UpdateUserId), updateUserId); fieldValues.Add(nameof(IAuditUpdate.UpdateUserId), updateUserId);
}
} }
} }

View File

@ -297,10 +297,10 @@ namespace IRaCIS.Core.Infra.EFCore
/// <summary>批量更新相当于原生sql 没用EF跟踪方式所有查询出来再更新 浪费性能)</summary> /// <summary>批量更新相当于原生sql 没用EF跟踪方式所有查询出来再更新 浪费性能)</summary>
public async Task<bool> BatchUpdateNoTrackingAsync(Expression<Func<TEntity, bool>> where, public async Task<bool> BatchUpdateNoTrackingAsync(Expression<Func<TEntity, bool>> where,
Expression<Func<TEntity, TEntity>> updateFactory) Expression<Func<TEntity, TEntity>> updateFactory ,bool isAutoIncludeTimeAndUser = true)
{ {
return await _dbContext.BatchUpdateNoTrackingAsync(where, updateFactory, _userInfo.UserRoleId); return await _dbContext.BatchUpdateNoTrackingAsync(where, updateFactory, _userInfo.UserRoleId, isAutoIncludeTimeAndUser);
} }