pydicom / pynetdicom

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

pynetdicom will hang on certain compression levels #930

Closed tokeefe closed 6 months ago

tokeefe commented 6 months ago

I'm trying to send a GrayscaleSoftcopyPresentationStateStorage image to pynetdicom v2.0.2 using DCMTK 3.6.6 and the process is hanging based on the chosen compression level.

For both dcmsend and storescu, the supported compression level can be set to a value between 0 and 9 (default is 6). You can set the compression level using e.g., +cl [N]. For the image in question, if I set the compression level to 3, 5, or 6, the C-STORE request will hang. For example, both of these commands will hang

dcmsend -v -aec TEST pynetdicom.example.org 11112 file.dcm +cl 3
storescu -v -aec TEST pynetdicom.example.org 11112 file.dcm -xd +cl 5

If I set the compression level to 0, 1, 2, 4, 7, 8, or 9, it works! Very bizarre.

At this point, I'm mostly looking for some help understanding how this could happen and why it might happen for this one type of image. Happy to share this image with you if you think it would help (its from a phantom scan, so nothing sensitive).

Thank you!

scaramallion commented 6 months ago

I can't reproduce this with pynetdicom 2.0.2 and DCMTK's storescu v3.6.4 using the CTImageStorage.dcm dataset that comes with pynetdicom.

Store SCP command: $ python3.10 -m pynetdicom storescp 11112 -d Store SCU command: $ storescu -d localhost 11112 CTImageStorage.dcm -xd +cl 6

Can you post the full debug log output from the SCP when it hangs? Can you attach an anonymised dataset that reproduces the issue?

You should be able to access the raw encoded dataset via Event.request.DataSet in the EVT_C_STORE handler, or alternatively if you set the STORE_RECV_CHUNKED_DATASET configuration option. You can then experiment to see if Python can decompress the data as-is and read the decompress data with pydicom's dcmread() as usual.

For reference here is where pynetdicom does the decompression for deflated datasets.

tokeefe commented 6 months ago

Thank you! This was super helpful in narrowing it down.

I can confirm that the file is received properly with python3.10 -m pynetdicom storescp 11112 -d which helped me spot the difference in my code. It has something to do with setting this configuration option to True

_config.UNRESTRICTED_STORAGE_SERVICE = True

When this is True, I experience the issue described above. When this set to False, the file is received properly regardless of the chosen compression level.

I dug around in dsutils.py and from what I can tell, I never fall into that if deflated block. It jumps to the call to pydicom.read_dataset which is working fine. Whatever is happening seems to be beyond that portion of the code. That's where I am, currently, in my attempt to troubleshoot this.

I'm going to attach a zip file with my simplified version of storescp.py, the pesky DICOM file in question, and the pynetdicom debug output when it works and when it doesn't.

pynetdicom.zip

Thanks again for your help!

scaramallion commented 6 months ago

OK, after some more digging this is purely a DCMTK issue with their storescu app that affects certain maximum PDU sizes.

Can reproduce using only DCMTK with: storescp 11112 +xd -d --max-pdu 26200 storescu localhost 11112 -xd +cl 6 dataset.dcm

Workaround in pynetdicom is to set no maximum PDU size via ae.maximum_pdu_size = 0

tokeefe commented 6 months ago

Wow! Thank you! I had no idea that this question was going to ultimately send you down a rabbit hole like this. I apologize for that, but I really appreciate the time you spent on this and getting to the bottom of it.

I'll try to figure out how to submit this issue to the DCMTK devs. Thanks again!