custom N-ACTION-RQ command

31 views
Skip to first unread message

orr...@gmail.com

unread,
May 31, 2018, 4:15:18 PM5/31/18
to ruby-dicom
Hi,

Ruby-DICOM is excellent, I am truly impressed. This is by far the easiest DICOM library to work with for a medical professional who had some programming classes 30 years ago. It opens new ways for me to automate stuff, do statistics on performed studies, collect information on radiation exposure, etc.

I have a specific question though. I would like to send a N-ACTION-RQ command to my PACS server. Would it be possible to do with ruby-dicom? How would I go with creating such custom DICOM command in ruby-dicom? I have tried to modify the d_client.rb code using C-FIND as an example, but I did not get too far and the communication with the server fails after successfull association (please see attached myd_client.rb for my attempt).

This is the wireshark capture of how the communication between the server and client should look (client is 192.168.6.133, server is 192.168.7.3):

Internet Protocol Version 4, Src: 192.168.6.133, Dst: 192.168.7.3
Transmission Control Protocol, Src Port: 52806, Dst Port: 104, Seq: 1163, Ack: 2108, Len: 146
[2 Reassembled TCP Segments (158 bytes): #39(12), #40(146)]
DICOM
, N-ACTION-RQ ID=4
    PDU
Type: Data (0x04)
    PDU
Length: 152
    PDV
, N-ACTION-RQ ID=4
        PDV
Length: 148
       
Context: 0x05 (Explicit VR Little Endian, <NULL>)
       
Flags: 0x03 (Command, Last Fragment)
       
(0000,0000)          4 Command Group Length                          134
       
(0000,0003)         22 Requested SOP Class UID                       1.3.6.1.4.1.20468.1.8
       
(0000,0100)          2 Command Field                                 N-ACTION-RQ
       
(0000,0110)          2 Message ID                                    4
       
(0000,0800)          2 Data Set Type                                 1
       
(0000,1001)         56 Requested SOP Instance UID                    1.3.12.2.1107.5.2.6.23265.30000017050904531421800000001
       
(0000,1008)          2 Action Type ID                                2


Internet Protocol Version 4, Src: 192.168.6.133, Dst: 192.168.7.3
Transmission Control Protocol, Src Port: 52806, Dst Port: 104, Seq: 1321, Ack: 2108, Len: 48
[2 Reassembled TCP Segments (60 bytes): #41(12), #42(48)]
DICOM
, N-ACTION-RQ-DATA
    PDU
Type: Data (0x04)
    PDU
Length: 54
    PDV
, N-ACTION-RQ-DATA
        PDV
Length: 50
       
Context: 0x05 (Explicit VR Little Endian, <NULL>)
       
Flags: 0x02 (Data, Last Fragment)
       
(0008,0005)         10 Specific Character Set                        [CS] ISO_IR 192
       
(0010,0020)         22 Patient ID                                    [LO] 656516/0000_MR_10-1729


Internet Protocol Version 4, Src: 192.168.7.3, Dst: 192.168.6.133
Transmission Control Protocol, Src Port: 104, Dst Port: 52806, Seq: 2120, Ack: 1369, Len: 52
[2 Reassembled TCP Segments (64 bytes): #44(12), #45(52)]
DICOM
, N-ACTION-RSP ID=4
    PDU
Type: Data (0x04)
    PDU
Length: 58
    PDV
, N-ACTION-RSP ID=4
        PDV
Length: 54
       
Context: 0x05 (Explicit VR Little Endian, <NULL>)
       
Flags: 0x03 (Command, Last Fragment)
       
(0000,0000)          4 Command Group Length                          40
       
(0000,0100)          2 Command Field                                 N-ACTION-RSP
       
(0000,0120)          2 Message ID Being Responded To                 4
       
(0000,0800)          2 Data Set Type                                 257
       
(0000,0900)          2 Status                                        Success (0x00)

The purpose of all this is to modify patient ID on the server. I know it is usually a no-no in DICOM, but this is in a very controlled environment. I am able to do it using a PACS provider supplied tool (hence the wireshark capture) which is really painfull to use (windows only, etc.)

Thank you very much for your work and for your time.

Best regards,

orr721
myd_client.rb

Christoffer Lervåg

unread,
Jun 1, 2018, 8:32:36 AM6/1/18
to ruby-dicom
Hello orr721

Thank you for your kind words. It is always a pleasure to hear of new people using the ruby-dicom library!

As far as implementing a new DClient method, I think I would suggest the following approach:
1. Record a Wireshark session from another program successfully completing the task that you want to replicate in ruby-dicom (it seems like you have already done this).
2. Find an existing DClient method which uses the same overall communication structure as the one you want to implement. (possible examples: #find_images, #move_image, #send)
3. Copy it, rename it and try to modify it according to what you have seen in the Wireshark capture.
4. Try to use it with your PACS.
5. Record your attempts with Wireshark and try to figure out what goes wrong, and fix it.
6. Success! File a Github pull request (or send it to me on email if you dont have a github user) so we can get your improvements into ruby-dicom and other people can benefit from it.

Please give it a go. If you get stuck, please dont hesitate to follow up with more questions.

Best of luck!

Chris

orr...@gmail.com

unread,
Jun 2, 2018, 4:36:45 PM6/2/18
to ruby-dicom
thank you for your response.

I have tried to do it but unfortunately it seems I am stuck. my modification fails at the presentation context negotiation even though the communication with the server looks identical in wireshark. this is how it looks like:

No.     Time           Source                Destination           Protocol Length Info
     
28 0.136305       192.168.6.133         192.168.7.3           DICOM    510    58085 104 [PSH, ACK] Seq=1 Ack=1 Win=66560 Len=456A-ASSOCIATE request RUBY_DICOM --> PACS

Transmission Control Protocol, Src Port: 58085, Dst Port: 104, Seq: 1, Ack: 1, Len: 456
DICOM
, A-ASSOCIATE request RUBY_DICOM --> PACS
    PDU
Type: ASSOC Request (0x01)
    PDU
Length: 450
    A
-ASSOCIATE request RUBY_DICOM --> PACS
       
Protocol Version: 1
       
Called  AE Title: PACS            
       
Calling AE Title: RUBY_DICOM      
       
Application Context: DICOM Application Context Name (1.2.840.10008.3.1.1.1)
       
Presentation Context: 1.3.6.1.4.1.20468.1.1
           
Item Type: Presentation Context (0x20)
           
Item Length: 96
           
Context ID: 0x01
           
Abstract Syntax: 1.3.6.1.4.1.20468.1.1
           
Transfer Syntax: Explicit VR Little Endian (1.2.840.10008.1.2.1)
           
Transfer Syntax: Explicit VR Big Endian (1.2.840.10008.1.2.2)
           
Transfer Syntax: Implicit VR Little Endian (1.2.840.10008.1.2)
       
Presentation Context: 1.3.6.1.4.1.20468.1.9
           
Item Type: Presentation Context (0x20)
           
Item Length: 96
           
Context ID: 0x03
           
Abstract Syntax: 1.3.6.1.4.1.20468.1.9
           
Transfer Syntax: Explicit VR Little Endian (1.2.840.10008.1.2.1)
           
Transfer Syntax: Explicit VR Big Endian (1.2.840.10008.1.2.2)
           
Transfer Syntax: Implicit VR Little Endian (1.2.840.10008.1.2)
       
Presentation Context: 1.3.6.1.4.1.20468.1.8
           
Item Type: Presentation Context (0x20)
           
Item Length: 96
           
Context ID: 0x05
           
Abstract Syntax: 1.3.6.1.4.1.20468.1.8
           
Transfer Syntax: Explicit VR Little Endian (1.2.840.10008.1.2.1)
           
Transfer Syntax: Explicit VR Big Endian (1.2.840.10008.1.2.2)
           
Transfer Syntax: Implicit VR Little Endian (1.2.840.10008.1.2)
       
User Info: Max PDU Length 32768, Implementation UID 1.3.6.1.4.1.20468.0.1.1.6.0.1, Version TMDTK160


No.     Time           Source                Destination           Protocol Length Info
     
30 0.140363       192.168.7.3           192.168.6.133         DICOM    295    104 58085 [PSH, ACK] Seq=1 Ack=457 Win=15744 Len=241A-ASSOCIATE accept  RUBY_DICOM <-- PACS

Transmission Control Protocol, Src Port: 104, Dst Port: 58085, Seq: 1, Ack: 457, Len: 241
DICOM
, A-ASSOCIATE accept  RUBY_DICOM <-- PACS
    PDU
Type: ASSOC Accept (0x02)
    PDU
Length: 235
    A
-ASSOCIATE accept  RUBY_DICOM <-- PACS
       
Protocol Version: 0
       
Called  AE Title: PACS            
       
Calling AE Title: RUBY_DICOM      
       
Application Context: DICOM Application Context Name (1.2.840.10008.3.1.1.1)
       
Presentation Context: ID 0x01, Abstract Syntax Unsupported, <NULL>
           
Item Type: Presentation Context Reply (0x21)
           
Item Length: 25
           
Context ID: 0x01
           
Result: Abstract Syntax Unsupported (0x3)
           
Transfer Syntax: Implicit VR Little Endian (1.2.840.10008.1.2)
       
Presentation Context: ID 0x03, Abstract Syntax Unsupported, <NULL>
           
Item Type: Presentation Context Reply (0x21)
           
Item Length: 25
           
Context ID: 0x03
           
Result: Abstract Syntax Unsupported (0x3)
           
Transfer Syntax: Implicit VR Little Endian (1.2.840.10008.1.2)
       
Presentation Context: ID 0x05, Abstract Syntax Unsupported, <NULL>
           
Item Type: Presentation Context Reply (0x21)
           
Item Length: 25
           
Context ID: 0x05
           
Result: Abstract Syntax Unsupported (0x3)
           
Transfer Syntax: Implicit VR Little Endian (1.2.840.10008.1.2)
       
User Info: Max PDU Length 16384, Implementation UID 1.3.6.1.4.1.20468.0.1.1.6.2, Version TMDTK162


the vendor's original program sends the same three private presentation contexts and the association is accepted. I have even tried the same implementation UID and version.. maybe they use some private fields for further verification and it's missed by wireshark? I don't know.. Do you have any ideas what I can try next?

thanks again and best regards..

orr...@gmail.com

unread,
Jun 2, 2018, 4:43:52 PM6/2/18
to ruby-dicom
this is my last attempt at d_client.rb modification:

module DICOM


  N_ACTION_RQ
= 304 # (encodes to 0130H as US)


 
# This class contains code for extending the DClient Ruby DICOM class.
 
#
 
class DClient


   
# Modify study on a DICOM server.
   
#
   
# === Instance level attributes for options of this procedure:
   
#
   
# * '0010,0010' (Patient Name)
   
# * '0010,0020' (Patient ID)
   
#
   
# @param [String] Study Instance UID which will be modified
   
# @param [Hash] options the options to use for moving the DICOM objects
   
# @option options [String] 'GGGG,EEEE' a tag and value pair to be used for the procedure
   
#
   
def modify_study(mystudyUID, options={})
     
# tmcn user
      set_modify_user_information_array
     
# presentation context
      set_modify_presentation_contexts
     
# Transfer the current options to the data_elements hash:
      set_command_fragment_modify
(mystudyUID)
     
# Prepare data elements for this operation:
      default_params
= {
       
"0008,0005" => "ISO_IR 192"
     
}
     
# Raising an error if a non-tag attribute is used:
      options
.keys.each do |tag|
       
raise ArgumentError, "The supplied tag (#{tag}) is not valid. It must be a string of the form 'GGGG,EEEE'." unless tag.is_a?(String) && tag.tag?
     
end
     
# Set up the parameters and carry out the operation:
      set_data_elements
(default_params.merge(options))
      perform_modify
   
end




   
private




   
# Handles the communication involved in DICOM N-ACTION-RQ for modify study.
   
# Build the necessary strings and sends the command element that makes up the request.
   
# Listens for and interpretes the incoming response.
   
#
   
def perform_modify
     
# Open a DICOM link:
      establish_association
     
if association_established?
       
if request_approved?
         
# Continue with our operation, since the request was accepted.
         
@link.build_command_fragment(PDU_DATA, presentation_context_id, COMMAND_LAST_FRAGMENT, @command_elements)
         
@link.transmit
         
@link.build_data_fragment(@data_elements, presentation_context_id)
         
@link.transmit
         
# Receive confirmation response:
          segments
= @link.receive_multiple_transmissions
          process_returned_data
(segments)
       
end
       
# Close the DICOM link:
        establish_release
     
end
   
end


   
# Sets the command elements used in a N-ACTION-RQ for modify study.
   
#
   
def set_command_fragment_modify(mystudyUID)
     
@command_elements = [
       
["0000,0002", "UI", @presentation_contexts.keys.first], # Affected SOP Class UID
       
["0000,0003", "UI", "1.3.6.1.4.1.20468.1.8"], # Requested SOP Class UID
       
["0000,0100", "US", N_ACTION_RQ],
       
["0000,0110", "US", 4], # Message ID
       
["0020,000D", "UI", mystudyUID], # Requested SOP Instance UID
       
["0000,1008", "US", 2], # Action Type ID
       
["0000,0800", "US", DATA_SET_PRESENT]
     
]
   
end


   
# Creates the presentation context used for the non-file-transmission association requests..
   
#
   
def set_modify_presentation_contexts
     
@presentation_contexts = make_pc(1, "1.3.6.1.4.1.20468.1.1")
     
@presentation_contexts.merge!(make_pc(3, "1.3.6.1.4.1.20468.1.9"))
     
@presentation_contexts.merge!(make_pc(5, "1.3.6.1.4.1.20468.1.8"))
     
# puts @presentation_contexts
   
end


   
def make_pc(id, abstract_syntax)
     
raise ArgumentError, "Expected String, got #{abstract_syntax.class}" unless abstract_syntax.is_a?(String)
      transfer_syntaxes
= [EXPLICIT_LITTLE_ENDIAN, EXPLICIT_BIG_ENDIAN, IMPLICIT_LITTLE_ENDIAN]
      item
= {:transfer_syntaxes => transfer_syntaxes}
      pc
= {id => item}
     
return {abstract_syntax => pc}
   
end


   
# Sets the @user_information items instance array.
   
#
   
# === Notes
   
#
   
# Each user information item is a three element array consisting of: item type code, VR & value.
   
#
   
def set_modify_user_information_array
     
@user_information = [
       
[ITEM_MAX_LENGTH, "UL", @max_package_size],
       
[ITEM_IMPLEMENTATION_UID, "STR", "1.3.6.1.4.1.20468.0.1.1.6.0.1"],
       
[ITEM_IMPLEMENTATION_VERSION, "STR", "TMDTK160"]
     
]
   
end
   
 
end
end



Reply all
Reply to author
Forward
0 new messages