题记:
DICOM专栏系列虽然写了多年,但是依然不能解决大家日常中遇到的种种问题,其实这恰恰就是程序员(码农)工作的最大乐趣所在。就像每个人的人生一样,所处的环境不同,所遭遇的事件不同,结果自然就不同。程序开发亦是如此,操作系统不同、软件版本不同,本地配置不同都会导致种种问题。
“授人以鱼不如授人以渔”,所以正常的解决之道是希望通过专栏的讲解,能够让大家真正理解每个问题出现的背后原因,从而主动排查并解决问题。对于排查和解决过程中遇到的问题,我会整理总结成博文供大家参考。正如上文所说,每个人的环境不同,自然真正的解决方案不同,所以仅仅是参考。
背景:
DICOM相关开源库很多,按照开发语言不同主流有三类,基于C/C++的dcmtk、基于Java的dcm4che、以及基于C#的fo-dicom(mDCM)。这里再次强调一下DCM4CHEE是基于dcm4che14版本基础上,采用EJB2.x技术开发依托于JBoss的DICOM归档服务框架,可以简单的理解为一个开源的PACS系统。每个库的维护机构和人员不同,因此导致版本和数据格式兼容性上都有所不同。尤其是当在自己的项目中使用的开源库版本升级后,如何平稳过度到最新版本,如何将修复的bug补丁到现有版本尤为关键。
我个人在项目中使用的诸多开源库,都会在github上fork一个个人私有库,平日里根据国内现状以及特殊性修复自己发现的bug,此外间隔一段时间会将主库的版本pull到本地进行merge。这样可以确保现有项目平稳运行,既能适应项目特殊性,又能兼容主版本相关补丁。本篇博文简单列举几个本地修复的问题,详情可参考我的github主页相关项目。
dcm4che14开源库:
1. CDimseService.cFIND()结果返回异常
cFIND()函数的部分源码如下:
// New Cammand Set, see: DICOM Part 7: Message Exchange, 6.3.1 Command
// Set Structure
Command rqCmd = dof.newCommand();
rqCmd.initCFindRQ(assoc.nextMsgID(), UIDs.StudyRootQueryRetrieveInformationModelFIND, priority);
Dimse findRq = aFact.newDimse(pc.pcid(), rqCmd, keys);
// Invoke active association with find request Dimse
FutureRSP future = aassoc.invoke(findRq);
// Response to the C-FIND request.
// The result cannot be accessed until it has been set. LINE:2234
// Get the list of found objects
dimseList = future.listPending();
datasetVector = new Vector<Dataset>();
// Process all elements
for (int i = 0; i < dimseList.size(); i++) {
datasetVector.addElement(((Dimse) dimseList.get(i)).getDataset());
}
return datasetVector;
从代码以及命名规则上可以判断出assoc.invoke应该是发起了异步请求,再仔细看上述注释的LINE:2234标记的一行,“The result cannot be accessed until it has been set. LINE:2234”。理想状态下面的dimseList = future.listPending();应该是利用FutureRSPImpl进行的同步管理,直至所有结果都返回后获取内部的数据集。
我们再看一下FutureRSPImpl.listPending()函数,源码如下:
//zssure:用于存储DIMSE返回结果
private final ArrayList pending = new ArrayList();
//zssure:获取返回结果
public synchronized List listPending() {
return Collections.unmodifiableList(pending);
}
其中pending就是我们希望获得的最终C-FIND的查询结果,具体对pending的操作如下图所示:
// DimseListener implementation ---------------------------------
public void dimseReceived(Association assoc, Dimse dimse) {
if (dimse.getCommand().isPending()) {
pending.add(dimse);
} else {
set(dimse);
}
}
这就是之前多次介绍过的DICOM的状态机中调用的回调函数。用于处理汇总C-FIND的各条记录。
【问题】:但是在本地测试时会出现严重的问题,每次listPending返回的结果格式是随机的。那么究竟是什么问题导致的呢。让我们看看listPending()函数是否能够做到同步处理直至所有的C-FIND结果顺利返回。
上文源码可以看到listPending()中并未出现任何同步操作,而是使用了Java的Collections.unmodifiableList,这个其实就是简单的重构方式,返回pending的只读副本,限制外部对pending这一ArrayList的更改,即使listPending函数使用了synchronized关键字依然达不到等待所有结果返回的目的,因为synchronized关键字限制的是多个线程同时并发进入listPending函数,然而listPending函数并不会再多个线程中使用。所以listPending()函数设计有误,并不能确保所有结果返回。真正的等待结果返回应该使用FutureRSPImpl的isReady函数,从上面dimseReceived函数内部的判断逻辑可知当最后一个结果返回OK(即非Pending)时,FutureRSPImpl会使用set函数将ready标记为设置为true,因此希望同步确保获取所有查询结果的使用方法如下:
// Invoke active association with find request Dimse
FutureRSP future = aassoc.invoke(findRq);
// Response to the C-FIND request.
// The result cannot be accessed until it has been set.
//zssure:2016/07/16 NIT
while(!future.isReady())
{
TimeUnit.MILLISECONDS.sleep(500);//you can do something by yourself
}
//zssure:2016/07/16 NIT end
fo-dicom开源库:
1. TransferSyntax字段自纠正
之前对于Transfer Syntax介绍过多次,在DICOM医学图像处理:DICOM网络传输中区别过Abstract Syntax与Transfer Syntax,在DICOM:dcmqrscp.exe与storescu.exe中C-STORE服务的差别中介绍过在网络服务中Transfer Syntax的作用。以及在DICOM:dcm4che工具包如何压缩dcm文件探讨(续篇)中介绍对dcm文件进行压缩时提到的JPEG LossLess压缩语义以及Implicit VR Little Endian。
Transfer Sytanx在DICOM标准中占有重要的一席之地,既作为必要元素写入到DCM文件元信息(MetaInformation)中,又是DICOM网络服务中双方数据传输的前提。此外还有一点需要注意:Transfer Syntax“作用域”,即其能够影响的范围:DICOM协议规定包含头信息(File Meta Information)的文件,头信息(即group=0002)的所有元素默认采用Explicit VR Little Endian存储,数据体Dataset(即group>0002的分组)元素如何存储则由头信息File Meta Information中的Transfer Syntax来决定。
我在github主页的fo-dico私有库中提交了fo-dicom开源库TransferSyntax字段的自校验代码Auto Check TransferSyntax of DICOM file in zssure github。具体的分析思路可参见专栏上一篇博文:DICOM:由fo-dicom库解析DICOM文件引申出来的……
dcm4chee2.X开源存储框架:
近期不少网友邮件咨询DCM4CHEE的相关问题,大致有以下几类:一类是希望使用DCM4CHEE来直接替换自己现有的PACS系统、一类是咨询DCM4CHEE是如何存储和获取图像的,最后一类就是关于DCM4CHEE中中文存储乱码的问题。对于前两类我简单的表达一下个人的观点:
- 第一,DCM4CHEE充当PACS。如果希望使用DCM4CHEE来替代自己现有的PACS作为过渡期是可以的,如果想长久使用不建议。因为目前DCM4CHEE的稳定版本是2.X,这个版本依赖的JDK、使用的JBoss都是很老的版本,存在着诸多问题。此外DCM4CHEE不单单是作为图像存储来设计的,运行过程中各项功能各个模块耦合重,所以实际运行维护过程中比较麻烦。
- 第二,对于DCM4CHEE存储和获取图像,建议直接从官网下载dcm4cheXXX二进制工具包。我建议使用命令行工具直接通过DIMSE-C服务来完成与dcm4chee的交互,并且实际工作中我也很少用可视化工具,所以大家就不要再邮件跟我索要GUI的工具了,我真是没有哈。你想向DCM4CHEE存储数据的时候,DCM4CHEE就是一个简单的PACS,提供C-STORE SCP服务。你使用任何C-STORE SCU客户端都可完成上传任务。如果想从DCM4CHEE获取数据,一种是直接使用C-GET、C-MOVE等来下载DCM文件,一种是使用WADO直接获取。
- 第三,中文乱码问题。这个问题其实不是仅仅在DCM4CHEE中出现的。而是mysql数据库使用过程中最常见的问题。无非就是各个终端(发送、接收、存储)的编码格式不一致导致的。这里我找到了几篇文章10分钟学会理解和解决MySQL乱码问题,详解mysql中文乱码的问题:三个字符集搭建可以仔细阅读以下,按照这里面的方案逐个排查自己各个环节的编码格式即可。简单的截几张图示意一下:
作者:[email protected]
时间:2016-07-16