C# 实现 DICOM C-Store SCP 服务从零到一
·
① 开发环境搭建与 DICOM 库安装
Dicom.Core 4.0.4.0
② CStoreSCP
public class CStoreSCP : DicomService, IDicomServiceProvider, IDicomCStoreProvider, IDicomCEchoProvider
{
public static string StoragePath = @"d:\DICOM";
public static string AECstoreTitle = "CS";
public static string ForwardOrgId = "1001";
public readonly static DicomTag HospitalCode = DicomTag.InstitutionName;
// 需要中文处理的标签
private static readonly DicomTag[] TextTags = new[]
{
DicomTag.PatientName, // 患者姓名
DicomTag.StudyDescription, // 检查描述
DicomTag.SeriesDescription, // 序列描述
DicomTag.InstitutionName // 机构名称
};
private static readonly DicomTransferSyntax[] AcceptedTransferSyntaxes = new DicomTransferSyntax[]
{
DicomTransferSyntax.ExplicitVRLittleEndian,
DicomTransferSyntax.ExplicitVRBigEndian,
DicomTransferSyntax.ImplicitVRLittleEndian
};
private static readonly DicomTransferSyntax[] AcceptedImageTransferSyntaxes = new DicomTransferSyntax[]
{
// Lossless
DicomTransferSyntax.JPEGLSLossless,
DicomTransferSyntax.JPEG2000Lossless,
DicomTransferSyntax.JPEGProcess14SV1,
DicomTransferSyntax.JPEGProcess14,
DicomTransferSyntax.RLELossless,
// Lossy
DicomTransferSyntax.JPEGLSNearLossless,
DicomTransferSyntax.JPEG2000Lossy,
DicomTransferSyntax.JPEGProcess1,
DicomTransferSyntax.JPEGProcess2_4,
// Uncompressed
DicomTransferSyntax.ExplicitVRLittleEndian,
DicomTransferSyntax.ExplicitVRBigEndian,
DicomTransferSyntax.ImplicitVRLittleEndian
};
public CStoreSCP(INetworkStream stream, Encoding fallbackEncoding, Logger log)
: base(stream, fallbackEncoding, log)
{
}
public Task OnReceiveAssociationRequestAsync(DicomAssociation association)
{
DicomLogger.Log("StoreSCP接收请求信息CalledAE:" + association.CalledAE + " CallingAE:" + association.CallingAE);
if (association.CalledAE != AECstoreTitle)
{
return SendAssociationRejectAsync(
DicomRejectResult.Permanent,
DicomRejectSource.ServiceUser,
DicomRejectReason.CalledAENotRecognized);
}
foreach (var pc in association.PresentationContexts)
{
if (pc.AbstractSyntax == DicomUID.Verification) pc.AcceptTransferSyntaxes(AcceptedTransferSyntaxes);
else if (pc.AbstractSyntax.StorageCategory != DicomStorageCategory.None) pc.AcceptTransferSyntaxes(AcceptedImageTransferSyntaxes);
}
return SendAssociationAcceptAsync(association);
}
public Task OnReceiveAssociationReleaseRequestAsync()
{
return SendAssociationReleaseResponseAsync();
}
public void OnReceiveAbort(DicomAbortSource source, DicomAbortReason reason)
{
}
public void OnConnectionClosed(Exception exception)
{
}
public DicomCStoreResponse OnCStoreRequest(DicomCStoreRequest request)
{
string tempFilePath = null;
try
{
// 记录基本信息
DicomLogger.Log("StoreSCP接收DICOM文件 - 患者ID: " + request.Dataset.GetSingleValueOrDefault(DicomTag.PatientID, "Unknown"));
var studyUid = request.Dataset.GetSingleValue<string>(DicomTag.StudyInstanceUID);
var seriesUid = request.Dataset.GetSingleValue<string>(DicomTag.SeriesInstanceUID);
var instUid = request.SOPInstanceUID.UID;
var patientId = request.Dataset.GetSingleValue<string>(DicomTag.PatientID);
var studyId = request.Dataset.GetSingleValue<string>(DicomTag.AccessionNumber);
var modality = request.Dataset.GetSingleValue<string>(DicomTag.Modality);
var path = Path.GetFullPath(StoragePath);
bool flag= localSQLite.InsertDataIfNotExists(studyUid, seriesUid, instUid, patientId, studyId, modality, path);
if (flag)
{
// 写入机构编码
DicomLogger.Log("DICOM写入机构编码: " + ForwardOrgId);
if (!string.IsNullOrEmpty(ForwardOrgId))
{
request.Dataset.AddOrUpdate(HospitalCode, ForwardOrgId);
}
else
{
DicomLogger.Log("ForwardOrgId 为空,无法写入");
}
// 确保目录存在
path = Path.Combine(path, studyUid);
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
path = Path.Combine(path, instUid) + ".dcm";
// 保存文件(确保 Dataset 被正确写入)
var dicomFile = new DicomFile(request.Dataset);
dicomFile.Save(path);
// 验证是否写入成功
var savedFile = DicomFile.Open(path);
var savedOrgId = savedFile.Dataset.GetString(HospitalCode);
DicomLogger.Log($"实际保存的机构编码: {savedOrgId}");
Thread.Sleep(100);
// 只有成功写入才转发
if (!string.IsNullOrEmpty(savedOrgId))
{
// 使用异步转发,不阻塞主流程
Task.Run(() =>
{
var result = CStoreHelp.CStore(path);
if (!result.Success)
{
DicomLogger.Log($"转发DICOM文件失败: {result.ErrorMessage}");
}
});
}
else
{
DicomLogger.Log("警告:机构编码未正确写入 DICOM 文件");
}
}
else
{
DicomLogger.Log("StoreSCP的dicom已存储过");
}
}
catch (Exception ex)
{
DicomLogger.Log("StoreSCP"+ex+ "保存DICOM文件失败 - 实例: {InstanceUid}"+ request.SOPInstanceUID.UID);
throw;
}
return new DicomCStoreResponse(request, DicomStatus.Success);
}
public void OnCStoreRequestException(string tempFileName, Exception e)
{
// let library handle logging and error response
}
public DicomCEchoResponse OnCEchoRequest(DicomCEchoRequest request)
{
return new DicomCEchoResponse(request, DicomStatus.Success);
}
private (bool IsValid, string ErrorMessage) ValidateKeyDicomTags(DicomDataset dataset)
{
try
{
// 定义必需的标签
var requiredTags = new[]
{
(DicomTag.PatientID, "Patient ID"),
(DicomTag.StudyInstanceUID, "Study Instance UID"),
(DicomTag.SeriesInstanceUID, "Series Instance UID"),
(DicomTag.SOPInstanceUID, "SOP Instance UID")
};
// 检查必需标签
var missingTags = requiredTags
.Where(t => !dataset.Contains(t.Item1))
.Select(t => t.Item2)
.ToList();
// 检查像素数据
if (IsImageStorage(dataset.GetSingleValue<DicomUID>(DicomTag.SOPClassUID)))
{
if (!dataset.Contains(DicomTag.PixelData) ||
dataset.GetDicomItem<DicomItem>(DicomTag.PixelData) == null)
{
missingTags.Add("Pixel Data (empty or missing)");
}
}
if (missingTags.Any())
{
var errorMessage = $"DICOM数据验证失败: {string.Join(", ", missingTags)}";
DicomLogger.Log("StoreSCP{ErrorMessage}"+errorMessage);
return (false, errorMessage);
}
return (true, string.Empty);
}
catch (Exception ex)
{
var errorMessage = "DICOM数据验证过程发生异常";
DicomLogger.Log("StoreSCP"+ex+"{ErrorMessage}"+errorMessage);
return (false, errorMessage);
}
}
private bool IsImageStorage(DicomUID sopClass)
{
// 检查是否是图像存储类别
if (sopClass.StorageCategory == DicomStorageCategory.Image)
return true;
// 检查特定的图像存储SOP类
return sopClass.Equals(DicomUID.SecondaryCaptureImageStorage) || // 二次获取图像
sopClass.Equals(DicomUID.CTImageStorage) || // CT图像
sopClass.Equals(DicomUID.MRImageStorage) || // MR图像
sopClass.Equals(DicomUID.UltrasoundImageStorage) || // 超声图像
sopClass.Equals(DicomUID.UltrasoundMultiFrameImageStorage) || // 超声多帧图像
sopClass.Equals(DicomUID.XRayAngiographicImageStorage) || // 血管造影图
sopClass.Equals(DicomUID.XRayRadiofluoroscopicImageStorage) || // X射线透视图像
sopClass.Equals(DicomUID.DigitalXRayImageStorageForPresentation) || // DR图像
sopClass.Equals(DicomUID.DigitalMammographyXRayImageStorageForPresentation) || // 乳腺X射线图像
sopClass.Equals(DicomUID.EnhancedCTImageStorage) || // 增强CT图像
sopClass.Equals(DicomUID.EnhancedMRImageStorage) || // 增强MR图像
sopClass.Equals(DicomUID.EnhancedXAImageStorage); // 增强血管造影图像
}
// 添加时间格式化方法
private string StandardizeDicomDate(string dateValue)
{
if (string.IsNullOrEmpty(dateValue))
{
DicomLogger.Log("StoreSCP日期为空,使用当前日期");
return DateTime.Now.ToString("yyyyMMdd");
}
DicomLogger.Log("StoreSCP处理日期 - 原始值:"+ dateValue);
// 移除所有非数字字符
var cleanDate = new string(dateValue.Where(char.IsDigit).ToArray());
DicomLogger.Log("StoreSCP清理后的日期:"+ cleanDate);
// 如果清理后的日期不是8位,使用当前日期
if (cleanDate.Length != 8)
{
DicomLogger.Log("StoreSCP非标准日期格式:"+dateValue+ "->" + cleanDate + "使用当前日期");
return DateTime.Now.ToString("yyyyMMdd");
}
try
{
// 验证日期是否有效
var year = int.Parse(cleanDate.Substring(0, 4));
var month = int.Parse(cleanDate.Substring(4, 2));
var day = int.Parse(cleanDate.Substring(6, 2));
var date = new DateTime(year, month, day);
var result = date.ToString("yyyyMMdd");
DicomLogger.Log("StoreSCP日期格式化完成:"+ dateValue + " ->"+result);
return result;
}
catch (Exception ex)
{
DicomLogger.Log("StoreSCP"+ex+"日期格式无效:"+ dateValue + "使用当前日期");
return DateTime.Now.ToString("yyyyMMdd");
}
}
}
③ C-Store SCP 监听服务启动
var appSettings = ConfigurationManager.AppSettings;
//接收服务配置
ReceiveAe.Text = string.IsNullOrEmpty(appSettings[“ReceiveAe”]) ? “CS” : appSettings[“ReceiveAe”];
ReceivePort.Text = string.IsNullOrEmpty(appSettings[“ReceivePort”]) ? “1004” : appSettings[“ReceivePort”];
StoragePath.Text = string.IsNullOrEmpty(appSettings[“StoragePath”]) ? @“C:\DICOM” : appSettings[“StoragePath”];
string ForwardOrgId = appSettings[“ForwardOrgId”] ?? null;
//启动接收服务
StartCStoreService(ReceiveAe.Text, StoragePath.Text, int.Parse(ReceivePort.Text), ForwardOrgId);
④ AppSettings 核心配置参考
<appSettings>
<!-- 接收服务配置 -->
<add key="ReceivePort" value="1004" />
<add key="ReceiveAe" value="CS" />
<add key="StoragePath" value="d://DICOM/S" />
</appSettings>
更多推荐
所有评论(0)