pydicom/pynetdicom
GitHub: pydicom/pynetdicom
pynetdicom 是 DICOM 医学影像网络通信协议的纯 Python 实现,支持构建 SCU/SCP 以实现医学影像设备间的数据交换。
Stars: 566 | Forks: 193
|coverage| |unit-tests| |type-hints| |docs| |black| |pypi-versions| |python-versions| |conda| |zenodo|
.. |coverage| image:: https://codecov.io/gh/pydicom/pynetdicom/branch/main/graph/badge.svg
:target: https://codecov.io/gh/pydicom/pynetdicom
.. |unit-tests| image:: https://github.com/pydicom/pynetdicom/workflows/unit-tests/badge.svg
:target: https://github.com/pydicom/pynetdicom/actions?query=workflow%3Aunit-tests
.. |type-hints| image:: https://github.com/pydicom/pynetdicom/workflows/type-hints/badge.svg
:target: https://github.com/pydicom/pynetdicom/actions?query=workflow%3Atype-hints
.. |docs| image:: https://circleci.com/gh/pydicom/pynetdicom/tree/main.svg?style=shield
:target: https://circleci.com/gh/pydicom/pynetdicom/tree/main
.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/psf/black
.. |pypi-versions| image:: https://badge.fury.io/py/pynetdicom.svg
:target: https://badge.fury.io/py/pynetdicom
.. |python-versions| image:: https://img.shields.io/pypi/pyversions/pynetdicom.svg
:target: https://img.shields.io/pypi/pyversions/pynetdicom.svg
.. |conda| image:: https://img.shields.io/conda/vn/conda-forge/pynetdicom.svg
:target: https://anaconda.org/conda-forge/pynetdicom
.. |zenodo| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.3880767.svg
:target: https://doi.org/10.5281/zenodo.3880767
# pynetdicom
`DICOM `_ 网络协议的 Python 实现,
最初基于(旧版)
`pynetdicom `_。
## 描述
`DICOM `_ 是医学图像及相关信息的国际标准。它定义了放射学、心脏病学、放射治疗学和其他医学领域中进行媒体交换的格式和通信协议。
*pynetdicom* 是一个实现 DICOM 网络协议的纯 Python 包。配合 `pydicom `_ 使用,它可以轻松创建 DICOM *Service Class Users* (SCUs) 和 *Service Class Providers* (SCPs)。
*pynetdicom* 的主要用户类是
`AE `_
,用于表示 DICOM Application Entity。通过它你可以:
- 通过指定支持的 presentation contexts,然后调用
`AE.start_server() `_
并等待传入的 association 请求,以此将应用程序作为 SCP 启动
- 通过指定希望对端 SCP 支持的 presentation contexts,然后通过
`AE.associate() `_
方法请求 association(该方法会返回一个
`Association `_
线程),以此将应用程序作为 SCU 使用。
一旦建立关联,就可以通过发送
`DIMSE-C `_
和
`DIMSE-N `_
消息来使用该关联可用的服务。
## 文档
*pynetdicom* 的
`tutorials `_、
`user guide `_、
`code examples `_、
`application `_ 和
`API reference `_
文档可在此处获取,包括
`当前发布版本 `_ 以及
`开发版本 `_。
## 安装说明
### 依赖项
`pydicom `_
### 安装当前发布版本
使用 pip:
.. code-block:: sh
```
pip install -U pynetdicom
```
使用 conda:
.. code-block:: sh
```
conda install -c conda-forge pynetdicom
```
有关更详细的说明,包括如何安装当前的开发版本,请参阅 `安装指南
`_。
## 支持的 DIMSE 服务
### SCU 服务
当 AE 作为 SCU 运行并与对端 SCP 建立 association 后,可以使用以下 DIMSE-C 和 DIMSE-N 服务:
.. _assoc: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.association.Association.html
.. _echo: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.association.Association.html#pynetdicom.association.Association.send_c_echo
.. _find: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.association.Association.html#pynetdicom.association.Association.send_c_find
.. _c_get: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.association.Association.html#pynetdicom.association.Association.send_c_get
.. _move: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.association.Association.html#pynetdicom.association.Association.send_c_move
.. _store: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.association.Association.html#pynetdicom.association.Association.send_c_store
.. _action: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.association.Association.html#pynetdicom.association.Association.send_n_action
.. _create: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.association.Association.html#pynetdicom.association.Association.send_n_create
.. _delete: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.association.Association.html#pynetdicom.association.Association.send_n_delete
.. _er: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.association.Association.html#pynetdicom.association.Association.send_n_event_report
.. _n_get: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.association.Association.html#pynetdicom.association.Association.send_n_get
.. _set: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.association.Association.html#pynetdicom.association.Association.send_n_set
+----------------+----------------------------------------------------------------------------------------+
| DIMSE 服务 | `Association `_ 方法 |
+================+========================================================================================+
| C-ECHO | `Association.send_c_echo() `_ |
+----------------+----------------------------------------------------------------------------------------+
| C-FIND | `Association.send_c_find(dataset, query_model) `_ |
+----------------+----------------------------------------------------------------------------------------+
| C-GET | `Association.send_c_get(dataset, query_model) `_ |
+----------------+----------------------------------------------------------------------------------------+
| C-MOVE | `Association.send_c_move(dataset, move_aet, query_model) `_ |
+----------------+----------------------------------------------------------------------------------------+
| C-STORE | `Association.send_c_store(dataset) `_ |
+----------------+----------------------------------------------------------------------------------------+
| N-ACTION | `Association.send_n_action(dataset, action_type, class_uid, instance_uid) `_ |
+----------------+----------------------------------------------------------------------------------------+
| N-CREATE | `Association.send_n_create(dataset, class_uid, instance_uid) `_ |
+----------------+----------------------------------------------------------------------------------------+
| N-DELETE | `Association.send_n_delete(class_uid, instance_uid) `_ |
+----------------+----------------------------------------------------------------------------------------+
| N-EVENT-REPORT | `Association.send_n_event_report(dataset, event_type, class_uid, instance_uid) `_ |
+----------------+----------------------------------------------------------------------------------------+
| N-GET | `Association.send_n_get(identifier_list, class_uid, instance_uid) `_ |
+----------------+----------------------------------------------------------------------------------------+
| N-SET | `Association.send_n_set(dataset, class_uid, instance_uid) `_ |
+----------------+----------------------------------------------------------------------------------------+
其中 *dataset* 是一个 pydicom
`Dataset `_
对象,*query_model* 是一个 UID 字符串,*identifier_list* 是一个 pydicom
`Tag `_
对象列表,*event_type* 和 *action_type* 是整数,*class_uid* 和
*instance_uid* 是 UID 字符串。有关更多信息,请参阅
`Association 文档 `_。
### SCP 服务
当 AE 作为 SCP 运行时,一旦建立 association,对端就可以使用以下 DIMSE-C 和 DIMSE-N 服务:
.. _hecho: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom._handlers.doc_handle_echo.html
.. _hfind: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom._handlers.doc_handle_find.html
.. _hc_get: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom._handlers.doc_handle_c_get.html
.. _hmove: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom._handlers.doc_handle_move.html
.. _hstore: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom._handlers.doc_handle_store.html
.. _haction: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom._handlers.doc_handle_action.html
.. _hcreate: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom._handlers.doc_handle_create.html
.. _hdelete: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom._handlers.doc_handle_delete.html
.. _her: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom._handlers.doc_handle_event_report.html
.. _hn_get: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom._handlers.doc_handle_n_get.html
.. _hset: https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom._handlers.doc_handle_set.html
+----------------+----------------------------+---------------------------------+
| DIMSE 服务 | Intervention Event | Handler 文档 |
+================+============================+=================================+
| C-ECHO | ``evt.EVT_C_ECHO`` | `Handle C-ECHO `_ |
+----------------+----------------------------+---------------------------------+
| C-FIND | ``evt.EVT_C_FIND`` | `Handle C-FIND `_ |
+----------------+----------------------------+---------------------------------+
| C-GET | ``evt.EVT_C_GET`` | `Handle C-GET `_ |
+----------------+----------------------------+---------------------------------+
| C-MOVE | ``evt.EVT_C_MOVE`` | `Handle C-MOVE `_ |
+----------------+----------------------------+---------------------------------+
| C-STORE | ``evt.EVT_C_STORE`` | `Handle C-STORE `_ |
+----------------+----------------------------+---------------------------------+
| N-ACTION | ``evt.EVT_N_ACTION`` | `Handle N-ACTION `_ |
+----------------+----------------------------+---------------------------------+
| N-CREATE | ``.EVT_N_CREATE`` | `Handle N-CREATE `_ |
+----------------+----------------------------+---------------------------------+
| N-DELETE | ``evt.EVT_N_DELETE`` | `Handle N-DELETE `_ |
+----------------+----------------------------+---------------------------------+
| N-EVENT-REPORT | ``evt.EVT_N_EVENT_REPORT`` | `Handle N-EVENT-REPORT `_ |
+----------------+----------------------------+---------------------------------+
| N-GET | ``evt.EVT_N_GET`` | `Handle N-GET `_ |
+----------------+----------------------------+---------------------------------+
| N-SET | ``evt.EVT_N_SET`` | `Handle N-SET `_ |
+----------------+----------------------------+---------------------------------+
除 C-ECHO 服务外,用户定义的可调用函数(即 *handler*)必须绑定到相应的
`intervention event `_
才能完成 DIMSE 服务请求。可以通过 ``from pynetdicom import evt`` 导入事件,并在启动 association 之前,通过
`AE.start_server() `_
和
`AE.associate() `_
中的 *evt_handlers* 关键字参数将 handler 绑定到事件。
当事件发生时,将调用 *handler* 函数并传递一个参数 *event*,它是一个
`Event `_
对象,其具体属性取决于发生的事件类型。
绑定到 intervention event 的 handler 必须返回或生成(yield)特定值。有关每种事件类型在 ``Event`` 中可用的属性和属性,以及对应 handler 的预期返回值/生成值的更多信息,请参阅
`handler 文档 `_。
## 应用程序
*pynetdicom* 包含一些基本的 DICOM 应用程序:
* `echoscp `_
* `echoscu `_
* `findscu `_
* `getscu `_
* `qrscp `_
(需要 `sqlalchemy `_)
* `movescu `_
* `storescp `_
* `storescu `_
## 代码示例
文档中提供了更多
`代码示例 `_。
### Echo SCU
向 Verification SCP 发送 C-ECHO 请求(位于 TCP/IP 地址 *addr*,监听端口
*port*):
.. code-block:: python
```
from pynetdicom import AE
ae = AE(ae_title='MY_ECHO_SCU')
# Verification SOP Class has a UID of 1.2.840.10008.1.1
# we can use the UID str directly when adding the requested
# presentation context
ae.add_requested_context('1.2.840.10008.1.1')
# Associate with a peer AE
assoc = ae.associate(addr, port)
if assoc.is_established:
# Send a DIMSE C-ECHO request to the peer
status = assoc.send_c_echo()
# Print the response from the peer
if status:
print('C-ECHO Response: 0x{0:04x}'.format(status.Status))
# Release the association
assoc.release()
```
### Echo SCP
在端口 ``11112`` 上创建一个阻塞式的 Echo SCP(如果你想返回 ``0x0000`` 的 *Success* 状态以外的内容,你可以选择将一个 handler 绑定到
``evt.EVT_C_ECHO`` 事件):
.. code-block:: python
```
from pynetdicom import AE, VerificationPresentationContexts
ae = AE(ae_title='MY_ECHO_SCP')
# Or we can use the inbuilt VerificationPresentationContexts list,
# there's one for each of the supported Service Classes
# In this case, we are supporting any requests to use Verification SOP
# Class in the association
ae.supported_contexts = VerificationPresentationContexts
# Start the SCP on (host, port) in blocking mode
ae.start_server(("localhost", 11112), block=True)
```
或者,你可以以非阻塞模式启动 SCP,这会返回正在运行的服务器实例。当你在同一个 AE 中运行 Storage SCP 并发起 C-MOVE 请求时,这会非常有用。
在下一个示例中,我们将创建一个非阻塞的 Verification SCP,并为 C-ECHO 服务请求事件 ``evt.EVT_C_ECHO`` 绑定一个 handler,用于记录请求者的地址、端口号以及事件的时间戳。
.. code-block:: python
```
import logging
from pynetdicom import AE, evt, debug_logger
from pynetdicom.sop_class import Verification
# Setup logging to use the StreamHandler at the debug level
debug_logger()
ae = AE(ae_title='MY_ECHO_SCP')
ae.add_supported_context(Verification)
# Implement the EVT_C_ECHO handler
def handle_echo(event, logger):
"""Handle a C-ECHO service request.
Parameters
----------
event : evt.Event
The C-ECHO service request event, this parameter is always
present.
logger : logging.Logger
The logger to use, this parameter is only present because we
bound ``evt.EVT_C_ECHO`` using a 3-tuple.
Returns
-------
int or pydicom.dataset.Dataset
The status returned to the peer AE in the C-ECHO response.
Must be a valid C-ECHO status value as either an ``int`` or a
``Dataset`` object containing an (0000,0900) *Status* element.
"""
# Every *Event* includes `assoc` and `timestamp` attributes
# which are the *Association* instance the event occurred in
# and the *datetime.datetime* the event occurred at
requestor = event.assoc.requestor
timestamp = event.timestamp.strftime("%Y-%m-%d %H:%M:%S")
msg = (
"Received C-ECHO service request from "
f"({requestor.address}, {requestor.port}) at {timestamp}"
)
logger.info(msg)
# Return a *Success* status
return 0x0000
# By binding using a 3-tuple we can pass extra arguments to
# the handler
handlers = [(evt.EVT_C_ECHO, handle_echo, [logging.getLogger('pynetdicom')])]
# Start the SCP in non-blocking mode
scp = ae.start_server(("localhost", 11112), block=False, evt_handlers=handlers)
# Associate and send a C-ECHO request to our own Verification SCP
ae.add_requested_context(Verification)
assoc = ae.associate('localhost', 11112)
if assoc.is_established:
status = assoc.send_c_echo()
assoc.release()
# Shutdown the SCP
scp.shutdown()
```
### Storage SCU
将 *file-in.dcm* 中的 DICOM *CT Image Storage* dataset 发送到对端的 Storage
SCP(位于 TCP/IP 地址 *addr*,监听端口 *port*):
.. code-block:: python
```
from pydicom import dcmread
from pydicom.uid import ImplicitVRLittleEndian
from pynetdicom import AE, VerificationPresentationContexts
from pynetdicom.sop_class import CTImageStorage, MRImageStorage
ae = AE(ae_title='MY_STORAGE_SCU')
# We can also do the same thing with the requested contexts
ae.requested_contexts = VerificationPresentationContexts
# Or we can use inbuilt objects like CTImageStorage.
# The requested presentation context's transfer syntaxes can also
# be specified using a str/UID or list of str/UIDs
ae.add_requested_context(
CTImageStorage,
transfer_syntax=ImplicitVRLittleEndian,
)
# Adding a presentation context with multiple transfer syntaxes
ae.add_requested_context(
MRImageStorage,
transfer_syntax=[ImplicitVRLittleEndian, '1.2.840.10008.1.2.1'],
)
assoc = ae.associate(addr, port)
if assoc.is_established:
dataset = dcmread('file-in.dcm')
# `status` is the response from the peer to the store request
# but may be an empty pydicom Dataset if the peer timed out or
# sent an invalid dataset.
status = assoc.send_c_store(dataset)
assoc.release()
```
标签:DICOM, pynetdicom, Python, 内核驱动, 医疗影像, 无后门, 网络协议, 逆向工具