pydicom / pynetdicom

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

Download dcm file from pacs #550

Closed specikrumpel closed 3 years ago

specikrumpel commented 3 years ago

Hi, I've just started using pynetdicom and I was able to get responses from pacs based on StudyInstanceUID or PatientID, but these datasets contain only the information on the corresponding query retrieve level. I would need nearly all the data of the dicom files corresponding to given (for example) StudyInstanceUID. Is there a way to download these dcm files to my device, and if so how?

Thank You!

scaramallion commented 3 years ago

The Query/Retrieve - Move or Get examples should show you the basics, or you can take a look at the movescu and getscu apps.

If your PACS supports QR Get then use that, otherwise you'll have to either use a known storage destination or register your own with the PACS in order to use QR Move

specikrumpel commented 3 years ago

Thank you so much for your answer @scaramallion . I tried experimenting with getscu.py, but with no success. I don't quite understand this part from getscu documentation: "When using the -k keyword option it becomes possible to specify the query dataset (the Identifier) without needing to create a DICOM file. Multiple instances of -k can be used to build up the Identifier" How do I specify that I want the whole DICOM file? I tried leaving out the "QueryRetrieveLevel" keyword but I'm not sure if that is the correct way. Another issue I have is that I'm not sure how to specify the output directory parameter.

I tried using the example to run the file, but I also ran into issues there. Here is my output (in the second part I tried to recieve all studies from dicomserver.co.uk that have the StudyDate tag set to "20200529")

PS C:\Users\Matlap\Documents> python -m pynetdicom getscu 127.0.0.1 11112 -k QueryRetrieveLevel=PATIENT -k PatientName=
I: Requesting Association
I: Association Accepted
E: No presentation context for 'Patient Root Query/Retrieve Information Model - GET' has been accepted by the peer for the SCU role
Traceback (most recent call last):
  File "C:\Users\Matlap\AppData\Local\Programs\Python\Python38\lib\runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Users\Matlap\AppData\Local\Programs\Python\Python38\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "C:\Users\Matlap\AppData\Local\Programs\Python\Python38\lib\site-packages\pynetdicom\__main__.py", line 28, in <module>
    app.main(args)
  File "C:\Users\Matlap\AppData\Local\Programs\Python\Python38\lib\site-packages\pynetdicom\apps\getscu\getscu.py", line 300, in main
    responses = assoc.send_c_get(identifier, query_model)
  File "C:\Users\Matlap\AppData\Local\Programs\Python\Python38\lib\site-packages\pynetdicom\association.py", line 1324, in send_c_get
    context = self._get_valid_context(query_model, '', 'scu')
  File "C:\Users\Matlap\AppData\Local\Programs\Python\Python38\lib\site-packages\pynetdicom\association.py", line 444, in _get_valid_context
    raise ValueError(msg)
ValueError: No presentation context for 'Patient Root Query/Retrieve Information Model - GET' has been accepted by the peer for the SCU role
I: Aborting Association
PS C:\Users\Matlap\Documents> python -m pynetdicom getscu www.dicomserver.co.uk 11112 -k StudyDate="20200529"
I: Requesting Association
I: Association Accepted
I: Sending Get Request: MsgID 1
I:
I: # Request Identifier
I: (0008,0020) DA [20200529]                               # 1 StudyDate
I:
I: Get SCP Result: 0x0000 (Success)
I: Sub-Operations Remaining: 0, Completed: 0, Failed: 0, Warning: 0
I: Releasing Association

Could you please show me an example how to run the file to get all studies with corresponding date as DICOM files into some specified local directory?

Thank You!

specikrumpel commented 3 years ago

@scaramallion I would be really thankful for a help with this. When I use findscu.py like this

python -m pynetdicom findscu www.dicomserver.co.uk 11112 -k QueryRetrieveLevel=STUDY -k StudyDate="20200529"

I get correct output (18 studies with this date are listed) i.e list that looks like this:

