DICOM:参考dcm4che2扩展fo-dicom(mDCM)中的UserIdentity字段

背景:

5月份的前半段好懒惰,手里积攒了好多篇文章,也有之前答应过博友要写的,迟迟未动笔。究其根源,有些许懒惰,但更多的是迷惑和一知半解,虽想写但却不知如何入手,零星的感悟要积累成文还是需要时间去沉淀的,以期尽量做到每篇博文有理有据。

今天正好借着手头新任务的机会,介绍一下DICOM3.0标准中的又一新内容。继之前两篇博文基于JMeter+dcm4che2测试PACS服务器性能的解决方案(前/续篇)中提到的DICOM标准第15部分中的TransferCapability概念,本篇介绍标准第8部分附录D中A-ASSOCIATE-RQ中的UserInformation,以及其扩展UserIdentity(DICOM3.0标准第7部分附录D)

此外,在文章后半段,会通过对比dcm4che2与fo-dicom(mDCM)的实现,同时借助于RawCap+WireShark对实际数据包进行分析,给出扩展UserInformation的实现。

UserInformation:

关于UserInformation的详细介绍在标准第8部分附录D,其属于A-ASSOCIATE-RQ PDU结构子项。A-ASSOCIATE-RQ PDU的Application Context以及Presentation Context两个子项之前介绍比较多,且与DICOM连接Association建立密切相关。对于User Information子项介绍的不多,该子项以0x50H为标志,内部可进一步扩展子项,范围从0x51H-0xFFH。在第8部分的附录D中只是简略的介绍了Maximum length子项,起始标志为0x51H。该项用于约束连接双方传递数据包P-DATA-TF的最大长度。

关于UserInformation项,标准中有两条注意事项:

1. The values of the Sub-Items types in the User Information Field are assigned by this standard in the range of 51H through FFH. Sub-Item values are defined by PS 3.7 and PS 3.8.

2. Succeeding editions of the Standard may define additional user information Sub-Items in a manner that does not affect the semantics of previously defined Sub-Items. Association acceptors compliant to

an earlier edition of the Standard are required to ignore such unrecognized user information Sub-Items and not reject an Association because of their presence.

由上述注意事项可知,之前鲜有涉猎UserInformation字段而并未影响我们完成DIMSE中的各种服务是因为“标准中提到后续更新的版本中会加入对自定义扩展的说明,对于符合早期DICOM标准的终端(即A-ASSOCIATE-RQ接收方)需要自动忽略无法识别的UserInformation子项,而不应该因为存在无法识别的子项而拒绝连接请求。

UserIdentity:

DICOM标准第8部分在A-ASSOCIATE-RQ PDU结构体中介绍了User Information项目,作为A-ASSOCIATE-RQ PDU的一部分,对于用户自定义扩展并未做深入讨论。既然标准提到了扩展子项可以从0x51H-0xFFH,是不是我们可以任意扩展呢?原则上是可以任意扩展的,只要确保Associate连接双方约定好解析方式即可。

其实在DICOM3.0标准第7部分的附录中对于UserInformation中常见的扩展有更详细的介绍。在附录中D.3中详解介绍了几种常见的扩展,诸如以0x51H开头的Max PDU Length,以0x52H开头的Implentation Identification Notification,以0x53H开头的Asynchronous Operations Windows Negotiation,以0x54H开头的SCP/SCU Role Selection Negotiation,以0x56H开头的Service-Object Pair Class Extended Negotiation,以0x57H开头的Service-Object Pair Class Common Extended Negotiation,以0x58H开头的User identity Negotiation等等,

【注1】:在与0x52H开始的Implentation Identification Notification包含了两个子项,其中0x52H对应于Implentation Class UID,0x55H对应于Implentation Version Name

【注2】:在关于UserIdentity扩展字段中,其实包括以0x58H开始的UserIdentityRQ,和以0x59H开始的UserIdentityAC

近期在项目中要同时使用到fo-dicom(前身是mDCM)和dcm4che2工具包,且需要完成两者之间数据互传,按照之前对DICOM开源库的了解,以为只要配置完成AE Title、IP和Port后即可顺利实现,但是在尝试了几天未果。通过仔细分析dcm4che2附带工具包dcmsnd.bat以及dcmqr.bat,又让我重新阅读了DICOM协议的部分章节,因此也就有了此篇博文。

