Skip to content

SCP presentation context negotiation ignores requestor's transfer syntax preference order #1085

Description

@julianmarshall911

Describe the bug
When acting as an Association Acceptor (SCP), negotiate_as_acceptor() in presentation.py selects the accepted Transfer Syntax by iterating the acceptor's supported TS list and picking the first match found in the requestor's proposed list. This ignores the requestor's preference order.

The DICOM convention (and the behavior of DCMTK, the reference DICOM implementation) is to iterate the requestor's proposed TS list and pick the first one the acceptor supports. The requestor lists transfer syntaxes in preference order, and the acceptor should respect that.

This causes real-world interoperability failures. For example, when a sender has JPEG-LS Lossless compressed mammography files and proposes [JPEG-LS Lossless, Explicit VR LE, Implicit VR LE], the SCP accepts Explicit VR LE (because it appears first in the SCP's own supported list). The sender then cannot use the accepted context because it would need to decompress on the fly, which most DICOM implementations cannot do. The sender aborts the association.

Expected behavior
The SCP should accept 1.2.840.10008.1.2.4.80 (JPEG-LS Lossless) because it is the requestor's first-preference transfer syntax and the SCP supports it. This is how DCMTK's storescp behaves. Actual behavior? The SCP accepts 1.2.840.10008.1.2 (Implicit VR LE) because it appears first in the SCP's own supported transfer syntax list, regardless of the requestor's preference.

Steps To Reproduce

from pynetdicom import AE, evt
from pynetdicom.sop_class import Verification

sop = '1.2.840.10008.5.1.4.1.1.1.2'  # Digital Mammography
jpeg_ls = '1.2.840.10008.1.2.4.80'    # JPEG-LS Lossless
implicit = '1.2.840.10008.1.2'         # Implicit VR LE

# SCP: supports both, but lists implicit first
scp_ae = AE()
scp_ae.add_supported_context(sop, [implicit, jpeg_ls])
scp_ae.add_supported_context(Verification)
server = scp_ae.start_server(('', 11199), block=False)

# SCU: proposes JPEG-LS first (sender's preference)
scu_ae = AE()
scu_ae.add_requested_context(sop, [jpeg_ls, implicit])
scu_ae.add_requested_context(Verification)
assoc = scu_ae.associate('127.0.0.1', 11199)

if assoc.is_established:
    for cx in assoc.accepted_contexts:
        if str(cx.abstract_syntax) == sop:
            print('Accepted TS:', cx.transfer_syntax)
            # Prints: ['1.2.840.10008.1.2'] (Implicit VR LE)
            # Expected: ['1.2.840.10008.1.2.4.80'] (JPEG-LS Lossless)
    assoc.release()

server.shutdown()

**Your environment**
- pynetdicom version: 2.0.2
- Python version: 3.13
- OS: Debian Linux (also reproduced on macOS with Python 3.9)

** Fix**
In `pynetdicom/presentation.py`, function `negotiate_as_acceptor`:

**Current code** (iterates acceptor's list):
```python
for tr_syntax in ac_context.transfer_syntax:
    if tr_syntax in rq_context.transfer_syntax:
        context.transfer_syntax = [tr_syntax]
        context.result = 0x00
        result_contexts.append(context)
        break

Proposed fix (iterates requestor's list):

for tr_syntax in rq_context.transfer_syntax:
    if tr_syntax in ac_context.transfer_syntax:
        context.transfer_syntax = [tr_syntax]
        context.result = 0x00
        result_contexts.append(context)
        break

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions