Skip to content
A Python 2.7/3+ implementation of the DICOM networking protocol
Branch: master
Clone or download
Pull request Compare This branch is 1 commit behind pydicom:master.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.circleci
.github
build_tools Update travis builds (pydicom#277) Jan 10, 2019
docs Fix server socket not closing properly (pydicom#383) Jul 22, 2019
pynetdicom Fix server socket not closing properly (pydicom#383) Jul 22, 2019
.codecov.yml
.gitignore Refactor ACSE (pydicom#238) Dec 27, 2018
.pylintrc [WIP] Add _globals.py, style updates (pydicom#263) Jan 2, 2019
.travis.yml Update travis builds (pydicom#277) Jan 10, 2019
CONTRIBUTING.md
LICENCE.txt Change package name (pydicom#250) Dec 28, 2018
MANIFEST.in
README.rst
asv.conf.json
requirements.txt
setup.cfg
setup.py

README.rst

https://travis-ci.org/pydicom/pynetdicom.svg?branch=master https://circleci.com/gh/pydicom/pynetdicom/tree/master.svg?style=shield https://badges.gitter.im/pydicom.png

pynetdicom

A Python implementation of the DICOM networking protocol, originally based on (legacy) pynetdicom.

Description

DICOM 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 (2.7/3.4+) package that implements the DICOM networking protocol. Working with pydicom, it allows the easy creation of DICOM Service Class Users (SCUs) and Service Class Providers (SCPs).

The main user class is AE, which is used to represent a DICOM Application Entity. Once an AE has been created you would typically either:

  • Start the application as an SCP by specifying the presentation contexts that you will support, then calling AE.start_server((host, port)) 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(host, port) method, which returns an Association thread.

Once the application is associated with a peer AE, DICOM data can be sent between them by utilising the DIMSE-C and -N services (see the DICOM Standard Part 7, Sections 7.5, 9, and 10).

Supported Service Classes

pynetdicom supports the following DICOM service classes:

Supported DIMSE 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 (provided the peer supports the Service Class and a corresponding Presentation Context has been accepted):

DIMSE service Association method
C-ECHO send_c_echo()
C-FIND send_c_find(dataset, query_model)
C-GET send_c_get(dataset, query_model)
C-MOVE send_c_move(dataset, move_aet, query_model)
C-STORE send_c_store(dataset)
N-ACTION send_n_action(dataset, action_type, class_uid, instance_uid)
N-CREATE send_n_create(dataset, class_uid, instance_uid)
N-DELETE send_n_delete(class_uid, instance_uid)
N-EVENT-REPORT send_n_event_report(dataset, event_type, class_uid, instance_uid)
N-GET send_n_get(identifier_list, class_uid, instance_uid)
N-SET send_n_set(dataset, class_uid, instance_uid)

Where dataset is a pydicom Dataset object, query_model is a UID string, identifier_list is a list of pydicom Tag objects, event_type and action_type are ints and class_uid and instance_uid are UID strings. See the Association documentation for more information.

Supported DIMSE 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:

DIMSE service Intervention Event
C-ECHO evt.EVT_C_ECHO
C-FIND evt.EVT_C_FIND
C-GET evt.EVT_C_GET
C-MOVE evt.EVT_C_MOVE
C-STORE evt.EVT_C_STORE
N-ACTION evt.EVT_N_ACTION
N-CREATE evt.EVT_N_CREATE
N-DELETE evt.EVT_N_DELETE
N-EVENT-REPORT evt.EVT_N_EVENT_REPORT
N-GET evt.EVT_N_GET
N-SET evt.EVT_N_SET

With the exception of the C-ECHO service, a user-defined callable function, handler, must be bound to the corresponding intervention event 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() and AE.associate().

When an event occurs the handler function is called and passed a single parameter, event, which is an evt.Event 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 for information on what attributes and properties are available in Event for each event type and the expected returns/yields for the corresponding handlers.

Documentation

The pynetdicom user guide, code examples and API reference documentation is available for the current release as well as the development version.

Installation

Dependencies

pydicom

Installing current release

$ pip install pynetdicom

Installing development version

$ pip install git+git://github.com/pydicom/pynetdicom.git

Examples

Send a DICOM C-ECHO to a peer Verification SCP (at TCP/IP address addr, listen port number port):

from pynetdicom import AE

ae = AE(ae_title=b'MY_ECHO_SCU')
# Verification SOP Class has a UID of 1.2.840.10008.1.1
#   we can use the UID string directly when requesting the presentation
#   contexts we want to use in the association
ae.add_requested_context('1.2.840.10008.1.1')

# Associate with a peer DICOM AE
assoc = ae.associate(addr, port)

if assoc.is_established:
    # Send a DIMSE C-ECHO request to the peer
    # `status` is a pydicom Dataset object with (at a minimum) a
    #   (0000,0900) Status element
    # If the peer hasn't accepted the requested context then this
    #   will raise a RuntimeError exception
    status = assoc.send_c_echo()

    # Output the response from the peer
    if status:
        print('C-ECHO Response: 0x{0:04x}'.format(status.Status))

    # Release the association
    assoc.release()

Create a blocking DICOM C-ECHO listen 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 a Success status):

from pynetdicom import AE, VerificationPresentationContexts

ae = AE(ae_title=b'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(('', 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.

import logging

from pynetdicom import AE, evt, VerificationPresentationContexts, debug_logger

# Setup logging to use the StreamHandler at the debug level
debug_logger()
LOGGER = logging.getLogger('pynetdicom')

ae = AE(ae_title=b'MY_ECHO_SCP')
ae.supported_contexts = VerificationPresentationContexts

# Implement the EVT_C_ECHO handler
def handle_echo(event):
    """Handle a C-ECHO service request.

    Parameters
    ----------
    event : evt.Event
        The C-ECHO service request event.

    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 ({}, {}) at {}"
        .format(requestor.address, requestor.port, timestamp)
    )
    LOGGER.info(msg)

    # Return a *Success* status
    return 0x0000

handlers = [(evt.EVT_C_ECHO, handle_echo)]

# Start the SCP in non-blocking mode
scp = ae.start_server(('', 11112), block=False, evt_handlers=handlers)

# Send a C-ECHO request to our own Verification SCP
ae.add_requested_context('1.2.840.10008.1.1')
assoc = ae.associate('localhost', 11112)
if assoc.is_established:
    status = assoc.send_c_echo()
    assoc.release()

# Shutdown the SCP
scp.shutdown()

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):

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=b'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()
You can’t perform that action at this time.