① 开发环境搭建与 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>

更多推荐