DICOM通讯(ACSE->DIMSE->Worklist)

本文详细介绍了DICOM通讯中的Worklist交互过程,包括A-ASSOCIATE-req和A-ASSOCIATE-ac的建立连接,以及C-FIND-RQ和C-FIND-RSP的C-FIND服务请求与响应。通过Wireshark抓包并设置过滤条件,展示了如何解析PDU和PDVs。关键词涉及:DICOM, TCP/IP, A-ASSOCIATE, C-FIND, PDV, 数据流, wireshark

下文中的worklist交互的测试数据,请在资源中下载,需要wireshark将文件打开,并且加入过滤条件ip.addr == 192.168.2.193 and tcp.port == 104 ,并且选择dicom协议。

1 DICOM通讯概要介绍

       DICOM通讯和TCP IP的设计原理别无二致。从用户数据发送到网络上的数据的过程中,要经历多层协议处理,每经一层,就会加入用来描述当前层含义的数据字段,例如,我们熟悉的TCP层,会在用户的数据流前,加入目标端口,源端口,TCP层用来模拟链接通道的Sequence Number和Acknowlegment Number等的字段。

TCP IP协议栈示意图

        在DICOM通讯中,当数据通过TCP层后,数据流就进入了DICOM的会话层(ACSE),详细介绍可在标准第8章中。ACSE(Association Control Service Element),包含了7种类型的协议数据单元,分别是A-ASSOCIATE-RQ PDU(Protocol Data Units),A-ASSOCIATE-AC PDU,A-ASSOCIATE-RJ PDU,P-DATA-TF PDU,A-RELEASE-RQ PDU,A-RELEASE-RP PDU,A-ABORT PDU。这些服务数据单元构成了ACSE服务组。解析数据流时,当第一个字节的值是4的时候,就代表PDU是一个P-DATA类型,应当由DIMSE层来处理;相反,如果是其他值,就属于a-associate-rq, a-associate-rj, a-associate-ac, a-release-rq, a-release-rsp, a-abort6种类型PDU中的一种,直接在ACSE层内进行处理。具体的解析过程,第二章节中,以一个Modality Worklist(C-Find)的例子来介绍。

ACSE层数据流示意图

          当会话层接收到PDU,并且PDU的Type是4的时候,ACSE层协议,擦掉PDU头信息,将信息流变为PDVs(Presentation Data Values )后,将数据流交给表示层来解析。详细介绍,可参考第七章。在表示层DIMSE层中,提供了C-Find, C-Store, C-Move, C-Get, C-Echo, N-EVENT-R

