Presentation Contexts¶
Introduction¶
Presentation Contexts are used in DICOM to fully define the content and the encoding of a piece of data (typically a DICOM dataset). They consist of three main parts; a Context ID, an Abstract Syntax and one or more Transfer Syntaxes.
The Context ID is an odd-integer between 1 and 255 (inclusive) and identifies the context. With pynetdicom this is not something you usually have to worry about.
The Abstract Syntax defines what the data represents, usually identified by a DICOM SOP Class UID (however private abstract syntaxes are also allowed) such as:
1.2.840.10008.1.1
- Verification SOP Class1.2.840.10008.5.1.4.1.1
- CT Image Storage
The Transfer Syntax defines how the data is encoded, usually identified by a DICOM Transfer Syntax UID (however private transfer syntaxes are also allowed) such as:
1.2.840.10008.1.2
- Implicit VR Little Endian1.2.840.10008.1.2.4.50
- JPEG Baseline
Representation in pynetdicom¶
In pynetdicom presentation contexts are represented using the
PresentationContext
class.
>>> from pynetdicom.presentation import PresentationContext
>>> cx = PresentationContext()
>>> cx.context_id = 1
>>> cx.abstract_syntax = '1.2.840.10008.1.1'
>>> cx.transfer_syntax = ['1.2.840.10008.1.2', '1.2.840.10008.1.2.4.50']
>>> print(cx)
ID: 1
Abstract Syntax: Verification SOP Class
Transfer Syntax(es):
=Implicit VR Little Endian
=JPEG Baseline (Process 1)
However it’s easier to use the build_context()
convenience function which
takes the abstract syntax and optionally one or more transfer syntaxes and
returns a PresentationContext
instance:
>>> from pynetdicom import build_context
>>> from pynetdicom.sop_class import Verification
>>> Verification
'1.2.840.10008.1.1'
>>> cx = build_context(
... Verification, ['1.2.840.10008.1.2', '1.2.840.10008.1.2.4.50']
... )
...
>>> print(cx)
Abstract Syntax: Verification SOP Class
Transfer Syntax(es):
=Implicit VR Little Endian
=JPEG Baseline (Process 1)
>>> print(build_context('1.2.840.10008.1.1')) # Default transfer syntaxes
Abstract Syntax: Verification SOP Class
Transfer Syntax(es):
=Implicit VR Little Endian
=Explicit VR Little Endian
=Explicit VR Big Endian
If no transfer syntaxes are supplied then the default transfer syntaxes will be used instead.
Presentation Contexts and the Association Requestor¶
When acting as the association requestor (usually the SCU), you must propose presentation contexts to be negotiated by the association process. There are a couple of simple rules for these:
There must be at least 1 and up to 128 proposed presentation contexts.
You can use the same abstract syntax in more than one presentation context.
Each presentation context must have at least one transfer syntax.
In pynetdicom this is accomplished through one of the following methods:
Setting the
AE.requested_contexts
attribute directly using a list ofPresentationContext
items.from pynetdicom import AE, build_context from pynetdicom.sop_class import Verification ae = AE() ae.requested_contexts = [build_context(Verification)] assoc = ae.associate("127.0.0.1", 11112)
Using the
AE.add_requested_context()
method to add a newPresentationContext
to theAE.requested_contexts
attribute.from pynetdicom import AE from pynetdicom.sop_class import Verification ae = AE() ae.add_requested_context(Verification) assoc = ae.associate("127.0.0.1", 11112)
Supplying a list of
PresentationContext
items toAE.associate()
via the contexts keyword parameter.from pynetdicom import AE, build_context from pynetdicom.sop_class import Verification ae = AE() requested = [build_context(Verification)] assoc = ae.associate("127.0.0.1", 11112, contexts=requested)
The abstract syntaxes you propose should match the SOP Class or Meta SOP Class that corresponds to the service you wish to use. For example, if you’re intending to use the storage service then you’d propose one or more abstract syntaxes from the corresponding SOP Class UIDs.
The transfer syntaxes you propose for each abstract syntax should match the transfer syntax of the data you wish to send. For example, if you have a CT Image Storage dataset with a (0002,0010) Transfer Syntax UID value of 1.2.840.10008.1.2.4.50 (JPEG Baseline) then you won’t be able to send it unless you propose (and get accepted) a presentation context with a matching transfer syntax.
Note
Uncompressed and deflated transfer syntaxes are the exception to this rule as pydicom is able to freely convert between these (provided the endianness remains the same).
If you have data encoded in a variety of transfer syntaxes then you can propose multiple presentation contexts with the same abstract syntax but different transfer syntaxes:
>>> from pydicom.uid import ImplicitVRLittleEndian, JPEGBaseline
>>> from pynetdicom import AE
>>> from pynetdicom.sop_class import CTImageStorage
>>> ae = AE()
>>> ae.add_requested_context(CTImageStorage, ImplicitVRLittleEndian)
>>> ae.add_requested_context(CTImageStorage, JPEGBaseline)
>>> for cx in ae.requested_contexts:
... print(cx)
...
Abstract Syntax: CT Image Storage
Transfer Syntax(es):
=Implicit VR Little Endian
Abstract Syntax: CT Image Storage
Transfer Syntax(es):
=JPEG Baseline (Process 1)
Provided both contexts get accepted then it becomes possible to transfer CT
Image datasets encoded in JPEG Baseline and/or Implicit VR Little Endian.
Alternatively it may be necessary to
decompress()
datasets prior to sending (as
Implicit VR Little Endian should always be accepted).
Presentation Contexts and the Association Acceptor¶
When acting as the association acceptor (usually the SCP), you should define which presentation contexts will be supported. Unlike the requestor you can define an unlimited number of supported presentation contexts.
In pynetdicom this is accomplished through one of the following methods:
Setting the
AE.supported_contexts
attribute directly using a list ofPresentationContext
items.from pynetdicom import AE, build_context from pynetdicom.sop_class import Verification ae = AE() ae.supported_contexts = [build_context(Verification)] ae.start_server(("127.0.0.1", 11112))
Using the
AE.add_supported_context()
method to add a newPresentationContext
to theAE.supported_contexts
attribute.from pynetdicom import AE from pynetdicom.sop_class import Verification ae = AE() ae.add_supported_context(Verification) ae.start_server(("127.0.0.1", 11112))
Supplying a list of
PresentationContext
items toAE.start_server()
via the contexts keyword parameterfrom pynetdicom import AE, build_context from pynetdicom.sop_class import Verification ae = AE() supported = [build_context(Verification)] ae.start_server(("127.0.0.1", 11112), contexts=supported)
The abstract syntaxes you support should correspond to the service classes that are being offered. For example, if you offer the Storage Service then you should support one or more of the Storage Service’s corresponding SOP Classes.
The transfer syntaxes for each abstract syntax should match the data encoding you support.
Note
In general, pynetdicom is able to support any transfer syntax when acting as an SCP.
Presentation Context Negotiation¶
Consider an acceptor that supports the following abstract syntax/transfer syntaxes:
Verification SOP Class
Implicit VR Little Endian
Explicit VR Little Endian
CT Image Storage
Implicit VR Little Endian
MR Image Storage
JPEG Baseline
And a requestor that proposes the following presentation contexts:
Context 1: Verification SOP Class
Implicit VR Little Endian
Explicit VR Little Endian
Explicit VR Big Endian
JPEG Baseline
Context 3: CT Image Storage
Implicit VR Little Endian
Explicit VR Little Endian
Explicit VR Big Endian
Context 5: MR Image Storage
Implicit VR Little Endian
Explicit VR Little Endian
Context 7: CR Image Storage
Implicit VR Little Endian
Explicit VR Little Endian
Then the outcome of the presentation context negotiation will be:
Context 1: Accepted (with the acceptor choosing either Implicit or Explicit VR Little Endian to use as the transfer syntax)
Context 3: Accepted with Implicit VR Little Endian transfer syntax
Context 5: Rejected (transfer syntax not supported) because the acceptor and requestor have no matching transfer syntax for the context.
Context 7: Rejected (abstract syntax not supported) because the acceptor doesn’t support the CR Image Storage abstract syntax.
Contexts 1 and 3 have been accepted and can be used for sending data while 5 and 7 have been rejected and are not available.
Implementation Note¶
When acting as an acceptor, pynetdicom will choose the first matching
transfer syntax in PresentationContext.transfer_syntax
. For example, if
the requestor proposes the following:
Abstract Syntax: Verification SOP Class Transfer Syntax(es): =Implicit VR Little Endian =Explicit VR Little Endian =Explicit VR Big Endian
While the acceptor supports:
Abstract Syntax: Verification SOP Class Transfer Syntax(es): =Explicit VR Little Endian =Implicit VR Little Endian =Explicit VR Big Endian
Then the accepted transfer syntax will be Explicit VR Little Endian.
SCP/SCU Role Selection¶
The final wrinkle in presentation context negotiation is SCP/SCU Role Selection, which allows an association requestor to propose it’s role (SCU, SCP, or SCU and SCP) for each proposed abstract syntax. Role selection is used for services such as the Query/Retrieve Service’s C-GET requests, where the association acceptor sends data back to the requestor.
To propose SCP/SCU Role Selection as a requestor you should include
SCP_SCU_RoleSelectionNegotiation
items in the extended negotiation, either by creating them from scratch or
using the build_role()
convenience function:
from pynetdicom import AE, build_role
from pynetdicom.pdu_primitives import SCP_SCU_RoleSelectionNegotiation
from pynetdicom.sop_class import CTImageStorage, MRImageStorage
ae = AE()
ae.add_requested_context(CTImageStorage)
ae.add_requested_context(MRImageStorage)
role_a = SCP_SCU_RoleSelectionNegotiation()
role_a.sop_class_uid = CTImageStorage
role_a.scu_role = True
role_a.scp_role = True
role_b = build_role(MRImageStorage, scp_role=True)
assoc = ae.associate("127.0.0.1", 11112, ext_neg=[role_a, role_b])
When acting as the requestor you can set either or both of scu_role and
scp_role, with the non-specified role assumed to be False
.
To support SCP/SCU Role Selection as an acceptor you can use the scu_role
and scp_role keyword parameters in AE.add_supported_context()
:
from pynetdicom import AE
from pynetdicom.pdu_primitives import SCP_SCU_RoleSelectionNegotiation
from pynetdicom.sop_class import CTImageStorage
ae = AE()
ae.add_supported_context(CTImageStorage, scu_role=True, scp_role=False)
ae.start_server(("127.0.0.1", 11112))
When acting as the acceptor both scu_role and scp_role must be
specified. A value of True
indicates that the acceptor will accept the
proposed role. pynetdicom uses the following table to decide the outcome
of role selection negotiation:
Requestor |
Acceptor |
Outcome |
Notes |
|||
---|---|---|---|---|---|---|
scu_role |
scp_role |
scu_role |
scp_role |
Requestor |
Acceptor |
|
N/A |
N/A |
N/A |
N/A |
SCU |
SCP |
Default |
True |
True |
False |
False |
N/A |
N/A |
Rejected |
True |
SCP |
SCU |
||||
True |
False |
SCU |
SCP |
Default |
||
True |
SCU/SCP |
SCU/SCP |
||||
True |
False |
False |
False |
N/A |
N/A |
Rejected |
True |
SCU |
SCP |
Default |
|||
False |
True |
False |
False |
N/A |
N/A |
Rejected |
True |
SCP |
SCU |
||||
False |
False |
False |
False |
N/A |
N/A |
Rejected |
As can be seen there are four possible outcomes:
Requestor is SCU, acceptor is SCP (default roles)
Requestor is SCP, acceptor is SCU
Requestor and acceptor are both SCU/SCP
Requestor and acceptor are neither (context rejected)
Warning
Role selection negotiation is not very well defined by the DICOM Standard, so different implementations may not give the same outcomes.