pydicom / pynetdicom

A Python implementation of the DICOM networking protocol
https://pydicom.github.io/pynetdicom
MIT License
510 stars 180 forks source link

send_c_store w/ JPEG 2000 (Lossless) 1.2.840.10008.1.2.4.90 #704

Closed vpapaioannou closed 2 years ago

vpapaioannou commented 2 years ago

For pynetdicom 1.5.7,

Is JPEG 2000 (Lossless) 1.2.840.10008.1.2.4.90 supported by send_c_store? Although I can upload/read my image/dicom file on Orthanc, Horos and Weasis, I cannot upload/store the image using pynetdicom.

From https://github.com/pydicom/pynetdicom/blob/master/pynetdicom/dimse_messages.py#L202 I see that only Little Endian Implicit VR datasets are supported. Is this correct?

From my investigation, I can tell that in line https://github.com/pydicom/pynetdicom/blob/master/pynetdicom/association.py#L1886 loading fails w/ rsp to be None and it's not a timeout issue.

Thanks.

scaramallion commented 2 years ago

It's supported, but the default presentation contexts don't use it. You have to add it yourself using whichever of the various methods for defining presentation contexts you prefer.

from pynetdicom import AE, StoragePresentationContexts
from pynetdicom.sop_class import CTImageStorage
from pydicom.uid import JPEG2000

ae = AE()
# Add specific contexts
ae.add_requested_context(CTImageStorage, JPEG2000)

# Or update all the defaults
for cx in StoragePresentationContexts:
    cx.add_transfer_syntax(JPEG2000)
ae.requested_contexts = StoragePresentationContexts

See also the API reference for AE.add_requested_context()

A DIMSE Message's Command Set is always Implicit Little, but the Command Set is used strictly for exchanging messages within an association. The actual datasets sent during C-STORE should have a negotiated transfer syntax that matches their encoding.

vpapaioannou commented 2 years ago

Thanks, and doing as suggested, i.e.

image

I get,

ValueError: No presentation context for 'MR Image Storage' has been accepted by the peer with 'JPEG 2000 Image Compression (Lossless Only)' transfer syntax for the SCU role

which comes from _get_valid_context.

Does that mean that I need to do something in SCP side?

debug_logger() shows the message at the very end.

I tried also to decompress the dicom file and build a context as ae.requested_contexts=[build_context(ds.SOPClassUID, ds.file_meta.TransferSyntaxUID)] which falls back to the previous situation where the status is empty.

Show log ``` I: Requesting Association D: Request Parameters: D: ======================= OUTGOING A-ASSOCIATE-RQ PDU ======================== D: Our Implementation Class UID: 1.2.826.0.1.3680043.9.3811.1.5.7 D: Our Implementation Version Name: PYNETDICOM_157 D: Application Context Name: 1.2.840.10008.3.1.1.1 D: Calling Application Name: PYNETDICOM D: Called Application Name: ANY-SCP D: Our Max PDU Receive Size: 16382 D: Presentation Context: D: Context ID: 1 (Proposed) D: Abstract Syntax: =Study Root Query/Retrieve Information Model - FIND D: Proposed SCP/SCU Role: Default D: Proposed Transfer Syntaxes: D: =Implicit VR Little Endian D: =Explicit VR Little Endian D: =Deflated Explicit VR Little Endian D: =Explicit VR Big Endian D: Requested Extended Negotiation: None D: Requested Common Extended Negotiation: None D: Requested Asynchronous Operations Window Negotiation: None D: Requested User Identity Negotiation: None D: ========================== END A-ASSOCIATE-RQ PDU ========================== D: Accept Parameters: D: ======================= INCOMING A-ASSOCIATE-AC PDU ======================== D: Their Implementation Class UID: 1.2.276.0.7230010.3.0.3.6.6 D: Their Implementation Version Name: OFFIS_DCMTK_366 D: Application Context Name: 1.2.840.10008.3.1.1.1 D: Calling Application Name: PYNETDICOM D: Called Application Name: ANY-SCP D: Their Max PDU Receive Size: 16384 D: Presentation Contexts: D: Context ID: 1 (Accepted) D: Abstract Syntax: =Study Root Query/Retrieve Information Model - FIND D: Accepted SCP/SCU Role: Default D: Accepted Transfer Syntax: =Explicit VR Little Endian D: Accepted Extended Negotiation: None D: Accepted Asynchronous Operations Window Negotiation: None D: User Identity Negotiation Response: None D: ========================== END A-ASSOCIATE-AC PDU ========================== I: Association Accepted I: Sending Find Request: MsgID 1 I: I: # Request Identifier I: (0008,0020) DA (no value available) # 0 StudyDate I: (0008,0030) TM (no value available) # 0 StudyTime I: (0008,0050) SH (no value available) # 0 AccessionNumber I: (0008,0052) CS [STUDY] # 1 QueryRetrieveLevel I: (0008,0060) CS (no value available) # 0 Modality I: (0008,0070) LO (no value available) # 0 Manufacturer I: (0008,1030) LO (no value available) # 0 StudyDescription I: (0008,1090) LO (no value available) # 0 ManufacturerModelName I: (0010,0010) PN (no value available) # 0 PatientName I: (0010,0020) LO (no value available) # 0 PatientID I: (0010,0030) DA (no value available) # 0 PatientBirthDate I: (0010,0040) CS (no value available) # 0 PatientSex I: (0010,1010) AS (no value available) # 0 PatientAge I: (0010,1020) DS (no value available) # 0 PatientSize I: (0010,1030) DS (no value available) # 0 PatientWeight I: (0018,1020) LO (no value available) # 0 SoftwareVersions I: (0018,1210) SH (no value available) # 0 ConvolutionKernel I: (0020,000D) UI (no value available) # 0 StudyInstanceUID I: (0020,0010) SH (no value available) # 0 StudyID I: <_io.BytesIO object at 0x7fded1941d00> D: ========================== OUTGOING DIMSE MESSAGE ========================== D: Message Type : C-FIND RQ D: Presentation Context ID : 1 D: Message ID : 1 D: Affected SOP Class UID : Study Root Query/Retrieve Information Model - FIND D: Identifier : Present D: Priority : Low D: ============================ END DIMSE MESSAGE ============================= D: pydicom.read_dataset() TransferSyntax="Little Endian Implicit" D: ========================== INCOMING DIMSE MESSAGE ========================== D: Message Type : C-FIND RSP D: Message ID Being Responded To : 1 D: Affected SOP Class UID : Study Root Query/Retrieve Information Model - FIND D: Identifier : None D: Status : 0x0000 D: ============================ END DIMSE MESSAGE ============================= D: I: Find SCP Result: 0x0000 (Success) I: Releasing Association D: Abort Parameters: D: =========================== INCOMING A-ABORT PDU =========================== D: Abort Source: DUL service-user D: Abort Reason: No reason given D: ============================= END A-ABORT PDU ============================== I: Association Aborted ```
vpapaioannou commented 2 years ago

Ignore my above comment, the file is JPEG2000Lossless and I used JPEG2000.

Doing some more investigation, depending on the method used two different outputs are seen.

1. If I do, ae.add_requested_context(MRImageStorage, JPEG2000Lossless) then there's no apparent error, but the output status is still empty. debug_logger output w/ no timeout set.

I: Requesting Association
D: Request Parameters:
D: ======================= OUTGOING A-ASSOCIATE-RQ PDU ========================
D: Our Implementation Class UID:      1.2.826.0.1.3680043.9.3811.1.5.7
D: Our Implementation Version Name:   PYNETDICOM_157
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    PYNETDICOM      
D: Called Application Name:     ANY-SCP         
D: Our Max PDU Receive Size:    16382
D: Presentation Context:
D:   Context ID:        1 (Proposed)
D:     Abstract Syntax: =MR Image Storage
D:     Proposed SCP/SCU Role: Default
D:     Proposed Transfer Syntax:
D:       =JPEG 2000 Image Compression (Lossless Only)
D: Requested Extended Negotiation: None
D: Requested Common Extended Negotiation: None
D: Requested Asynchronous Operations Window Negotiation: None
D: Requested User Identity Negotiation: None
D: ========================== END A-ASSOCIATE-RQ PDU ==========================
D: Accept Parameters:
D: ======================= INCOMING A-ASSOCIATE-AC PDU ========================
D: Their Implementation Class UID:    1.2.276.0.7230010.3.0.3.6.6
D: Their Implementation Version Name: OFFIS_DCMTK_366
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    PYNETDICOM      
D: Called Application Name:     ANY-SCP         
D: Their Max PDU Receive Size:  16384
D: Presentation Contexts:
D:   Context ID:        1 (Accepted)
D:     Abstract Syntax: =MR Image Storage
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =JPEG 2000 Image Compression (Lossless Only)
D: Accepted Extended Negotiation: None
D: Accepted Asynchronous Operations Window Negotiation: None
D: User Identity Negotiation Response: None
D: ========================== END A-ASSOCIATE-AC PDU ==========================
I: Association Accepted
I: Sending Store Request: MsgID 1, (MR)
D: ========================== OUTGOING DIMSE MESSAGE ==========================
D: Message Type                  : C-STORE RQ
D: Message ID                    : 1
D: Affected SOP Class UID        : MR Image Storage
D: Affected SOP Instance UID     : 1.3.12.2.1107.5.2.30.25123.2021120313231433429212156
D: Data Set                      : Present
D: Priority                      : Low
D: ============================ END DIMSE MESSAGE =============================
D: Abort Parameters:
D: =========================== INCOMING A-ABORT PDU ===========================
D: Abort Source: DUL service-user
D: Abort Reason: No reason given
D: ============================= END A-ABORT PDU ==============================
I: Releasing Association
I: Association Aborted

