本文由Markdown语法编辑器编辑完成。
1. 需求提出:
由于在日常工作中,我主要接触的是胸部CT图像。图像的扫描参数一般是:
Rows: 512
Columns: 512
一张图像的大小,大约是512k左右。
一般一套薄层1.25mm的序列,大约有200~300张,那么占用内存空间约为100M ~ 150M。
由于三甲医院每天的病人量特别大,因此我们在医院的服务器,一天大约光胸部CT影像,就能接到500 ~ 1000套左右,那么一天需要新增的存储量便是:50G ~ 100G左右。
虽然现在硬件成本已经比较低,但是仍然要考虑到长久上线的成本。
解决的方案,目前有两个:
(1)由于AI主要是为了辅助医生在阅片时查看是否有相关疾病(如肺结节,骨折,脑出血等)。因此对于影像的时效性比较关注。也就是在患者拍完片后,放射科医生准备阅片前,要完成相应的模型预测。而当影像报告出具后,其实这些影像可能就不用了。除非医生还需要拿一些片子做科研,写论文等。
因此,我们会设计一个磁盘空间阈值,当发现当前的硬盘占用量超过这个阈值时,便在凌晨开始进行自动清理。删除一些已经过期很久的图像。
(2)DCM标准中,本身提供了压缩算法。压缩算法又分为有损压缩和无损压缩。
为了达到既节省硬盘占用率,同时又不影响模型的预测效果,我们当然是需要使用无损压缩算法了。
经过测试,使用无损压缩算法,可以将1张512k的CT图像,压缩到大约200~300k之间,也就是50% ~ 60%的压缩率。当然,由于每一套图占用的磁盘空间减少了,那么同样的磁盘空间,就可以存储更长时间范围内的影像了。这样,即使医生想查看比较久远的影像,也不需要再重新从医院的PACS拉图和预测了。
2. 方案调研:
对于医学影像处理,每种编程语言似乎都有一个对应的,支持dcm标准的开发库。
C++ | dcmtk, gdcm, itk, vtk |
Java | dcm4chee |
Python | pydicom |
3. 解决方案:
3.1 编译dcm4chee:
由于已有技术方案是采用java实现的. 因此, 这里还是继续沿用java的语言, 采用dcm4chee的相关库来实现压缩图像的功能.
dcm4chee是DICOM标准的JAVA实现, 它的源代码在github的地址为:
https://github.com/dcm4che/dcm4che.
将dcm4chee的源码clone到本地代码仓库, 在IntelliJ中, 进行编译.
mvn install.
这里会遇到一个坑, 就是mvn install的时候, 总是会在编译: dcm4che-assembly这个子工程卡住. 卡住的原因是, 无法从mvn的远程仓库下载某几个dll(虽然我是在linux系统上编译, 但是还是会下载dll.)
被这个问题困扰了一段时间后. 尝试通过修改settings.xml中的镜像地址为阿里云. 但是发现, 替换为阿里云后, 同样无法通过编译. 从网上得知, 可能是阿里云的mvn仓库更新不是特别及时, 导致很多新的包无法下载.
后来, 通过查询网络, 发现了解决方案, 来自于:
https://blog.csdn.net/yashiro1123/article/details/109381622
就是手动从网络上将dcm4chee编译时无法下载下来的jar包, dll等文件, 依据提示下载下来, 并且手动拷贝到本地的.m2的文件夹下. 依次将提示缺失的文件都下载并保存后, 再次运行mvn install, 就可以将dcm4chee的源码编译成功了.
3.2 参考dcm4chee中的Compressor, Dcm2Dcm
dcm4chee编译完成后, 就可以参考dcm4chee中的压缩文件的相关类及其函数了.
在dcm4chee-tool中, 提供了一系列的转化工具, 几乎涵盖了目前所有的转换任务.
而本需求, 其实就是将一张原始的dcm图像, 转化为一张jpeglossless的无损压缩的dcm图像.
在DICOM标准中, dcm的TransferSyntaxUID是表明影像的传输语法及压缩格式. 经过编译后, dcm4chee中会新生成一个UID.java的类, 该类会存储dicom中所有的UID属性的值.
其中, 与TransferSyntaxUID相关的值:
/** Implicit VR Little Endian, TransferSyntax */
public static final String ImplicitVRLittleEndian = "1.2.840.10008.1.2";
/** Explicit VR Little Endian, TransferSyntax */
public static final String ExplicitVRLittleEndian = "1.2.840.10008.1.2.1";
/** Deflated Explicit VR Little Endian, TransferSyntax */
public static final String DeflatedExplicitVRLittleEndian = "1.2.840.10008.1.2.1.99";
/** Explicit VR Big Endian (Retired), TransferSyntax */
public static final String ExplicitVRBigEndian = "1.2.840.10008.1.2.2";
/** JPEG Lossless, Non-Hierarchical, First-Order Prediction (Process 14 [Selection Value 1]), TransferSyntax */
public static final String JPEGLosslessSV1 = "1.2.840.10008.1.2.4.70";
/** JPEG-LS Lossless Image Compression, TransferSyntax */
public static final String JPEGLSLossless = "1.2.840.10008.1.2.4.80";
/** JPEG-LS Lossy (Near-Lossless) Image Compression, TransferSyntax */
public static final String JPEGLSNearLossless = "1.2.840.10008.1.2.4.81";
等等.