问题描述:使用fo-dicom开源库实现的客户端,诸如CStoreClient,向基于dcm4che2实现的dcm4chee服务器发送DCM数据,在配置完成AETitile、IP以及Port后,体会无法建立连接。

扩展fo-dicom(mDCM):

RawCap+WireShark抓包分析:

如上图所示是使用RawCap+WireShark抓取的dcmsnd.bat工具包向基于fo-dicom搭建的PACS服务器发送数据数据包。之所以如此操作,谁因为使用fo-dicom构建的CStoreClient无法顺利向基于dcm4che2的dcm4chee服务器发送数据。我们反向操作发现,竟然可以顺利成功。这说明dcm4che实现的dcmsnd.bat工具包发送的数据量可能比我们使用fo-dicom的CStoreClient要大,可能包含了额外的数据。

通过分析上图可以发现,多出来的部分就是以0x58H开头的UserIdentity自扩展子项,从我的专栏文章也可以知道之前在介绍dcmtk、fo-dicom、mDCM时并未涉及到该自定义字段。而dcm4che2工具包内部却对其进行了实现,并且在基于dcm4che2搭建的dcm4chee服务端中还通过该扩展字段进行了连接屏蔽(貌似这不符合我们之前看到的标准备注中提到的“服务端要忽略无法识别字段,而不应该拒绝连接”的说明。想想dcm4chee之所以如是操作,大抵也是为了增加安全性。

dcm4che2工具分析:

既然已经找到了问题原因,接下来让我们分析一下dcm4che2,找出解决方案。dcm4che2工具包中对于PDU数据结构的定义在PDUEncoder.java文件中。此外在ItemType.java类中预定了各种扩展字段类型枚举变量,具体代码如下:

class ItemType {

public static final int APP_CONTEXT       = 0x10;
public static final int RQ_PRES_CONTEXT   = 0x20;
public static final int AC_PRES_CONTEXT   = 0x21;
public static final int ABSTRACT_SYNTAX   = 0x30;
public static final int TRANSFER_SYNTAX   = 0x40;
public static final int USER_INFO         = 0x50;
public static final int MAX_PDU_LENGTH    = 0x51;
public static final int IMPL_CLASS_UID    = 0x52;
public static final int ASYNC_OPS_WINDOW  = 0x53;
public static final int ROLE_SELECTION    = 0x54;
public static final int IMPL_VERSION_NAME = 0x55;
public static final int EXT_NEG           = 0x56;
public static final int COMMON_EXT_NEG    = 0x57;
public static final int RQ_USER_IDENTITY  = 0x58;
public static final int AC_USER_IDENTITY  = 0x59;

}

标准中规定的0x58H扩展项的结构如下:

通过分析PDUEncoder.java文件,可以看出dcm4che2是严格按照DICOM标准中对UserIdentity子项的约束来构建的,具体构建过程如下:

至此,我们找到了dcm4che2工具中如何向A-ASSOCIATE-RQ PDU中写入UserIdentity扩展项了。接下来就是解决问题的时刻了,让我们看看fo-dicom实现中是否留有接口,让我们来写入UserIdentity这一扩展字段。

实施fo-dicom(mDCM)扩展:

通过分析发现fo-dicom以及其早期版本mDCM中并未留有接口,让用户扩展UserIdentity字段。由于手中搭建的建议客户端以及PACS使用的是早期版本的mDCM,因此这里就直接给出如何修改mDCM代码,fo-dicom中的修改大致相同,这里就不详细介绍了。

主要的修改有以下几处:

第一:在Dicom.Network文件夹下添加自定义的UserIdentity类,可仿照dcm4che2中的实现来进行,此处为了方便,我只向A-ASSOCIATE-RQ PDU中写入了Useridentity,而并未向A-ASSOCIATE-AC PDU中扩展。因此我定义的UserIdentity类结构如下:

public class UserIdentity
{
    //密码类型
    public static int USERNAME = 1;

    public static int USERNAME_PASSCODE = 2;

    public static int KERBEROS = 3;

    public static int SAML = 4;

    private int userIdentityType;
    private bool positiveResponseRequested;
    private string username = null;
    private string passcode = null;

    #region Properties
    public string UserName
    {
        get { return username; }
        set { username = value; }
    }
    public string PassCode
    {
        get { return passcode; }
        set { passcode = value; }
    }
    public int UserIdentityType
    {
        get { return userIdentityType; }
        set { userIdentityType = value; }
    }
    public bool bPositiveResponseRequested
    {
        get { return positiveResponseRequested; }
        set { positiveResponseRequested = value; }
    }
    #endregion

    #region Constructors
    public UserIdentity()
    {
        this.username = "zssure";
        this.passcode = "zssure";
        this.positiveResponseRequested = false;
        this.userIdentityType = UserIdentity.USERNAME_PASSCODE;

    }
    public UserIdentity(string user, string passwd)
    {
        this.username = user;
        this.passcode = passwd;
        this.positiveResponseRequested = false;
        this.userIdentityType = UserIdentity.USERNAME_PASSCODE;
    }
    #endregion

}`<br>

第二:扩展PDU.cs类,添加WriteAddingUserIdentity函数,便于向socket流数据体中添加UserIdentity子项目,主要代码如下:

 //User Indentity
            //http://medical.nema.org/medical/dicom/current/output/html/part07.html#sect_D.3.3.7
            pdu.Write("Item-Type", (byte)0x58);
            pdu.Write("Reserved", (byte)0x00);
            pdu.MarkLength16("Item-Length");
            pdu.Write("User Identity Type", (byte)userIdentity.UserIdentityType);
            pdu.Write("Positive Response Requested", (userIdentity.bPositiveResponseRequested?(byte)0x01:(byte)0x00));
            pdu.Write("Primary Field Length", (ushort)userIdentity.UserName.Length);
            pdu.Write("Primary Field", userIdentity.UserName);
            pdu.Write("Secondary Field Length", (ushort)userIdentity.PassCode.Length);
            pdu.Write("Secondary Field", userIdentity.PassCode);
            pdu.WriteLength16();
            //zssure:end.

第三:扩展DicomNetworkBase.cs基类,添加WriteAddingUserIdentity函数,便于向socket流数据体中添加UserIdentity子项目。

        /// <summary>
        ///Add UserIdentity Information
        /// http://medical.nema.org/medical/dicom/current/output/html/part07.html#sect_D.3.3.7
        /// </summary>
        /// <param name="associate"></param>
        /// <param name="userIdentity"></param>
        protected void SendAssociateRequest(DcmAssociate associate, UserIdentity userIdentity)
        {
            _assoc = associate;
            if (UseRemoteAeForLogName)
            {
                LogID = Associate.CalledAE;
                Log = LogManager.GetLogger(LogID);
            }
            Log.Info("{0} -> Association request:\n{1}", LogID, Associate.ToString());
            AAssociateRQ pdu = new AAssociateRQ(_assoc);
            SendRawPDU(pdu.WriteAddingUserIdentity(userIdentity));
        }

第四:修改CStoreClient的OnConnected函数,添加写入UserIdentity子项目。另外为了兼容之前未写入UserIdentity字段的情况,此处做了一下分类讨论。

//zssure:2015/05/26
//Adding UserIdentity Information
                //http://medical.nema.org/medical/dicom/current/output/html/part07.html#sect_D.3.3.7
                if (userIdentity != null)
                {
                    SendAssociateRequest(associate, userIdentity);
                }
                else
                    SendAssociateRequest(associate);
                //zssure:end

测试:

重新编译mDCM,将新版Dicom.dll库添加到工程引用,重新使用CStoreClient向dcm4chee服务器发送数据,顺利完成数据上传任务。

【注】:在实验前要在dcm4chee服务端提前注册一个用户名和密码,比如我这里使用的用户名是zssure,密码是2015。

源码下载:

目前mDCM版本已经少有人维护,所以我就讲本地扩展了UserIdentity的版本上传到了Github上,博文中提到的几个主要修改,诸如UserIdentity.cs、DicomNetworkBase.cs、CStoreClient.cs以及PDU.cs都可以从mDCM仓库中找到。

mDCM on zssure GitHub

作者:[email protected]

时间:2015-05-27

时间: 2024-10-10 14:00:21

DICOM:参考dcm4che2扩展fo-dicom(mDCM)中的UserIdentity字段的相关文章

参考dcm4che2扩展fo-dicom(mDCM)中的UserIdentity字段

5月份的前半段好懒惰,手里积攒了好多篇文章,也有之前答应过博友要写的,迟迟未动笔.究其根源,有些许懒惰,但更多的是迷惑和一知半解,虽想写但却不知如何入手,零星的感悟要积累成文还是需要时间去沉淀的,以期尽量做到每篇博文有理有据. 今天正好借着手头新任务介绍DICOM标准中的又一新内容,参照dcm4che2工具扩展fo-dicom(mDCM)向A-ASSOCIATE-RQ PDU中添加UserIdentity....yuedu.baidu.com/album/view/a9e914106c175f0

DICOM医学图像处理:开源库mDCM与DCMTK的比较分析(一),JPEG无损压缩DCM图像(续)

背景: 上周通过单步调试,找出了开源库mDCM与DCMTK在对DICOM图像进行JPEG无损压缩时的细小区别,并顺利实现了在C++和C#环境下对DICOM图像的压缩.但是问题接踵而至啊,随着项目的深入,发现在单独的测试工程中可以实现的mDCM版本,在嵌入到项目整体中后,却意外地出现了错误,并未顺利实现DICOM图像的JPEG无损压缩.因此需要继续详细对比分析mDCM与DCMTK两者,期望寻找原因. 问题分析: 开启项目的日志功能后,得到的信息反馈为: No registered codec fo

DICOM医学图像处理:DCMTK在VS2012中的配置

背景: 最近由于项目需要,将原本的开发IDE环境由VS2008升级到了VS2012.本以为编译完成后的DCMTK开源库可以直接从VS2008移植到VS2012.但是通过项目属性添加完包含目录和依赖库后,编译会出现大量的链接错误(大多是跟dcmdata.lib.oflog.lib有关). 解决方法: 重新按照原本的博客前辈柳北风儿(大神目前已经博客转移到网易:http://blog.163.com/[email protected]/),利用CMake工具,选择VS2012本地编译器对DCMTK3

浏览器扩展系列————在WPF中定制WebBrowser快捷菜单

原文:浏览器扩展系列----在WPF中定制WebBrowser快捷菜单 关于如何定制菜单可以参考codeproject上的这篇文章:http://www.codeproject.com/KB/books/0764549146_8.aspx?fid=13574&df=90&mpp=25&noise=3&sort=Position&view=Quick&fr=26#xx0xx 本文主要讲述如何在这篇文章中的ShowContextMenu方法中弹出自己的Conte

涛哥的Python脚本工具箱之批量替换目录所有指定扩展名的文件中的指定字符串

今天发布刚完成的涛哥的Python脚本工具箱之批量替换目录所有指定扩展名的文件中的指定字符串,命令行参数处理改用目前比较好用的argparse库,Python代码如下: #!/usr/bin/python2.7 # -*- encoding: UTF-8 -*- # Copyright 2014 [email protected] """replace old string with new string from all files in path 批量替换目录所有指定扩展

IE 扩展调用主窗口中的函数

在函数名前加上 parentWindow 即可.如: <script> var doc = external.menuArguments.document doc.parentWindow.函数名(); </script>IE 扩展调用主窗口中的函数

DNS 中的协议字段详细定义

DNS中的协议字段定义 Table of Contents 1 概述 2 DNS Classes 3 DNS OpCodes 4 DNS RCODEs 5 DNS Label Types 6 DNS资源记录 7 EDNS Version 8 DNS EDNS0 Option Codes (OPT) 1 概述 总结DNS协议中各字段的取值 2 DNS Classes Decimal Name Reference 0 Reserved RFC6895 1 Internet(IN) RFC1035 2

【译】 AWK教程指南 3计算并打印文件中指定的字段数据

awk 处理数据时,它会自动从数据文件中一次读取一条记录,并会将该记录切分成一个个的字段:程序中可使用 $1, $2,... 直接取得各个字段的内容.这个特色让使用者易于用 awk 编写 reformatter 来改变数据格式. 范例:以数据文件 emp.dat 为例,计算每人应发工资并打印报表. 分析:awk 会自行一次读入一条记录,故程序中仅需告诉 awk 如何处理所读入的数据行. 执行如下命令:($ 表UNIX命令行上的提示符)  $ awk '{ print $2, $3 * $4 }'

数据库设计中主键字段类型的选择

很久都没有写过博客了,从最后一次发表的文章到现在已经是两个多月的时间了,一直都想写点什么,可一直没有时间(其实都是借口),随笔内容无疑就是工作学习中的总结,经验的分享,也是自己成长的一面镜子,好了,言规正传,这次谈谈在数据库设计中主键字段类型的选择. 做web 开发时,经常要与数据库交互,数据库主键的选择也犹为重要,怎么么选择数据库主键字段的类型,主要从以下几个方面考虑: 1. 首先要符合业务需求,这是设计中重要的出发点 2. 数据库的迁移问题,考虑在后期是否要经常迁移,数据库高度唯一性 3.程