<think>我们正在处理DICOM文件,其中`dataset->putAndInsertString`用于写入DICOM标签的值。第二个参数需要是一个`const char*`类型,而用户有一个`item.patientInfo.name`,这个`name`可能是`QString`类型(因为之前的问题涉及Qt)或者标准字符串类型。但是,用户的问题描述中提到“转换为符合要求的string类型”,所以我们需要明确如何将可能的不同字符串类型转换为DICOMTK所需的`const char*`。 根据DCMTK文档,`putAndInsertString`函数的第二个参数是`const char*`类型。因此,我们需要确保传入的字符串是C风格字符串(以空字符结尾的字符数组)。 常见情况: 1. 如果`item.patientInfo.name`是`std::string`,则可以直接使用`.c_str()`方法。 2. 如果`item.patientInfo.name`是`QString`,则需要转换为`const char*`。但注意:DICOM标准规定了特定字符集,直接转换可能遇到编码问题。 更重要的考虑:DICOM标准要求使用特定的字符集(如ISO_IR 100表示Latin-1,或者UTF-8等)。如果字符串包含非ASCII字符(如中文),则必须正确设置字符集标签(0008,0005)并确保字符串以正确的编码存储。 因此,修改方法分为两步: 步骤1:确保整个DICOM文件的字符集设置正确(通常为ISO_IR 192表示UTF-8,或GB18030等)。 步骤2:将字符串转换为对应字符集的字节数组,然后以`const char*`形式传入。 假设我们使用UTF-8字符集(推荐),则操作如下: 第一步:在DICOM数据集中设置字符集标签(0008,0005)为"ISO_IR 192"(即UTF-8)。注意,这个标签应该在整个数据集的最开始设置,因为后续字符串的解析依赖于它。 第二步:将字符串(无论原始编码是什么)转换为UTF-8编码的字节数组,然后传递给`putAndInsertString`。 具体实现: 情况A:如果`item.patientInfo.name`是`QString`(来自Qt,内部是UTF-16): 需要将QString转换为UTF-8编码的std::string或QByteArray,然后获取其`const char*`。 情况B:如果`item.patientInfo.name`是`std::string`,但我们不知道其编码(可能当前系统本地编码): 最好先将其转换为QString(假设知道本地编码),然后再转换为UTF-8;或者直接使用DCMTK的转换工具(但较复杂)。这里假设我们的源字符串是UTF-8,则可以直接使用。 为了简化,我们假设整个程序内部使用UTF-8编码,这样就不需要转换。 但是,如果源字符串不是UTF-8,则需要转换。这里给出通用的方法,确保字符串以UTF-8编码传入: 使用Qt进行转换(如果原始字符串是QString): ```cpp // 设置字符集(只需设置一次,通常在创建数据集时) dataset->putAndInsertString(DCM_SpecificCharacterSet, "ISO_IR 192"); // 设置字符集为UTF-8 // 如果name是QString QString name = item.patientInfo.name; QByteArray utf8Data = name.toUtf8(); // 转换为UTF-8字节数组 dataset->putAndInsertString(DCM_PatientName, utf8Data.constData()); ``` 如果原始字符串是`std::wstring`,或者系统本地编码的`std::string`,我们同样需要转换为UTF-8。可以使用Qt或标准库(C++11以上)进行转换。 注意:DCMTK从3.6.0版本开始支持UTF-8,所以请确保使用较新版本的DCMTK。 如果不想使用Qt进行转换,也可以使用标准库(但标准库的UTF-8转换在Windows上可能较麻烦): ```cpp // 使用标准库将宽字符串(wstring)转换为UTF-8 #include <codecvt> #include <locale> #include <string> std::wstring wideName = ...; // 宽字符串 std::wstring_convert<std::codecvt_utf8<wchar_t>> converter; std::string utf8Name = converter.to_bytes(wideName); dataset->putAndInsertString(DCM_PatientName, utf8Name.c_str()); ``` 但是,如果源字符串已经是UTF-8编码的`std::string`,则可以直接使用: ```cpp dataset->putAndInsertString(DCM_PatientName, item.patientInfo.name.c_str()); ``` 总结步骤: 1. 设置字符集标签为"ISO_IR 192"(UTF-8),确保数据集支持UTF-8。 2. 将任何非UTF-8编码的字符串转换为UTF-8编码的字节数组(例如通过`toUtf8()`或标准库转换)。 3. 传入转换后的字符串的`const char*`。 注意:DICOM标准要求某些标签的字符长度限制,比如患者姓名(PatientName)最多64个字符(但这是字符数还是字节数?DICOM标准中是64字节,所以UTF-8编码下,中文字符可能占用3个字节,因此实际字符数会少于64)。所以需要确保转换后的UTF-8字节数不超过标签定义的最大长度。 因此,修改代码如下: ```cpp // 设置字符集 dataset->putAndInsertString(DCM_SpecificCharacterSet, "ISO_IR 192"); // 假设item.patientInfo.name是QString QByteArray utf8Name = item.patientInfo.name.toUtf8(); // 检查长度是否超过64字节(DCM_PatientName的最大长度) if (utf8Name.size() > 64) { // 截断或处理错误,但注意截断可能会导致信息丢失或乱码 utf8Name = utf8Name.left(64); } dataset->putAndInsertString(DCM_PatientName, utf8Name.constData()); ``` 如果原始字符串是其他类型,请根据上述方法转换。 最后,注意:在同一个数据集中,字符集设置只需要一次,通常在创建数据集后立即设置。 引用说明:根据DICOM标准,字符集标签(0008,0005)必须在其他包含字符串的标签之前设置[^1]。 相关问题: 1. DICOM字符集设置有哪些注意事项? 2. 如何确保DICOM标签值的长度符合标准要求? 3. 在DCMTK中,除了`putAndInsertString`,还有哪些写入字符串的方法? </think>### DICOM 中字符串参数的修改方法 在 DCMTK 库中,`dataset->putAndInsertString()` 方法的第二个参数需要符合 **DICOM 字符串编码规范**(VR=LO/PN/CS 等类型),同时满足以下要求: 1. 必须是 `const char*` 类型(C 风格字符串) 2. 字符长度不超过 DICOM 标签规定的最大长度(如 DCM_PatientName 最大 64 字符) 3. 使用正确的字符集编码(通常要求 ASCII 或 DICOM 指定字符集) #### 修改步骤(基于 Qt 环境) ```cpp #include <dcmtk/dcmdata/dctk.h> // 1. 从Item获取患者姓名(假设item.patientInfo.name是QString) QString patientNameQt = item.patientInfo.name; // 2. 转换为符合DICOM规范的字符串 // --- 方法1:直接转换(QString → QByteArray → const char*) --- const char* dicomName = patientNameQt.toLatin1().constData(); // ASCII字符集 // --- 方法2:带长度检查和截断(推荐)--- QByteArray nameBuffer = patientNameQt.left(64).toLatin1(); // 限制64字符 OFString dicomString(nameBuffer.constData(), nameBuffer.size()); // 3. 写入DICOM数据集 dataset->putAndInsertString(DCM_PatientName, dicomString.c_str()); ``` #### 关键注意事项 1. **字符集处理**: - 使用 `toLatin1()` 确保生成 ASCII 兼容字符串(大多数 DICOM 系统要求) - 如需 Unicode 支持,需设置字符集标签: ```cpp dataset->putAndInsertString(DCM_SpecificCharacterSet, "ISO_IR 192"); // UTF-8 ``` 2. **长度限制**: ```cpp // DICOM标签最大长度常量(DCMTK定义) const Uint32 maxLen = DcmPatientName::getMaxLength(); // 返回64 ``` 3. **特殊字符处理**: ```cpp // 过滤非法字符(如反斜杠、控制字符) patientNameQt.replace(QRegularExpression("[\\\\\x00-\x1F]"), ""); ``` #### 完整示例代码 ```cpp void updatePatientName(DcmDataset* dataset, const PatientItem& item) { // 获取并清理患者姓名 QString rawName = item.patientInfo.name; rawName.replace(QRegularExpression("[^a-zA-Z0-9 -]"), ""); // 移除非字母数字字符 // 转换并截断 QByteArray nameData = rawName.left(64).toLatin1(); OFString dicomName(nameData.constData()); // 写入DICOM数据集 dataset->putAndInsertString(DCM_PatientName, dicomName.c_str()); } ``` ### 错误处理建议 ```cpp OFCondition status = dataset->putAndInsertString(DCM_PatientName, nameStr); if (status.bad()) { qWarning() << "写入失败:" << status.text(); // 常见错误:EC_InvalidCharacter(非法字符)或 EC_ValueRepresentationViolated(长度超限) } ``` > **原理说明**: > DCMTK 内部使用 `OFString` 存储字符串[^3],通过 `toLatin1()` 转换可确保兼容 DICOM 的 ASCII 字符集要求。对于包含中文等非拉丁字符的场景,建议设置 `DCM_SpecificCharacterSet` 为 UTF-8(`ISO_IR 192`)[^1]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值