在上一篇文章《》的“如何实现DICOMweb”部分,提到了有一套Python版本的DICOMweb服务器端开源代码,NeurDICOM。这里书接上文,对这个开源框架做一个介绍。包括这个框架的定位、功能、框架、实现,以及有哪些值得参考借鉴的。
轻量级DICOMweb服务器
NeurDICOM是一个轻量级的DICOMweb服务器,代码完全开源,Github网址:。框架的开发初衷是用于支持机器学习和神经网络在医学影像领域的应用。使用者可以采用这个框架,集中存储和管理大批量影像,通过互联网进行影像的查询、获取和上传,并且能够集成调用自己开发的影像分析与处理扩展插件。
这篇文章只介绍NeurDICOM框架是如何实现和提供DICOMweb标准接口服务的。
提供最常用的“增删改查”功能
NeurDICOM作为服务器,对外提供DICOMweb服务,包括QIDO-RS,WADO-RS,STOW-RS。具体来说,NeurDICOM在患者(Patient)、检查(Study)、序列(Series)和实例(Instance)四个级别的对象上,提供查询、获取、上传(更新)和删除服务。简单来说,就是通过RESTful接口hfs网络文件服务器,面向互联网,提供DICOM对象的增删改查服务。
Instance大家可能不熟悉,可以简单理解为Image。当然,严格来说Instance还包含了图像以外的其他文件。
开源软件4件套
NeurDICOM基于4个非常具有代表性的开源软件搭建。包括:
1)采用Django作为框架;
2)通过Tornado提供非阻塞网络I/O服务;
3)借助Django REST framework来简化和提速RESTful接口开发;
4)底层使用PostgreSQL存储DICOM文件索引信息。
此外,还使用了pydicom来解析DICOM文件,使用pynetdicom来进行DICOM通讯。
PostgreSQL负责DICOM信息存储与索引
PostgreSQL是一个开源免费关系型数据库,可以看做是MySQL之外的另一个选择。NeurDICOM对PostgreSQL的使用简单明了,就是建立了Patients、Studies、Series和Instances这4个核心表格,分别存储从导入的DICOM文件中解析出来的DICOM头文件信息。每个表格中的字段也基本是包括且仅包括DICOM信息中包含的,同时又是DICOMweb接口需要的基础信息项。例如,Patiets表的ID、姓名、性别、年龄、出生日期字段;Study表的UID、ID、检查日期、检查描述,以及用于和Patient表关联的patient_ID字段。Series和Instances表格与此类似。
Django框架负责后台影像管理
Django是一个重量级Python开源Web Server开发框架。突出优势在于:功能完善且丰富,提供了ORM(Object-Relation-Model)框架,以及丰富的后台管理功能。
NeurDICOM恰恰重度使用Django框架的上述两个特点。首先,建立了Patient、Study、Series和Instance Model,与PostgreSQL数据库中的相应表格对应;其次,使用Django的后台管理,也就是manage.py文件,来完成包括DICOM文件批量导入在内的后台影像管理功能。例如,通过如下示例代码,导入”images”文件夹下的所有DICOM影像。
python ./manage.py store_dicom ./images
对于导入的DICOM影像,其DICOM头文件信息将按级别分类存储到前述PostgreSQL表格中,DICOM文件本身将集中存储在一个指定的文件夹目录下,缺省为代码根目录下的“media”目录。在media目录下,所有DICOM文件不再划分目录,直接存储在根目录下。Windows平台用户需要注意,如果一个文件夹下存储文件过多,会导致这个文件夹打开非常慢,几乎不可操作。也是因为这个原因,NeurDICOM在Windows平台上,不适合管理大规模的影像数据。
Tornado提供高效异步IO
Tornado是一个轻量级Python开源Web Server开发框架。特点和优势在于提供异步非阻塞IO处理,具备出色的抗负载能力和优异的处理性能,不依赖多进程/多线程。这也应该是作者选择Tornado来提供DICOMweb接口服务的主要原因吧。
此外,Tornado还有一个优点,全面支持WSGI,可以与其他支持WSGI的web框架和HTTP服务器集成,搭档工作。也正是基于这一点,NeurDICOM才能将Tornado作为Django框架之上的更上一层框架,启动和运行。WSGI全称PythonWeb服务器网关接口(Python Web Server Gateway Interface)。对于普通开发人员,只需要知道遵循WSGI的Web Application可以在遵循WSGI的Web Server上运行就可以了。()
Django REST framework加速接口开发
Django REST framework(以下简称DRF)是在Django框架上的一个扩展开发库。不谈技术细节,只说用途,如果你的Django应用是一个非常典型的ORM系统,应用主要功能是通过RESTful接口进行后台数据库的增删改查,那么DRF绝对能让你事倍功半,“出活贼快”。而且,DRF还附赠你一套网页版的RESTful API管理和测试页面,即实用,还“高大上”。此外,DRF还可以帮你实现用户认证功能。
对照再来看NeurDICOM,它就是一个典型(几乎纯粹)的ORM系统,说白了,就是通过RESTful对后台DICOM影像“增删改查”。这种场景下,非常适合选择DRF。
接口列表
NeurDICOM实现了QIDO-RS,WADO-RS,STOW-RS服务。具体来说,对外提供面向Patient、Study、Series、Instance四个级别的查询、上传/更新和删除操作。概要列表如下。
Patient级别接口类型URL说明
GET
/patients
Find all patients
GET
/patients/:id
Find patient by id
GET
/patients/:id/studies
Find studies for patient
PUT
/patients/:id
Update patient
DELETE
/patients/:id
Delete patient
Study级别接口类型URL说明
GET
/studies
Find all studies
GET
/studies/:id
Find studies by id
GET
/studies/:id/series
Find study’s series
PUT
/studies/:id
Update study
DELETE
/studies/:id
Delete study
Series级别接口类型URL说明
GET
/series
Find all series
GET
/series/:id
Find series by id
GET
/series/:id/instances
Find series’ instances
PUT
/series/:id
Update series
DELETE
/series/:id
Delete series
Instance级别接口类型URL说明
GET
/instances
Find all instance
GET
/instances/:id
Find instance by id
GET
/instances/:id/tags
Get instance’s tags
GET
/instances/:id/image
Get instance’s image
GET
/instances/:id/raw
Get instance’s raw pixel data in bytes
PUT
/instances/:id
Update instance
DELETE
/instances/:id
Delete instance
接口调用示例
[ {"id": 1, "patient_id": "LGG-277", "patient_name": "LGG-277", "patient_sex": "F", "patient_age": "033Y", "patient_birthdate": null, "images_count": 40},
{"id": 2, "patient_id": "LGG-104", "patient_name": "LGG-104", "patient_sex": "F", "patient_age": "055Y", "patient_birthdate": null, "images_count": 60} ]
{"id": 2, "patient_id": "LGG-104", "patient_name": "LGG-104", "patient_sex": "F", "patient_age": "055Y", "patient_birthdate": null, "images_count": 60}
[ {"id": 1, "study_id": "", "study_instance_uid": "1.3.6.1.4.1.14519.5.2.1.3344.2526.255070860832049529485991067110", "study_description": "MRI Hd wo&w", "patient": {"id": 1, "patient_id": "LGG-277", "patient_name": "LGG-277", "patient_sex": "F", "patient_age": "033Y", "patient_birthdate": null, "images_count": 40}, "modalities": ["MR"], "images_count": 40, "series": [{"id": 1, "series_instance_uid": "1.3.6.1.4.1.14519.5.2.1.3344.2526.124096722224764465690712119626", "series_number": "2", "modality": "MR", "patient_position": "HFS", "body_part_examined": "BRAIN", "protocol_name": null, "images_count": 40}]}, {"id": 2, "study_id": "", "study_instance_uid": "1.3.6.1.4.1.14519.5.2.1.3344.2526.291265840929678567019499305523", "study_description": "MRI Hd wo+w", "patient": {"id": 2, "patient_id": "LGG-104", "patient_name": "LGG-104", "patient_sex": "F", "patient_age": "055Y", "patient_birthdate": null, "images_count": 60}, "modalities": ["MR"], "images_count": 60, "series": [{"id": 2, "series_instance_uid": "1.3.6.1.4.1.14519.5.2.1.3344.2526.290843139448249569449245338151", "series_number": "4", "modality": "MR", "patient_position": "HFS", "body_part_examined": "BRAIN", "protocol_name": null, "images_count": 60}]}]
内容很长,但还是把完整原版贴出来了。大家可以拷贝粘贴到网站,看如下图所示的美化版。
{"id": 1, "study_id": "", "study_instance_uid": "1.3.6.1.4.1.14519.5.2.1.3344.2526.255070860832049529485991067110", "study_description": "MRI Hd wo&w", "patient": {"id": 1, "patient_id": "LGG-277", "patient_name": "LGG-277", "patient_sex": "F", "patient_age": "033Y", "patient_birthdate": null, "images_count": 40}, "modalities": ["MR"], "images_count": 40, "series": [{"id": 1, "series_instance_uid": "1.3.6.1.4.1.14519.5.2.1.3344.2526.124096722224764465690712119626", "series_number": "2", "modality": "MR", "patient_position": "HFS", "body_part_examined": "BRAIN", "protocol_name": null, "images_count": 40}]}
{"id": 2, "series_instance_uid": "1.3.6.1.4.1.14519.5.2.1.3344.2526.290843139448249569449245338151", "series_number": "4", "modality": "MR", "patient_position": "HFS", "body_part_examined": "BRAIN", "protocol_name": null, "images_count": 60}
备注:篇幅原因,下面只贴了一个Instance,并且用上面的网站做了排版美化。
[
{
"id": 50,
"sop_instance_uid": "1.3.6.1.4.1.14519.5.2.1.3344.2526.488494922553545202183930699463",
"rows": 512,
"columns": 512,
"smallest_image_pixel_value": 0,
"largest_image_pixel_value": 2222,
"color_space": null,
"pixel_aspect_ratio": null,
"pixel_spacing": "['0.4688', '0.4688']",
"photometric_interpretation": "MONOCHROME2",
"image": "/media/instances/9b1ac1c16a3c4abf8ca863883749414d.dcm",
"instance_number": 1,
"parent": {
"patient": {
"patient_name": "LGG-104",
"patient_id": "LGG-104"
},
"study": {
"study_date": "2000-06-26"
},
"series": {
"id": 2,
"modality": "MR"
}
}
}
]
{
"id": 2,
"sop_instance_uid": "1.3.6.1.4.1.14519.5.2.1.3344.2526.123298277938639023090341767315",
"rows": 256,
"columns": 256,
"smallest_image_pixel_value": null,
"largest_image_pixel_value": null,
"color_space": null,
"pixel_aspect_ratio": null,
"pixel_spacing": "['0.781253', '0.781250']",
"photometric_interpretation": "MONOCHROME2",
"image": "/media/instances/74b49e07b57a4039b497f654266feb49.dcm",
"instance_number": 24,
"parent": {
"patient": {
"patient_name": "LGG-277",
"patient_id": "LGG-277"
},
"study": {
"study_date": "1990-06-14"
},
"series": {
"id": 1,
"modality": "MR"
}
}
}
如下图所示的图片。
如下图所示的16进制Raw Data。
一些感想
说白了,DICOMweb就是通过RESTful接口对DICOM影像进行“增删改查“,与通常的数据库网络应用本质上是一致的。Django框架在这方面提供了非常完善的支持,选用Django框架及其扩展组件,能够大大降低开发成本,提供开发效率。
DICOM的核心概念体系是Patient-Study-Series-Instances层级关系模型,DICOMweb的接口设计体系也与此严格对应。这又属于典型的ORM,Django对ORM也提供了完善的支持。
其实,各个PACS厂商的底层数据库设计中,都会包含类似于Patient-Study-Series-Instances这4张核心表格。也就是说,DICOMweb需要的各项信息在数据库中都已经有了。所以,如果要扩展增加DICOMweb接口,需要做的就是找一个好用的WebServer框架,实现一组对外RESTful接口。或者说,传统PACS系统与DICOMweb之间,差的就是一个中高级Python或JS程序员的距离。或者说,差的就是一套开源代码(MIT授权)的距离?
另外,也还有一些疑问。最关键的,DICOMweb接口的效率如何?能支持高并发调用吗?从前面的“接口调用示例”可以看到,如果查询请求包含的结果较多hfs网络文件服务器,JSON文本会非常长。是否需要,或是否能够对JSON文本进行压缩传输?如果请求图像的RawData,更面临类似的问题。JPEG2000压缩格式的DICOM影像是否更加适合DICOMweb模式?
更多问题和答案只能留待实践,“谁用谁知道了~~”
———END———
限 时 特 惠:本站每日持续更新海量各大内部创业教程,一年会员只需128元,全站资源免费下载点击查看详情
站 长 微 信:jiumai99