I: # Response Identifier
I: (0008,0005) CS [ISO_IR 100]                             # 1 SpecificCharacterSet
I: (0008,0020) DA [20200529]                               # 1 StudyDate
I: (0008,0052) CS [STUDY]                                  # 1 QueryRetrieveLevel
I: (0008,0054) AE [ANY-SCP]                                # 1 RetrieveAETitle
I:
I: Find SCP Response: 2 - 0xFF00 (Pending)
I:
I: # Response Identifier
I: (0008,0005) CS [ISO_IR 100]                             # 1 SpecificCharacterSet
I: (0008,0020) DA [20200529]                               # 1 StudyDate
I: (0008,0052) CS [STUDY]                                  # 1 QueryRetrieveLevel
I: (0008,0054) AE [ANY-SCP]                                # 1 RetrieveAETitle
I:
I: Find SCP Response: 3 - 0xFF00 (Pending)
....
.....
....

But when I use (almost) the same command with getscu.py, I don't get anything.

python -m pynetdicom getscu --output-directory "C:\Users\Matlap\Documents\studies" www.dicomserver.co.uk 11112 -k QueryRetrieveLevel=STUDY -k StudyDate="20200529"

I just get this:

I: Requesting Association
I: Association Accepted
I: Sending Get Request: MsgID 1
I:
I: # Request Identifier
I: (0008,0020) DA [20200529]                               # 1 StudyDate
I: (0008,0052) CS [STUDY]                                  # 1 QueryRetrieveLevel
I:
I: Get SCP Result: 0x0000 (Success)
I: Sub-Operations Remaining: 0, Completed: 0, Failed: 0, Warning: 0
I: Releasing Association

I need help with the command (i.e what to write as keywords to get dcm files into the specified directory). Thanks!

scaramallion commented 3 years ago

www.dicomserver.co.uk will always return 0x0000 with no sub-operations for a C-GET request. If you want something to test with you should try DCMTK's dcmqrscp app or the qrscp app that comes with pynetdicom.

specikrumpel commented 3 years ago

Thank you for letting me know @scaramallion . I tried experimenting with cmove and creating my own storage scp according to examples in pynetdicom documentation. but I could not get it to work. I started the server (the code used is included) but when iItried connecting via Radiant I got this message:

D: Request Parameters:
D: ======================= INCOMING A-ASSOCIATE-RQ PDU ========================
D: Their Implementation Class UID:      1.2.826.0.1.3680043.8.1223.1.2
D: Their Implementation Version Name:   RadiAnt-2020.1
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    matus           
D: Called Application Name:     SERVER12345     
D: Their Max PDU Receive Size:  16384
D: Presentation Context:
D:   Context ID:        1 (Proposed)
D:     Abstract Syntax: =Verification SOP Class
D:     Proposed SCP/SCU Role: Default
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:       =JPEG Lossless, Non-Hierarchical, First-Order Prediction (Process 14 [Selection Value 1])
D:       =JPEG-LS Lossless Image Compression
D:       =JPEG 2000 Image Compression (Lossless Only)
D:       =JPEG Baseline (Process 1)
D:       =JPEG Extended (Process 2 and 4)
D:       =JPEG-LS Lossy (Near-Lossless) Image Compression
D:       =JPEG 2000 Image Compression
D:       =RLE Lossless
D:       =MPEG2 Main Profile / Main Level
D:       =MPEG2 Main Profile / High Level
D:       =MPEG-4 AVC/H.264 High Profile / Level 4.1
D:       =MPEG-4 AVC/H.264 BD-compatible High Profile / Level 4.1
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 ==========================
I: Accepting Association
D: Accept Parameters:
D: ======================= OUTGOING A-ASSOCIATE-AC PDU ========================
D: Our Implementation Class UID:      1.2.826.0.1.3680043.9.3811.1.5.3
D: Our Implementation Version Name:   PYNETDICOM_153
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Responding Application Name: resp. AE Title
D: Our Max PDU Receive Size:    16382
D: Presentation Contexts:
D:   Context ID:        1 (Rejected - Abstract Syntax Not Supported)
D:     Abstract Syntax: =Verification SOP Class
D: Accepted Extended Negotiation: None
D: Accepted Asynchronous Operations Window Negotiation: None
D: User Identity Negotiation Response: None
D: ========================== END A-ASSOCIATE-AC PDU ==========================
D: pydicom.read_dataset() TransferSyntax="Little Endian Implicit"
I: Received Echo Request (MsgID 1)
D: ========================== INCOMING DIMSE MESSAGE ==========================
D: Message Type                  : C-ECHO RQ
D: Presentation Context ID       : 1
D: Message ID                    : 1
D: Data Set                      : None
D: ============================ END DIMSE MESSAGE =============================
I: Received DIMSE message with invalid or rejected context ID: 1
D: <pynetdicom.dimse_primitives.C_ECHO object at 0x000001AFC8A02EB0>
I: Aborting Association
D: Abort Parameters:
D: =========================== OUTGOING A-ABORT PDU ===========================
D: Abort Source: DUL service-user
D: Abort Reason: No reason given
D: ============================= END A-ABORT PDU ==============================

