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

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

下文中的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-REPORT, N-GET,N-SET, N-CREATE, N-DELETE, N-ACTION 11种类型的服务,这些服务成为了DIMSE服务组。

DIMSE层数据流示意图

 能标识当前PDV是什么类型,数据流中,有一个Flags标识,具体的含义见下图。DIMSE中消息由指令(Command)和数据集(Data Set)构成。

PDV类型含义

 2 Modality Worklist通讯

Worklist 消息交互过程

2.1 A-ASSOCIATE-req

A-ASSOCIATE-rq

       首先由SCU向SCP发起建立连接建立连接请求,此过程主要是要协商是否能够提供此服务,以及在实际通讯中所使用的传输语法,传输语法主要是指显式或者隐式VR以及大小尾编码方式;从上图中可以看出,在1标记处,能看到PDU的Type是0x01,是A-ASSOCIATE-req类型的PDU;SCU的AE Title是WLY(占用16个字节),SCP的AE Title是CONQUESTSRV1(占用16个字节);之后有32个字节的保留区域不存放任何信息;

A-ASSOCIATE-rq 字节流占位

具体的字节占位,请看下图中所示: 

A-ASSOCIATE-rq 二进制流占位

2.1.1 应用上下文

         应用上下文,大家可以不必关注,这个只是标识DICOM通讯类型的标识,所有通讯都是一致的;下图是应用上下文的二进制的占位

应用上下文二进制流占位

具体的二级制流如下

应用上下文二进制流

2.1.2 表示上下文

表示上下文二进制流占位
表示上下文二进制流

2.1.3 User Info

User Info 二进制流

通过对request的分析后,后续的PDU将只针对内部关键的信息进行说明。

2.2 A-ASSOCIATE-ac

A-ASSOCIATE-ac 二进制流

2.3 P-DATA-TF PDU (C-FIND-RQ)

       SCU发送端WLY向SCP服务端CONQUESTSRV1发送C-FIND的DIMSE消息,服务端解析当前的PDU,发现其类型是0x04。那么,后续的解析工作,就将PDU的头信息去掉后,交给DIMSE层来进行处理,详细的解释见下图,其中,橘黄色2位置,表示当前的PDV是一个命令集。在命令集中,请求的Message ID是1,注意,服务方要对当前的请求进行应答,必须也要携带此Message ID,来表示应答信息是对此请求进行的响应。

Worklist请求信息组成的PDU二进制流

 2.4 P-DATA-TF PDU (C-FIND-RSP)

   

C-FIND-RQ的命令集二级制流

   

     服务端接收到C-FIND-RQ后,将回复请求端信息。在回复命令信息中,Status是一个重要的概念,如果是Pending,表示对应客户端请求(回复数据流的Message ID Being Responsed To的值和请求端的Message ID的值相同)的回复数据流还未结束。具体的含义,可以在第4章中进行查询。下边给出Status的可能的值

C-FIND Response中的Status的值

 2.5  P-DATA-TF PDU 服务端返回相关查询信息

         

服务端回复客户端查询信息的数据集二级制信息流

 2.6 P-DATA-TF PDU ,PDV是命令集,标识对应MessageID=1的查询已结束

回复命令集二进制流

2.7  A-RELEASE request和  A-RELEASE response

A-RELEASE request
A-RELEASE response

