About Pynetdicom
Pynetdicom is an MCP server in the Healthcare category: a Python implementation of the DICOM networking protocol.
Install
git clone https://github.com/pydicom/pynetdicomThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about Pynetdicom
Powered by Claude · Grounded in docs
Security checks
- ✓README presentThe repository ships documentation.
5 checks have not been run yet.
README
|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
A Python implementation of the DICOM <https://www.dicomstandard.org>_ networking
protocol, originally based on (legacy)
pynetdicom <https://github.com/patmun/pynetdicom_legacy>_.
Description
DICOM <https://www.dicomstandard.org>_ is the international standard for medical
images and related information. It defines the formats and communication protocols for
media exchange in radiology, cardiology, radiotherapy and other medical domains.
pynetdicom is a pure Python package that implements the DICOM networking protocol.
Working with pydicom <https://github.com/pydicom/pydicom>_, it allows the easy
creation of DICOM Service Class Users (SCUs) and Service Class Providers (SCPs).
pynetdicom's main user class is
AE <https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.ae.ApplicationEntity.html>_
and is used to represent a DICOM Application Entity. With it you can:
- Start the application as an SCP by specifying the supported presentation contexts
then calling
AE.start_server() <https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.ae.ApplicationEntity.html#pynetdicom.ae.ApplicationEntity.start_server>_ and waiting for incoming association requests - Use the application as an SCU by specifying the presentation contexts you want the
peer SCP to support, then requesting an association via the
AE.associate() <https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.ae.ApplicationEntity.html#pynetdicom.ae.ApplicationEntity.associate>_ method, which returns anAssociation <https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.association.Association.html#pynetdicom.association.Association>_ thread.
Once associated, the services available to the association can be used by sending
DIMSE-C <https://dicom.nema.org/medical/dicom/current/output/html/part07.html#chapter_9>_
and
DIMSE-N <https://dicom.nema.org/medical/dicom/current/output/html/part07.html#chapter_10>_
messages.
Documentation
The pynetdicom
tutorials <https://pydicom.github.io/pynetdicom/stable/tutorials/index.html>,
user guide <https://pydicom.github.io/pynetdicom/stable/user/index.html>,
code examples <https://pydicom.github.io/pynetdicom/stable/examples/index.html>,
application <https://pydicom.github.io/pynetdicom/stable/apps/index.html> and
API reference <https://pydicom.github.io/pynetdicom/stable/reference/index.html>_
documentation is available for the
current release <https://pydicom.github.io/pynetdicom/>_ as well as the
development version <https://pydicom.github.io/pynetdicom/dev>_.
Installation
Dependencies
`pydicom <https://github.com/pydicom/pydicom>`_
Installing current release
Using pip:
.. code-block:: sh
pip install -U pynetdicom
Using conda:
.. code-block:: sh
conda install -c conda-forge pynetdicom
For more detailed instructions, including how to install the current development
version, please see the installation guide <https://pydicom.github.io/pynetdicom/stable/tutorials/installation.html>_.
Supported DIMSE Services
SCU Services
When the AE is acting as an SCU and an association has been established with a peer
SCP, the following DIMSE-C and -N services are available:
.. _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 service | `Association <assoc_>`_ method |
+================+========================================================================================+
| C-ECHO | `Association.send_c_echo() <echo_>`_ |
+----------------+----------------------------------------------------------------------------------------+
| C-FIND | `Association.send_c_find(dataset, query_model) <find_>`_ |
+----------------+----------------------------------------------------------------------------------------+
| C-GET | `Association.send_c_get(dataset, query_model) <c_get_>`_ |
+----------------+----------------------------------------------------------------------------------------+
| C-MOVE | `Association.send_c_move(dataset, move_aet, query_model) <move_>`_ |
+----------------+----------------------------------------------------------------------------------------+
| C-STORE | `Association.send_c_store(dataset) <store_>`_ |
+----------------+----------------------------------------------------------------------------------------+
| N-ACTION | `Association.send_n_action(dataset, action_type, class_uid, instance_uid) <action_>`_ |
+----------------+----------------------------------------------------------------------------------------+
| N-CREATE | `Association.send_n_create(dataset, class_uid, instance_uid) <create_>`_ |
+----------------+----------------------------------------------------------------------------------------+
| N-DELETE | `Association.send_n_delete(class_uid, instance_uid) <delete_>`_ |
+----------------+----------------------------------------------------------------------------------------+
| N-EVENT-REPORT | `Association.send_n_event_report(dataset, event_type, class_uid, instance_uid) <er_>`_ |
+----------------+----------------------------------------------------------------------------------------+
| N-GET | `Association.send_n_get(identifier_list, class_uid, instance_uid) <n_get_>`_ |
+----------------+----------------------------------------------------------------------------------------+
| N-SET | `Association.send_n_set(dataset, class_uid, instance_uid) <set_>`_ |
+----------------+----------------------------------------------------------------------------------------+
Where *dataset* is a pydicom
`Dataset <https://pydicom.github.io/pydicom/stable/ref_guide.html#dataset>`_
object, *query_model* is a UID string, *identifier_list* is a list of pydicom
`Tag <https://pydicom.github.io/pydicom/stable/api_ref.html#pydicom.tag.Tag>`_
objects, *event_type* and *action_type* are ints and *class_uid* and
*instance_uid* are UID strings. See the
`Association documentation <https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.association.Association.html>`_
for more information.
SCP Services
When the AE is acting as an SCP the following DIMSE-C and -N services are available to the peer once an association has been established:
.. _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 service | Intervention Event | Handler documentation |
+================+============================+=================================+
| C-ECHO | evt.EVT_C_ECHO | Handle C-ECHO <hecho_>_ |
+----------------+----------------------------+---------------------------------+
| C-FIND | evt.EVT_C_FIND | Handle C-FIND <hfind_>_ |
+----------------+----------------------------+---------------------------------+
| C-GET | evt.EVT_C_GET | Handle C-GET <hc_get_>_ |
+----------------+----------------------------+---------------------------------+
| C-MOVE | evt.EVT_C_MOVE | Handle C-MOVE <hmove_>_ |
+----------------+----------------------------+---------------------------------+
| C-STORE | evt.EVT_C_STORE | Handle C-STORE <hstore_>_ |
+----------------+----------------------------+---------------------------------+
| N-ACTION | evt.EVT_N_ACTION | Handle N-ACTION <haction_>_ |
+----------------+----------------------------+---------------------------------+
| N-CREATE | evt.EVT_N_CREATE | Handle N-CREATE <hcreate_>_ |
+----------------+----------------------------+---------------------------------+
| N-DELETE | evt.EVT_N_DELETE | Handle N-DELETE <hdelete_>_ |
+----------------+----------------------------+---------------------------------+
| N-EVENT-REPORT | evt.EVT_N_EVENT_REPORT | Handle N-EVENT-REPORT <her_>_ |
+----------------+----------------------------+---------------------------------+
| N-GET | evt.EVT_N_GET | Handle N-GET <hn_get_>_ |
+----------------+----------------------------+---------------------------------+
| N-SET | evt.EVT_N_SET | Handle N-SET <hset_>_ |
+----------------+----------------------------+---------------------------------+
With the exception of the C-ECHO service, a user-defined callable function, handler,
must be bound to the corresponding
intervention event <https://pydicom.github.io/pynetdicom/stable/user/events#intervention-events>_
in order to complete a DIMSE service request. Events can be imported with
from pynetdicom import evt and a handler can be bound to an event prior to starting
an association through the evt_handlers keyword arguments in
AE.start_server() <https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.ae.ApplicationEntity.html#pynetdicom.ae.ApplicationEntity.start_server>_
and
AE.associate() <https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.ae.ApplicationEntity.html#pynetdicom.ae.ApplicationEntity.associate>_.
When an event occurs the handler function is called and passed a single parameter,
event, which is an
Event <https://pydicom.github.io/pynetdicom/stable/reference/generated/pynetdicom.events.Event.html>_
object whose specific attributes are dependent on the type of event that occurred.
Handlers bound to intervention events must return or yield certain values. See the
handler documentation <https://pydicom.github.io/pynetdicom/stable/reference/events>_
for information on what attributes and properties are available in Event for each
event type and the expected returns/yields for the corresponding handlers.
Applications
Some basic DICOM applications are included with pynetdicom:
echoscp <https://pydicom.github.io/pynetdicom/stable/apps/echoscp.html>_echoscu <https://pydicom.github.io/pynetdicom/stable/apps/echoscu.html>_findscu <https://pydicom.github.io/pynetdicom/stable/apps/findscu.html>_getscu <https://pydicom.github.io/pynetdicom/stable/apps/getscu.html>_qrscp <https://pydicom.github.io/pynetdicom/stable/apps/qrscp.html>_ (requiressqlalchemy <https://www.sqlalchemy.org/>_)movescu <https://pydicom.github.io/pynetdicom/stable/apps/movescu.html>_storescp <https://pydicom.github.io/pynetdicom/stable/apps/storescp.html>_storescu <https://pydicom.github.io/pynetdicom/stable/apps/storescu.html>_
Code Examples
More
code examples <https://pydicom.github.io/pynetdicom/stable/examples/index.html>_
are available in the documentation.
Echo SCU
Send a C-ECHO request to a Verification SCP (at TCP/IP address *addr*, listen port
number *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
Create a blocking Echo SCP on port 11112 (you may optionally bind a handler to the
evt.EVT_C_ECHO event if you want to return something other than an 0x0000
Success status):
.. 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)
Alternatively, you can start the SCP in non-blocking mode, which returns the running server instance. This can be useful when you want to run a Storage SCP and make C-MOVE requests within the same AE.
In the next example we'll create a non-blocking Verification SCP and bind a
handler for the C-ECHO service request event evt.EVT_C_ECHO that logs the
requestor's address and port number and the timestamp for the event.
.. 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
Send the DICOM *CT Image Storage* dataset in *file-in.dcm* to a peer Storage
SCP (at TCP/IP address *addr*, listen port number *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()
README copy captured when this server was indexed; it may lag the repository. The original is authoritative.