Skip to content

Commit

Permalink
Fix selection of presentation context when using UPS (#512)
Browse files Browse the repository at this point in the history
  • Loading branch information
scaramallion committed Jul 4, 2020
1 parent 587025a commit d8cdeea
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 5 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ peer SCP, the following DIMSE-C and -N services are available:
+----------------+----------------------------------------------------------------------------------------+
| N-EVENT-REPORT | `Association.send_n_event_report(dataset, event_type, class_uid, instance_uid) <er_>`_ |
+----------------+----------------------------------------------------------------------------------------+
| N-GET | `Association.send_n_set(dataset, class_uid, instance_uid) <n_get_>`_ |
| 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_>`_ |
+----------------+----------------------------------------------------------------------------------------+
Expand Down
1 change: 1 addition & 0 deletions docs/changelog/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Release Notes
.. toctree::
:maxdepth: 1

v1.5.2
v1.5.1
v1.5.0
v1.4.1
Expand Down
2 changes: 1 addition & 1 deletion docs/changelog/v1.5.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
=====

Changes
.....
.......

* Switch *pydicom* dependency to >= 1.4.2 (:issue:`493`)
16 changes: 16 additions & 0 deletions docs/changelog/v1.5.2.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.. _v1.5.2:

1.5.2
=====

Changes
.......

* The ``Association.send_n_*()`` methods now allow the use of :class:`UPS Push
<pynetdicom.sop_class.UnifiedProcedureStepPushSOPClass>` as the
*Requested* or *Affected SOP Class* when :class:`UPS Pull
<pynetdicom.sop_class.UnifiedProcedureStepPullSOPClass>`, :class:`Watch
<pynetdicom.sop_class.UnifiedProcedureStepWatchSOPClass>`, :class:`Event
<pynetdicom.sop_class.UnifiedProcedureStepEventSOPClass>`, or :class:`Query
<pynetdicom.sop_class.UnifiedProcedureStepQuerySOPClass>` has been
accepted in a presentation context by the peer (:issue:`509`)
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ Applications
Release Notes
=============

* :doc:`v1.5.2 </changelog/v1.5.2>`
* :doc:`v1.5.1 </changelog/v1.5.1>`
* :doc:`v1.5.0 </changelog/v1.5.0>`
* :doc:`v1.4.1 </changelog/v1.4.1>`
Expand Down
1 change: 1 addition & 0 deletions docs/reference/sop_classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ Unified Procedure Step
UnifiedProcedureStepEventSOPClass
UnifiedProcedureStepPullSOPClass
UnifiedProcedureStepPushSOPClass
UnifiedProcedureStepQuerySOPClass
UnifiedProcedureStepWatchSOPClass

Verification
Expand Down
2 changes: 1 addition & 1 deletion pynetdicom/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import re


__version__ = '1.5.1'
__version__ = '1.5.2'


VERSION_PATTERN = r"""
Expand Down
61 changes: 60 additions & 1 deletion pynetdicom/association.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@
standard_dimse_recv_handler, standard_dimse_sent_handler,
standard_pdu_recv_handler, standard_pdu_sent_handler,
)
from pynetdicom.sop_class import uid_to_service_class, VerificationSOPClass
from pynetdicom.sop_class import (
uid_to_service_class, VerificationSOPClass,
UnifiedProcedureStepPullSOPClass, UnifiedProcedureStepPushSOPClass,
UnifiedProcedureStepWatchSOPClass, UnifiedProcedureStepEventSOPClass,
UnifiedProcedureStepQuerySOPClass
)
from pynetdicom.pdu_primitives import (
UserIdentityNegotiation,
MaximumLengthNotification,
Expand Down Expand Up @@ -366,6 +371,30 @@ def _get_valid_context(self, ab_syntax, tr_syntax, role=None,
possible_contexts = [
cx for cx in possible_contexts if ab_syntax == cx.abstract_syntax
]

# For UPS we can also match UPS Push to Pull/Watch/Event/Query
if (
ab_syntax == UnifiedProcedureStepPushSOPClass
and not possible_contexts
):
LOGGER.info(
"No exact matching context found for 'Unified Procedure Step "
"- Push SOP Class', checking accepted contexts for other UPS "
"SOP classes"
)
ups = [
UnifiedProcedureStepPullSOPClass,
UnifiedProcedureStepWatchSOPClass,
UnifiedProcedureStepEventSOPClass,
UnifiedProcedureStepQuerySOPClass
]
possible_contexts.extend(
[
cx for cx in self._accepted_cx.values()
if cx.abstract_syntax in ups
]
)

# Filter by role
if role == 'scu':
possible_contexts = [
Expand Down Expand Up @@ -1091,6 +1120,12 @@ def send_c_find(self, dataset, query_model, msg_id=1, priority=2):
# Determine the Presentation Context we are operating under
# and hence the transfer syntax to use for encoding `dataset`
context = self._get_valid_context(query_model, '', 'scu')
if context.abstract_syntax != query_model:
LOGGER.info("Using Presentation Context:")
LOGGER.info(" Context ID: {}".format(context.context_id))
LOGGER.info(
" Abstract Syntax: ={}".format(context.abstract_syntax.name)
)

# Build C-FIND request primitive
# (M) Message ID
Expand Down Expand Up @@ -2094,6 +2129,12 @@ def send_n_action(self, dataset, action_type, class_uid, instance_uid,
# Determine the Presentation Context we are operating under
# and hence the transfer syntax to use for encoding `dataset`
context = self._get_valid_context(meta_uid or class_uid, '', 'scu')
if class_uid and context.abstract_syntax != class_uid:
LOGGER.info("Using Presentation Context:")
LOGGER.info(" Context ID: {}".format(context.context_id))
LOGGER.info(
" Abstract Syntax: ={}".format(context.abstract_syntax.name)
)
transfer_syntax = context.transfer_syntax[0]

# Build N-ACTION request primitive
Expand Down Expand Up @@ -2652,6 +2693,12 @@ def send_n_event_report(self, dataset, event_type, class_uid,
# selection negotiation, so we need to ignore the negotiate role
# since the SCP will be sending requests to the SCU
context = self._get_valid_context(meta_uid or class_uid, '', None)
if class_uid and context.abstract_syntax != class_uid:
LOGGER.info("Using Presentation Context:")
LOGGER.info(" Context ID: {}".format(context.context_id))
LOGGER.info(
" Abstract Syntax: ={}".format(context.abstract_syntax.name)
)
transfer_syntax = context.transfer_syntax[0]

# Build N-EVENT-REPORT request primitive
Expand Down Expand Up @@ -2876,6 +2923,12 @@ def send_n_get(self, identifier_list, class_uid, instance_uid, msg_id=1,
# Determine the Presentation Context we are operating under
# and hence the transfer syntax to use for encoding `dataset`
context = self._get_valid_context(meta_uid or class_uid, '', 'scu')
if class_uid and context.abstract_syntax != class_uid:
LOGGER.info("Using Presentation Context:")
LOGGER.info(" Context ID: {}".format(context.context_id))
LOGGER.info(
" Abstract Syntax: ={}".format(context.abstract_syntax.name)
)
transfer_syntax = context.transfer_syntax[0]

# Build N-GET request primitive
Expand Down Expand Up @@ -3105,6 +3158,12 @@ def send_n_set(self, dataset, class_uid, instance_uid, msg_id=1,
# Determine the Presentation Context we are operating under
# and hence the transfer syntax to use for encoding `dataset`
context = self._get_valid_context(meta_uid or class_uid, '', 'scu')
if class_uid and context.abstract_syntax != class_uid:
LOGGER.info("Using Presentation Context:")
LOGGER.info(" Context ID: {}".format(context.context_id))
LOGGER.info(
" Abstract Syntax: ={}".format(context.abstract_syntax.name)
)
transfer_syntax = context.transfer_syntax[0]

# Build N-SET request primitive
Expand Down
162 changes: 161 additions & 1 deletion pynetdicom/tests/test_assoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@
PatientRootQueryRetrieveInformationModelMove,
PatientStudyOnlyQueryRetrieveInformationModelMove,
StudyRootQueryRetrieveInformationModelMove,
SecondaryCaptureImageStorage
SecondaryCaptureImageStorage,
UnifiedProcedureStepPullSOPClass,
UnifiedProcedureStepPushSOPClass,
UnifiedProcedureStepWatchSOPClass
)
from .dummy_c_scp import (
DummyVerificationSCP, DummyStorageSCP, DummyFindSCP, DummyGetSCP,
Expand Down Expand Up @@ -3838,12 +3841,16 @@ class TestGetValidContext(object):
def setup(self):
"""Run prior to each test"""
self.scp = None
self.ae = None

def teardown(self):
"""Clear any active threads"""
if self.scp:
self.scp.abort()

if self.ae:
self.ae.shutdown()

time.sleep(0.1)

for thread in threading.enumerate():
Expand Down Expand Up @@ -4362,6 +4369,159 @@ def test_little_big(self):

self.scp.abort()

def test_ups_push_action(self, caplog):
"""Test matching UPS Push to other UPS contexts."""
def handle(event, cx):
cx.append(event.context)
return 0x0000, None

self.ae = ae = AE()
ae.network_timeout = 5
ae.dimse_timeout = 5
ae.acse_timeout = 5
ae.add_supported_context(UnifiedProcedureStepPullSOPClass)

contexts = []
handlers = [(evt.EVT_N_ACTION, handle, [contexts])]
scp = ae.start_server(('', 11112), block=False, evt_handlers=handlers)

ae.add_requested_context(UnifiedProcedureStepPullSOPClass)
assoc = ae.associate('localhost', 11112)
assert assoc.is_established

msg = (
r"No exact matching context found for 'Unified Procedure Step "
r"- Push SOP Class', checking accepted contexts for other UPS "
r"SOP classes"
)
ds = Dataset()
ds.TransactionUID = '1.2.3.4'
with caplog.at_level(logging.DEBUG, logger='pynetdicom'):
status, rsp = assoc.send_n_action(
ds, 1, UnifiedProcedureStepPushSOPClass, '1.2.3'
)
assert msg in caplog.text

assoc.release()
assert contexts[0].abstract_syntax == UnifiedProcedureStepPullSOPClass
scp.shutdown()

def test_ups_push_get(self, caplog):
"""Test matching UPS Push to other UPS contexts."""
self.ae = ae = AE()
ae.network_timeout = 5
ae.dimse_timeout = 5
ae.acse_timeout = 5
ae.add_supported_context(UnifiedProcedureStepPullSOPClass)

scp = ae.start_server(('', 11112), block=False)

ae.add_requested_context(UnifiedProcedureStepPullSOPClass)
assoc = ae.associate('localhost', 11112)
assert assoc.is_established

msg = (
r"No exact matching context found for 'Unified Procedure Step "
r"- Push SOP Class', checking accepted contexts for other UPS "
r"SOP classes"
)
with caplog.at_level(logging.DEBUG, logger='pynetdicom'):
status, rsp = assoc.send_n_get(
[0x00100010], UnifiedProcedureStepPushSOPClass, '1.2.3'
)
assert msg in caplog.text

assoc.release()
scp.shutdown()

def test_ups_push_set(self, caplog):
"""Test matching UPS Push to other UPS contexts."""
self.ae = ae = AE()
ae.network_timeout = 5
ae.dimse_timeout = 5
ae.acse_timeout = 5
ae.add_supported_context(UnifiedProcedureStepPullSOPClass)

scp = ae.start_server(('', 11112), block=False)

ae.add_requested_context(UnifiedProcedureStepPullSOPClass)
assoc = ae.associate('localhost', 11112)
assert assoc.is_established

msg = (
r"No exact matching context found for 'Unified Procedure Step "
r"- Push SOP Class', checking accepted contexts for other UPS "
r"SOP classes"
)
ds = Dataset()
ds.TransactionUID = '1.2.3.4'
with caplog.at_level(logging.DEBUG, logger='pynetdicom'):
status, rsp = assoc.send_n_set(
ds, UnifiedProcedureStepPushSOPClass, '1.2.3'
)
assert msg in caplog.text

assoc.release()
scp.shutdown()

def test_ups_push_er(self, caplog):
"""Test matching UPS Push to other UPS contexts."""
self.ae = ae = AE()
ae.network_timeout = 5
ae.dimse_timeout = 5
ae.acse_timeout = 5
ae.add_supported_context(UnifiedProcedureStepPullSOPClass)

scp = ae.start_server(('', 11112), block=False)

ae.add_requested_context(UnifiedProcedureStepPullSOPClass)
assoc = ae.associate('localhost', 11112)
assert assoc.is_established

msg = (
r"No exact matching context found for 'Unified Procedure Step "
r"- Push SOP Class', checking accepted contexts for other UPS "
r"SOP classes"
)
ds = Dataset()
ds.TransactionUID = '1.2.3.4'
with caplog.at_level(logging.DEBUG, logger='pynetdicom'):
status, rsp = assoc.send_n_event_report(
ds, 1, UnifiedProcedureStepPushSOPClass, '1.2.3'
)
assert msg in caplog.text

assoc.release()
scp.shutdown()

def test_ups_push_find(self, caplog):
"""Test matching UPS Push to other UPS contexts."""
self.ae = ae = AE()
ae.network_timeout = 5
ae.dimse_timeout = 5
ae.acse_timeout = 5
ae.add_supported_context(UnifiedProcedureStepPullSOPClass)

scp = ae.start_server(('', 11112), block=False)

ae.add_requested_context(UnifiedProcedureStepPullSOPClass)
assoc = ae.associate('localhost', 11112)
assert assoc.is_established

msg = (
r"No exact matching context found for 'Unified Procedure Step "
r"- Push SOP Class', checking accepted contexts for other UPS "
r"SOP classes"
)
ds = Dataset()
ds.TransactionUID = '1.2.3.4'
with caplog.at_level(logging.DEBUG, logger='pynetdicom'):
responses = assoc.send_c_find(ds, UnifiedProcedureStepPushSOPClass)
assert msg in caplog.text

assoc.release()
scp.shutdown()


class TestEventHandlingAcceptor(object):
"""Test the transport events and handling as acceptor."""
Expand Down

0 comments on commit d8cdeea

Please sign in to comment.