So I tried Orthanc and it seems that this works better. I was able to verify my connection to my Orthanc server using Radiant and also by using Verification SCU code from here https://pydicom.github.io/pynetdicom/stable/examples/verification.html . So I tried using movescu.py to move desired DICOM files from dicomserver.co.uk to my Orthanc server (into my local directory) but I got this error (even after turning Firewall off):

C:\Users\matus\Documents>python -m pynetdicom movescu --calling-aet "ORTHANC" --called-aet "ORTHANC" www.dicomserver.co.uk 104 -k StudyDate="20200529" --store --store-port 4242 --store-aet "ORTHANC"
Traceback (most recent call last):
  File "C:\Users\matus\AppData\Local\Programs\Python\Python38\lib\runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Users\matus\AppData\Local\Programs\Python\Python38\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "C:\Users\matus\AppData\Local\Programs\Python\Python38\lib\site-packages\pynetdicom\__main__.py", line 28, in <module>
    app.main(args)
  File "C:\Users\matus\AppData\Local\Programs\Python\Python38\lib\site-packages\pynetdicom\apps\movescu\movescu.py", line 276, in main
    scp = ae.start_server(
  File "C:\Users\matus\AppData\Local\Programs\Python\Python38\lib\site-packages\pynetdicom\ae.py", line 1192, in start_server
    server = self.make_server(
  File "C:\Users\matus\AppData\Local\Programs\Python\Python38\lib\site-packages\pynetdicom\ae.py", line 683, in make_server
    return server_class(
  File "C:\Users\matus\AppData\Local\Programs\Python\Python38\lib\site-packages\pynetdicom\transport.py", line 546, in __init__
    TCPServer.__init__(
  File "C:\Users\matus\AppData\Local\Programs\Python\Python38\lib\socketserver.py", line 452, in __init__
    self.server_bind()
  File "C:\Users\matus\AppData\Local\Programs\Python\Python38\lib\site-packages\pynetdicom\transport.py", line 713, in server_bind
    self.socket.bind(self.server_address)
OSError: [WinError 10013] An attempt was made to access a socket in a way forbidden by its access permissions

I am still a newbie in this and I don't understand several things about this. After starting the Orthanc server and listing used ports (via 'netstat -a' command in cmd) I saw Orthanc server running on 0.0.0.0:4242 , but when I try to verify connection to this server (either via Radiant or via pynetdicom code) I have to use address 172.0.0.1 instead of 0.0.0.0, because it won't connect and I wonder why is that. I am also not sure why my scp (started via pynetdicom) could not verify connection.

Another thing is that I don't understand the difference between --store-aet and --move-aet in movescu.py documentation and I am not sure which to use when specifing to movescu.py. And probably the last thing, could you please tell me if I wrote the command correctly (python -m pynetdicom movescu --calling-aet "ORTHANC" --called-aet "ORTHANC" www.dicomserver.co.uk 104 -k StudyDate="20200529" --store --store-port 4242 --store-aet "ORTHANC") to get the result I need? (i.e to get all studies with the corresponding date to the Orthanc server in my local directory) dicomserver.co.uk says that when using cmove you should use port 104 and AEs should be the same. I am very thankful and I appreciate your time :)

I am also dropping here my Orthanc config file (as txt) if it is needed orthanc.txt

Here is my python file (also as txt) myscp.txt

Here is Radiant connections radiantconn

scaramallion commented 3 years ago

Radiant looks like it's sending a C-ECHO request to verify connectivity first. Add Verification SOP Class to your supported presentation contexts to fix that.

Port 104 on Windows will be a protected port and will require admin privileges. Try with 11112

specikrumpel commented 3 years ago

Thanks for the suggestions @scaramallion I tried adding this line to the code lineadd but after starting the server and connecting via radiant I got the same message :

D: Request Parameters:
D: ======================= INCOMING A-ASSOCIATE-RQ PDU ========================
D: Their Implementation Class UID:      1.2.826.0.1.3680043.8.1223.1.2
D: Their Implementation Version Name:   RadiAnt-2020.1
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Calling Application Name:    matus           
D: Called Application Name:     SERVER12345     
D: Their Max PDU Receive Size:  16384
D: Presentation Context:
D:   Context ID:        1 (Proposed)
D:     Abstract Syntax: =Verification SOP Class
D:     Proposed SCP/SCU Role: Default
D:     Proposed Transfer Syntaxes:
D:       =Explicit VR Little Endian
D:       =Implicit VR Little Endian
D:       =Explicit VR Big Endian
D:       =JPEG Lossless, Non-Hierarchical, First-Order Prediction (Process 14 [Selection Value 1])
D:       =JPEG-LS Lossless Image Compression
D:       =JPEG 2000 Image Compression (Lossless Only)
D:       =JPEG Baseline (Process 1)
D:       =JPEG Extended (Process 2 and 4)
D:       =JPEG-LS Lossy (Near-Lossless) Image Compression
D:       =JPEG 2000 Image Compression
D:       =RLE Lossless
D:       =MPEG2 Main Profile / Main Level
D:       =MPEG2 Main Profile / High Level
D:       =MPEG-4 AVC/H.264 High Profile / Level 4.1
D:       =MPEG-4 AVC/H.264 BD-compatible High Profile / Level 4.1
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 ==========================
I: Accepting Association
D: Accept Parameters:
D: ======================= OUTGOING A-ASSOCIATE-AC PDU ========================
D: Our Implementation Class UID:      1.2.826.0.1.3680043.9.3811.1.5.3
D: Our Implementation Version Name:   PYNETDICOM_153
D: Application Context Name:    1.2.840.10008.3.1.1.1
D: Responding Application Name: resp. AE Title
D: Our Max PDU Receive Size:    16382
D: Presentation Contexts:
D:   Context ID:        1 (Rejected - Abstract Syntax Not Supported)
D:     Abstract Syntax: =Verification SOP Class
D: Accepted Extended Negotiation: None
D: Accepted Asynchronous Operations Window Negotiation: None
D: User Identity Negotiation Response: None
D: ========================== END A-ASSOCIATE-AC PDU ==========================
D: pydicom.read_dataset() TransferSyntax="Little Endian Implicit"
I: Received Echo Request (MsgID 1)
D: ========================== INCOMING DIMSE MESSAGE ==========================
D: Message Type                  : C-ECHO RQ
D: Presentation Context ID       : 1
D: Message ID                    : 1
D: Data Set                      : None
D: ============================ END DIMSE MESSAGE =============================
I: Received DIMSE message with invalid or rejected context ID: 1
D: <pynetdicom.dimse_primitives.C_ECHO object at 0x0000024DC3142EE0>
I: Aborting Association
D: Abort Parameters:
D: =========================== OUTGOING A-ABORT PDU ===========================
D: Abort Source: DUL service-user
D: Abort Reason: No reason given
D: ============================= END A-ABORT PDU ==============================

I also tried using orthanc and listening to dicomserver.co.uk on port 11112 (python -m pynetdicom movescu --calling-aet "ORTHANC" --called-aet "ORTHANC" www.dicomserver.co.uk 11112 -k StudyDate="20200529" --store --store-port 4242 --store-aet "ORTHANC") but I got the same error message again.

OSError: [WinError 10013] An attempt was made to access a socket in a way forbidden by its access permissions

scaramallion commented 3 years ago

Your queries aren't quite right, you need the unique key for the STUDY level (i.e. Study Instance UID). Also, if you're using the STUDY as the top level query you should be using Study Root Query/Retrieve Information Model (the -S flag).

  1. Send QR Find request: python -m pynetdicom findscu www.dicomserver.co.uk 104 -d -k QueryRetrieveLevel=STUDY -k StudyDate=20200529 -k StudyInstanceUID=
    ...
    I: Find SCP Response: 1 - 0xFF00 (Pending)
    D: pydicom.read_dataset() TransferSyntax="Little Endian Explicit"
    I: 
    I: # Response Identifier
    I: (0008,0005) CS [ISO_IR 100]                             # 1 SpecificCharacterSet
    I: (0008,0020) DA [20200529]                               # 1 StudyDate
    I: (0008,0052) CS [STUDY]                                  # 1 QueryRetrieveLevel
    I: (0008,0054) AE [ANY-SCP]                                # 1 RetrieveAETitle
    I: (0020,000D) UI [1.2.276.0.7230010.3.1.4.0.698.1590747823.893656] # 1 StudyInstanceUID
    I: 
    D: pydicom.read_dataset() TransferSyntax="Little Endian Implicit"
    D: 
    I: Find SCP Response: 2 - 0xFF00 (Pending)
    D: pydicom.read_dataset() TransferSyntax="Little Endian Explicit"
    I: 
    I: # Response Identifier
    I: (0008,0005) CS [ISO_IR 100]                             # 1 SpecificCharacterSet
    I: (0008,0020) DA [20200529]                               # 1 StudyDate
    I: (0008,0052) CS [STUDY]                                  # 1 QueryRetrieveLevel
    I: (0008,0054) AE [ANY-SCP]                                # 1 RetrieveAETitle
    I: (0020,000D) UI [1.2.276.0.7230010.3.1.4.2831190786.698.1591282293.141102] # 1 StudyInstanceUID
    ...

    2a. Send QR Get request for 1.2.276.0.7230010.3.1.4.0.698.1590747823.893656: python -m pynetdicom getscu www.dicomserver.co.uk 104 -d -k QueryRetrieveLevel=STUDY -k StudyInstanceUID=1.2.276.0.7230010.3.1.4.0.698.1590747823.893656 -S Also, apparently I was wrong about C-GET not returning instances since the above worked OK for me.

OR

2b. Send QR Move request: python -m pynetdicom movescu www.dicomserver.co.uk 104 -d -k QueryRetrieveLevel=STUDY -k StudyInstanceUID=1.2.276.0.7230010.3.1.4.0.698.1590747823.893656 -aet MOVESCP -aem MOVESCP -S --store --store-port 11112

The above should work, but I'm getting 0xC001 responses, so I think something weird is going on at their end trying to connect to the Storage SCP.

scaramallion commented 3 years ago

It should be ae.add_supported_context('1.2.840.10008.1.1')

scaramallion commented 3 years ago

For the OSError I'd suggest googling or checking Stack Overflow

specikrumpel commented 3 years ago

THANK YOU SO MUCH @scaramallion A week ago I didn't even know what DICOM or PACS is and now I downloaded dcm file <3 I am super happy thanks to You!

specikrumpel commented 3 years ago

Changing "requested" to "supported" worked, now I am able to connect to that server, but I probably won't need it if I can just use getscu. I will continue my work and seek your help if I run into some problems.

specikrumpel commented 3 years ago

One quick thing I noticed is that when I try to download a bigger dicom file (I tried with StudyInstanceUID=1.3.6.1.4.1.5962.99.1.677899371.1025612506.1589815831659.3.0) it stops downloading after random amount of received images. First try I received 185 images and then got message:

Connection closed while waiting for DIMSE message I: Releasing Association I: Association Aborted (A-P-ABORT) I: Association Aborted

Second try I got to 206. Is this somehow connected to --dimse-timeout?

scaramallion commented 3 years ago

Yeah that sounds about right. You could try upping the timeout

specikrumpel commented 3 years ago

I tried to set dimse timeout to 180 seconds but it did not help. I got only 57 images. Because the connection stops after random ammount of images each time I run the command, I think it has something to do with dicomserver.co.uk, but I am not sure.

I will try experimenting with different dicom servers and see if I get better results. What do you think is causing the issue?

scaramallion commented 3 years ago

Hmm, could you post your query? I'll see if I can reproduce it and find the cause. You can remove the timeout by setting it to uh (checks docs): ae.dimse_timeout = None

specikrumpel commented 3 years ago

This is the command I wrote to cmd: python -m pynetdicom getscu -td 180 -od "C:\Users\matus\Documents\output" www.dicomserver.co.uk 104 -d -k QueryRetrieveLevel=STUDY -k StudyInstanceUID=1.3.6.1.4.1.5962.99.1.677899371.1025612506.1589815831659.3.0 -S

scaramallion commented 3 years ago

Actually, you could be hitting the network timeout as well. Try with --network-timeout 1800

specikrumpel commented 3 years ago

Thanks! Adding --network-timeout 1800 fixed the issue. But I wonder why it stopped after random amount of time before. Shouldn't it be always the default 30 sec?

I have one more question. This method gets me all the images in the study, but they are stored in individual files with .0 file extension. Is there a way to group these images into one .dcm file?

scaramallion commented 3 years ago

It'll timeout when there's no network/DIMSE message activity for 30 s. So if dicomserver.co.uk takes too long to send a particular message (for whatever reason) then it'll timeout.

I have one more question. This method gets me all the images in the study, but they are stored in individual files with .0 file extension. Is there a way to group these images into one .dcm file?

No, each file is a standalone SOP Instance (dataset). If you want to give them a custom extension then you'll have to modify getscu.py (source code here)

gozdeorhan commented 3 years ago

Hi @scaramallion,

Thank you for your replies. I'm also a newbie, trying to figure out how to get .dcm files from a PACS server. I read the whole thread and wrote a command as follows:

python -m pynetdicom getscu -td 180 --network-timeout 1800 -od "C:\Users\gozde.orhan\Desktop\test" www.dicomserver.co.uk 104 -d -k QueryRetrieveLevel=STUDY -k StudyInstanceUID=61.7.269302601177927385711338092726468287820 -S

(Trying to get this CT: http://webviewer.dicomserver.co.uk/Home/StudyView?StudyUID=61.7.269302601177927385711338092726468287820)

However I still get "Connection closed while waiting for DIMSE message" as an output and wasn't able to retrieve anything to my output directory. It is empty.

Why do you think I get this message? What can I do to resolve it?

Thank you, Gözde.

gozdeorhan commented 3 years ago

timeout_message

scaramallion commented 3 years ago

EIther the DIMSE timeout is being hit or the QR SCP is closing the connection (maybe its hitting its own timeout?). Try increasing -td 1800 or removing it (by editing the source to set it to None). Or use a different QR SCP to test against since www.dicomserver.co.uk doesn't seem to be particularly fast/reliable.

gozdeorhan commented 3 years ago

Thank you @scaramallion! increasing -td 1800, solved the problem. I managed to get the images.

Now I'm trying to retrieve images from a PACS server which doesn't support C-GET. However, I thought C-FIND should work since it is supported. But I'm getting an 'Out of Resources' error.

Here is my command (i had to exclude the ip): python -m pynetdicom findscu [serverip] 104 -k QueryRetrieveLevel=PATIENT -k PatientID=30782000370 -d

What does 'Out of Resources' mean? What should I do to avoid it?

cfind

scaramallion commented 3 years ago

It's a general failure status which usually means something vague has gone wrong... (really, it surprising how unhelpful most failure statuses are). Are you sure that the Patient ID is correct and exists on the PACS? You could try a wildcard match instead with -k PatientID=3078*

Or maybe there's too many matches (some PACS limit the number of matches), so maybe try and narrow it down to a specific study or series.

specikrumpel commented 3 years ago

Hi @scaramallion ,

I would like to ask additional question. Is there a way to use getscu to get only the header (I don't need images, and it takes a long time to get the studies with the images)? Even from the header, I don't really need all the tags but it would do. I thought about using findscu to specify the tags I need. But I don't know how to get the tags that don't fall into study or series level (in Study QR model), or at least I don't think they do. I looked at several tables with these attributes listed, like for example table C.6-5 here http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.6.2.html . How could I get either the whole header, or if that is not possible, how could I get specific tags, like for example tag (0018, 1000) DeviceSerialNumber?

Thank You!

EDIT: example tag is not that good of an example. Let's say 00081070 | OperatorsName

EDIT2: or for example key (0018,0050) Slice Thickness But getting the whole header with meta data would be fine too

EDIT3: more specifically I am asking about retrieving information without bulk data, like pixel data and so on...

scaramallion commented 3 years ago

Is there a way to use getscu to get only the header

It depends on whether or not the PACS supports Composite Instance Retrieve Without Bulk Data, you'd have to check their conformance statement (or try associating with the corresponding SOP Class and see if the presentation context gets accepted).

I thought about using findscu

Sometimes you can use C-FIND (just add something like -k OperatorsName= to your query), however these are optional keys and depend on the PACS so again you'd have to check their conformance statement. You can just try and see if it works (you might get lucky).

specikrumpel commented 3 years ago

Thanks @scaramallion for the quick answer. By associating with the corresponding SOP Class (1.2.840.10008.5.1.4.1.2.5.3) do you mean adding it to requested context? I tried with dicomserver.co.uk using this code:

from pynetdicom import AE

Initialise the Application Entity

ae = AE()

Add a requested presentation context

ae.add_requested_context('1.2.840.10008.1.1') #VerificationSOPClass ae.add_requested_context('1.2.840.10008.5.1.4.1.2.5.3') #CompositeInstanceRetrieveWithoutBulkDataGet

Associate with peer AE at IP 127.0.0.1 and port 11112

assoc = ae.associate('www.dicomserver.co.uk', 11112)

if assoc.is_established:

Use the C-ECHO service to send the request

# returns the response status a pydicom Dataset
status = assoc.send_c_echo()

# Check the status of the verification request
if status:
    # If the verification request succeeded this will be 0x0000
    print('C-ECHO request status: 0x{0:04x}'.format(status.Status))
else:
    print('Connection timed out, was aborted or received invalid response')

# Release the association
assoc.release()

else: print('Association rejected, aborted or never connected')

And the code printed success: C-ECHO request status: 0x0000 Does that mean that dicomserver.co.uk supports retrieving without bulk data? How would I use that in getscu.py?

gozdeorhan commented 3 years ago

Hello @scaramallion,

Thank you for your response. I solved the problem by adding calling aet.

Also, is there a way to retrieve images by using AccessionNumber instead of PatientID/StudyInstanceUID?

I tried the following:

python -m pynetdicom getscu -od "C:\Users\gozde.orhan\Desktop\test" [serverip] 104 -d -k QueryRetrieveLevel=STUDY -k AccessionNumber="GOZ1414031" -S -aet [calling] -aec [called]

and

python -m pynetdicom getscu -od "C:\Users\gozde.orhan\Desktop\test" [serverip] 104 -d -k QueryRetrieveLevel=STUDY -k AccessionNumber=GOZ1414031 -S -aet [calling] -aec [called]

Even though it returns a success message, my output directory remains unchanged.

PS: I'm positive that this accession number is true and corresponds to a chest image.

Capture

scaramallion commented 3 years ago

Also, is there a way to retrieve images by using AccessionNumber instead of PatientID/StudyInstanceUID?

It depends on the implementation, to be conformant you still need a key like Patient ID as the Accession Number won't be unique. Specifically:

The SCP shall identify a set of Entities at the level of the retrieval based upon the values in the Unique Keys in the Identifier of the C-GET request

You should really check the requirements for a QR Get SCU

You'll get a Success status if all C-STORE sub-operations completed OK, including if there were no sub-operations