2. If I do,

for cx in StoragePresentationContexts:
   cx.add_transfer_syntax(JPEG2000Lossless)
ae.requested_contexts = StoragePresentationContexts

requested = [build_context(MRImageStorage)]
assoc = ae.associate(node.address, node.port, contexts=requested)

I get, ValueError: No presentation context for 'MR Image Storage' has been accepted by the peer with 'JPEG 2000 Image Compression (Lossless Only)' transfer syntax for the SCU role

I: Requesting Association
D: Request Parameters:
D: ======================= OUTGOING A-ASSOCIATE-RQ PDU ========================
D: Our Implementation Class UID:      1.2.826.0.1.3680043.9.3811.1.5.7
D: Our Implementation Version Name:   PYNETDICOM_157
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    PYNETDICOM      
D: Called Application Name:     ANY-SCP         
D: Our Max PDU Receive Size:    16382
D: Presentation Context:
D:   Context ID:        1 (Proposed)
D:     Abstract Syntax: =MR Image Storage
D:     Proposed SCP/SCU Role: Default
D:     Proposed Transfer Syntaxes:
D:       =Implicit VR Little Endian
D:       =Explicit VR Little Endian
D:       =Deflated Explicit VR Little Endian
D:       =Explicit VR Big Endian
D: Requested Extended Negotiation: None
D: Requested Common Extended Negotiation: None
D: Requested Asynchronous Operations Window Negotiation: None
D: Requested User Identity Negotiation: None
D: ========================== END A-ASSOCIATE-RQ PDU ==========================
D: Accept Parameters:
D: ======================= INCOMING A-ASSOCIATE-AC PDU ========================
D: Their Implementation Class UID:    1.2.276.0.7230010.3.0.3.6.6
D: Their Implementation Version Name: OFFIS_DCMTK_366
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    PYNETDICOM      
D: Called Application Name:     ANY-SCP         
D: Their Max PDU Receive Size:  16384
D: Presentation Contexts:
D:   Context ID:        1 (Accepted)
D:     Abstract Syntax: =MR Image Storage
D:     Accepted SCP/SCU Role: Default
D:     Accepted Transfer Syntax: =Explicit VR Little Endian
D: Accepted Extended Negotiation: None
D: Accepted Asynchronous Operations Window Negotiation: None
D: User Identity Negotiation Response: None
D: ========================== END A-ASSOCIATE-AC PDU ==========================
I: Association Accepted
E: No presentation context for 'MR Image Storage' has been accepted by the peer with 'JPEG 2000 Image Compression (Lossless Only)' transfer syntax for the SCU role
scaramallion commented 2 years ago

The first example in the previous post is correct, but your association is being aborted by the peer. I would check the log files of your SCP to see why that's occurring, but it may be that you need to register the details of the SCU with the SCP.

Your second post shows a log from a C-FIND request, not C-STORE. Do you mind posting your query code for that? I'm curious about the I: <_io.BytesIO object at 0x7fded1941d00> line in the log, it may be an issue I should fix.

vpapaioannou commented 2 years ago

Thank you for all for you help. To answer your question, the BytesIO object it is just me printing the bytes object created w/in C-STORE and I forgot to comment it out when getting the logs or removing it.

Finally, the problem was that my file had a PixelData array of odd length and not even as expected by DICOM Standard. The solution is to make PixelData even by making each odd length fragment of even length and save back the PixelData. For compressed transfer syntaxes you might want to have this check and solution.

Merry Christmas!!!

scaramallion commented 2 years ago

Thanks for the update, Merry Christmas to you, too.