``` void esReprocessPANOGUI::UpdateSliceShow() { vtkImageData*imageData = DataCenter::GetInstance()->GetImageData(); esDicomTagInfo*tagInfo = DataCenter::GetInstance()->GetTagInfo(); esProjectBase*projectData = DataCenter::GetInstance()->GetProjectData(); QString annoStr2 = DataCenter::GetInstance()->GetConerInfo(2); QString annoStr3 = DataCenter::GetInstance()->GetConerInfo(3); if (imageData) { QMap<int, esWidgetDisplayInfo> &widDisInfoMap = projectData->widDisInfoMap; QMap<int, QList<esMarkInfo>>& markListMap = projectData->markInfoMap; int colorWindow, colorLevel; DataCenter::GetInstance()->GetWindowLevel(colorWindow, colorLevel, tagInfo, imageData); m_colorWindow = colorWindow; m_colorLevel = colorLevel; double scalarRange[2]; imageData->GetScalarRange(scalarRange); int windowId = m_sliceWidget->GetCustomWinId(); //init slider ui->contrastSlider->blockSignals(true); ui->brightSlider->blockSignals(true); ui->contrastSlider->setRange(scalarRange[0], scalarRange[1]); ui->brightSlider->setRange(scalarRange[0], scalarRange[1]); ui->contrastSlider->blockSignals(false); ui->brightSlider->blockSignals(false); m_sliceWidget->SetImageData(imageData); m_sliceWidget->SetAnnotationText(2, annoStr2); m_sliceWidget->SetAnnotationText(3, annoStr3); m_sliceWidget->UpdateShow(); if (widDisInfoMap.contains(windowId)) { esWidgetDisplayInfo &disInfo = widDisInfoMap[windowId]; vtkCamera*camera = m_sliceWidget->GetRender()->GetActiveCamera(); StructJsonConvert::QJsonObjectToInfo(disInfo.cameraInfo, camera); m_sliceWidget->GetRender()->ResetCameraClippingRange(); m_colorWindow = disInfo.colorWindow; m_colorLevel = disInfo.colorLevel; } else m_sliceWidget->ResetCamera(); //小工具 if (markListMap.contains(windowId)) { QList < esMarkInfo > &markList = markListMap[windowId]; m_sliceWidget->GetToolWidgetHelper()->SetMarkInfoList(markList); } m_sliceWidget->SetColorWindowLevel(m_colorWindow, m_colorLevel); ColorWindow_Slot(m_colorWindow); ColorLevel_Slot(m_colorLevel); m_sliceWidget->GetRenderWindow()->Render(); } }```帮我分析这段代码的功能并详细介绍
03-20
<think>好的,我需要分析用户提供的这段C++代码,理解它的功能并进行详细解释。首先,我会通读代码,了解整体结构和各个部分的作用。 代码属于`esReprocessPANOGUI`类的`UpdateSliceShow`方法。从函数名来看,这可能与更新切片显示有关,可能是在医学影像处理中常见的功能。 首先,函数开头获取了`imageData`、`tagInfo`、`projectData`以及两个注释字符串`annoStr2`和`annoStr3`。这些数据可能来自数据中心,用于后续的显示和处理。检查`imageData`是否存在,如果存在则继续执行,否则可能跳过更新。 接下来,代码访问了`projectData`中的`widDisInfoMap`和`markInfoMap`,这两个映射可能存储了窗口的显示信息和标记信息。然后获取了颜色窗宽和窗位,这在医学影像中用于调整图像的对比度和亮度。将获取的值赋给成员变量`m_colorWindow`和`m_colorLevel`。 之后,获取图像的标量范围`scalarRange`,用于设置对比度和亮度滑块的数值范围。这里使用了`blockSignals`来防止在设置范围时触发信号,避免不必要的信号处理。 接下来,`m_sliceWidget`被设置图像数据和注释文本,并调用`UpdateShow`进行更新。这里可能涉及到VTK的渲染管线,将图像数据传递给可视化组件。 然后,检查当前窗口ID是否存在于`widDisInfoMap`中。如果存在,则从`disInfo`中恢复相机参数和颜色设置,否则重置相机。这部分处理了视图的相机位置和裁剪范围,确保显示正确。 如果`markListMap`包含当前窗口ID,会将标记信息列表设置到工具小部件助手,这可能用于在图像上显示标注或测量结果。 最后,设置颜色窗宽和窗位,触发相应的槽函数更新界面,并调用`Render`进行重新渲染,确保所有更改生效。 需要确认各个函数和变量的具体作用,例如`SetColorWindowLevel`如何影响图像显示,`ColorWindow_Slot`和`ColorLevel_Slot`是否更新UI元素。此外,`GetCustomWinId`可能返回当前窗口的标识符,用于多窗口情况下的数据区分。 总结代码功能:该函数负责更新切片视图的显示,包括图像数据、注释信息、颜色窗设置、相机参数以及标记工具的状态,最终渲染更新后的图像。</think>以下是对这段C++代码的逐步分析: ### 一、功能概述 这是一个医学影像处理系统中切片显示更新的核心函数,主要完成以下功能: 1. **数据获取**:从数据中心获取DICOM图像数据、标签信息和标注信息 2. **窗宽窗位设置**:动态调整医学影像的对比度/亮度参数 3. **视图更新**:刷新2D切片显示(含标注信息) 4. **相机参数同步**:维护视图的缩放/平移状态 5. **标注工具管理**:处理测量标记的显示 ### 二、代码结构解析 ```cpp void esReprocessPANOGUI::UpdateSliceShow() { // 核心代码段解析 |-- 数据准备阶段 [L3-L7] |-- 有效性校验 [L8] |-- 显示参数配置 [L9-L26] |-- 视图渲染控制 [L27-L44] } ``` ### 三、关键代码段详解 #### 1. 数据获取 (医学影像处理核心) ```cpp vtkImageData* imageData = DataCenter::GetInstance()->GetImageData(); // 获取VTK图像数据 esDicomTagInfo* tagInfo = DataCenter::GetInstance()->GetTagInfo(); // 获取DICOM标签信息 QString annoStr3 = DataCenter::GetInstance()->GetConerInfo(3); // 获取角标注释(如:"PA\\34.5") ``` #### 2. 窗宽窗位动态调整 ```cpp // 计算初始窗宽窗位(CT值映射关键) DataCenter::GetInstance()->GetWindowLevel(colorWindow, colorLevel, tagInfo, imageData); // 设置滑动条范围(基于像素值范围) double scalarRange[2]; imageData->GetScalarRange(scalarRange); // 获取像素值范围,如:[0,4095] ui->contrastSlider->setRange(scalarRange[0], scalarRange[1]); // 对比度滑动条 ui->brightSlider->setRange(scalarRange[0], scalarRange[1]); // 亮度滑动条 ``` #### 3. 视图状态维护 ```cpp // 相机参数保存与恢复 if (widDisInfoMap.contains(windowId)) { StructJsonConvert::QJsonObjectToInfo(disInfo.cameraInfo, camera); // 反序列化相机参数 m_sliceWidget->GetRender()->ResetCameraClippingRange(); // 自动调整裁剪范围 } else { m_sliceWidget->ResetCamera(); // 重置为默认视图 } // 标注工具同步 m_sliceWidget->GetToolWidgetHelper()->SetMarkInfoList(markList); // 加载测量标记(如:长度标注) ``` #### 4. 渲染控制 ```cpp m_sliceWidget->SetColorWindowLevel(m_colorWindow, m_colorLevel); // 应用窗宽窗位 ColorWindow_Slot(m_colorWindow); // 更新UI显示(如:文本框数值) ColorLevel_Slot(m_colorLevel); m_sliceWidget->GetRenderWindow()->Render(); // 触发VTK渲染管线更新 ``` ### 四、设计模式分析 1. **单例模式**:`DataCenter::GetInstance()` 确保全局数据统一访问 2. **观察者模式**:`blockSignals(true)` 避免滑动条数值变更时的信号循环 3. **MVC架构**: - Model:`vtkImageData`(图像数据) - View:`m_sliceWidget`(显示组件) - Controller:滑动条事件处理 ### 五、医疗影像特性处理 1. **窗宽窗位公式**: 实际显示像素值 = $\frac{(原始值 - \text{窗位} + 0.5 \times \text{窗宽})}{\text{窗宽}} \times 255$ 通过`SetColorWindowLevel`实现该映射 2. **标注信息规范**: ```cpp // 角标信息示例: "PA\\34.5" // 体位标记\\层厚 ``` 该函数通过VTK可视化管线实现医学影像的实时交互显示,是医学影像处理系统中视图控制的关键模块。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值