dicom 0.9.3 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,11 +3,8 @@ module DICOM
3
3
 
4
4
  # This class contains code for handling the client side of DICOM TCP/IP network communication.
5
5
  #
6
- # === Resources
7
- #
8
- # * For information regarding query, such as required/optional attributes and value matching, refer to the DICOM standard, PS3.4, C 2.2.
9
- #--
10
- # FIXME: The code which waits for incoming network packets seems to be very CPU intensive. Perhaps there is a more elegant way to wait for incoming messages?
6
+ # For more information regarding queries, such as required/optional attributes
7
+ # and value matching, refer to the DICOM standard, PS3.4, C 2.2.
11
8
  #
12
9
  class DClient
13
10
  include Logging
@@ -31,26 +28,17 @@ module DICOM
31
28
 
32
29
  # Creates a DClient instance.
33
30
  #
34
- # === Notes
35
- #
36
- # * To customize logging behaviour, refer to the Logging module documentation.
37
- #
38
- # === Parameters
39
- #
40
- # * <tt>host_ip</tt> -- String. The IP adress of the server which you are going to communicate with.
41
- # * <tt>port</tt> -- Fixnum. The network port to be used.
42
- # * <tt>options</tt> -- A hash of parameters.
43
- #
44
- # === Options
31
+ # @note To customize logging behaviour, refer to the Logging module documentation.
45
32
  #
46
- # * <tt>:ae</tt> -- String. The name of this client (application entity).
47
- # * <tt>:host_ae</tt> -- String. The name of the server (application entity).
48
- # * <tt>:max_package_size</tt> -- Fixnum. The maximum allowed size of network packages (in bytes).
49
- # * <tt>:timeout</tt> -- Fixnum. The maximum period the server will wait on an answer from a client before aborting the communication.
33
+ # @param [String] host_ip the IP adress of the server which you are going to communicate with
34
+ # @param [Integer] port the network port to be used
35
+ # @param [Hash] options the options to use for the network communication
36
+ # @option options [String] :ae the name of this client (application entity)
37
+ # @option options [String] :host_ae the name of the server (application entity)
38
+ # @option options [Integer] :max_package_size the maximum allowed size of network packages (in bytes)
39
+ # @option options [Integer] :timeout the number of seconds the client will wait on an answer from a server before aborting (defaults to 10)
50
40
  #
51
- # === Examples
52
- #
53
- # # Create a client instance using default settings:
41
+ # @example Create a client instance using default settings
54
42
  # node = DICOM::DClient.new("10.1.25.200", 104)
55
43
  #
56
44
  def initialize(host_ip, port, options={})
@@ -78,7 +66,7 @@ module DICOM
78
66
  @link = Link.new(:ae => @ae, :host_ae => @host_ae, :max_package_size => @max_package_size, :timeout => @timeout)
79
67
  end
80
68
 
81
- # Tests the connection to the server by performing a C-ECHO.
69
+ # Tests the connection to the server by performing a C-ECHO procedure.
82
70
  #
83
71
  def echo
84
72
  # Verification SOP Class:
@@ -88,24 +76,22 @@ module DICOM
88
76
 
89
77
  # Queries a service class provider for images (composite object instances) that match the specified criteria.
90
78
  #
91
- # === Parameters
92
- #
93
- # * <tt>query_params</tt> -- A hash of query parameters.
79
+ # === Instance level attributes for this query:
94
80
  #
95
- # === Options
81
+ # * '0008,0018' (SOP Instance UID)
82
+ # * '0020,0013' (Instance Number)
96
83
  #
97
- # * <tt>"0008,0018"</tt> -- SOP Instance UID
98
- # * <tt>"0020,0013"</tt> -- Instance Number
84
+ # In addition to the above listed attributes, a number of "optional" attributes
85
+ # may be specified. For a general list of optional object instance level attributes,
86
+ # please refer to the DICOM standard, PS3.4 C.6.1.1.5, Table C.6-4.
99
87
  #
100
- # === Notes
88
+ # @note Caution: Calling this method without parameters will instruct your PACS
89
+ # to return info on ALL images in the database!
90
+ # @param [Hash] query_params the query parameters to use
91
+ # @option query_params [String] 'GGGG,EEEE' a tag and value pair to be used in the query
101
92
  #
102
- # * Caution: Calling this method without parameters will instruct your PACS to return info on ALL images in the database!
103
- # * In addition to the above listed attributes, a number of "optional" attributes may be specified.
104
- # * For a general list of optional object instance level attributes, refer to the DICOM standard, PS3.4 C.6.1.1.5, Table C.6-4.
105
- #
106
- # === Examples
107
- #
108
- # node.find_images("0020,000D" => "1.2.840.1145.342", "0020,000E" => "1.3.6.1.4.1.2452.6.687844")
93
+ # @example Find all images belonging to a given study and series
94
+ # node.find_images('0020,000D' => '1.2.840.1145.342', '0020,000E' => '1.3.6.1.4.1.2452.6.687844')
109
95
  #
110
96
  def find_images(query_params={})
111
97
  # Study Root Query/Retrieve Information Model - FIND:
@@ -128,27 +114,25 @@ module DICOM
128
114
 
129
115
  # Queries a service class provider for patients that match the specified criteria.
130
116
  #
131
- # === Parameters
117
+ # === Instance level attributes for this query:
132
118
  #
133
- # * <tt>query_params</tt> -- A hash of parameters.
119
+ # * '0008,0052' (Query/Retrieve Level)
120
+ # * '0010,0010' (Patient's Name)
121
+ # * '0010,0020' (Patient ID)
122
+ # * '0010,0030' (Patient's Birth Date)
123
+ # * '0010,0040' (Patient's Sex)
134
124
  #
135
- # === Options
125
+ # In addition to the above listed attributes, a number of "optional" attributes
126
+ # may be specified. For a general list of optional object instance level attributes,
127
+ # please refer to the DICOM standard, PS3.4 C.6.1.1.2, Table C.6-1.
136
128
  #
137
- # * <tt>"0008,0052"</tt> -- Query/Retrieve Level
138
- # * <tt>"0010,0010"</tt> -- Patient's Name
139
- # * <tt>"0010,0020"</tt> -- Patient ID
140
- # * <tt>"0010,0030"</tt> -- Patient's Birth Date
141
- # * <tt>"0010,0040"</tt> -- Patient's Sex
129
+ # @note Caution: Calling this method without parameters will instruct your PACS
130
+ # to return info on ALL patients in the database!
131
+ # @param [Hash] query_params the query parameters to use
132
+ # @option query_params [String] 'GGGG,EEEE' a tag and value pair to be used in the query
142
133
  #
143
- # === Notes
144
- #
145
- # * Caution: Calling this method without parameters will instruct your PACS to return info on ALL patients in the database!
146
- # * In addition to the above listed attributes, a number of "optional" attributes may be specified.
147
- # * For a general list of optional patient level attributes, refer to the DICOM standard, PS3.4 C.6.1.1.2, Table C.6-1.
148
- #
149
- # === Examples
150
- #
151
- # node.find_patients("0010,0010" => "James*")
134
+ # @example Find all patients matching the given name
135
+ # node.find_patients('0010,0010' => 'James*')
152
136
  #
153
137
  def find_patients(query_params={})
154
138
  # Patient Root Query/Retrieve Information Model - FIND:
@@ -172,25 +156,23 @@ module DICOM
172
156
 
173
157
  # Queries a service class provider for series that match the specified criteria.
174
158
  #
175
- # === Parameters
176
- #
177
- # * <tt>query_params</tt> -- A hash of query parameters.
159
+ # === Instance level attributes for this query:
178
160
  #
179
- # === Options
161
+ # * '0008,0060' (Modality)
162
+ # * '0020,000E' (Series Instance UID)
163
+ # * '0020,0011' (Series Number)
180
164
  #
181
- # * <tt>"0008,0060"</tt> -- Modality
182
- # * <tt>"0020,000E"</tt> -- Series Instance UID
183
- # * <tt>"0020,0011"</tt> -- Series Number
165
+ # In addition to the above listed attributes, a number of "optional" attributes
166
+ # may be specified. For a general list of optional object instance level attributes,
167
+ # please refer to the DICOM standard, PS3.4 C.6.1.1.4, Table C.6-3.
184
168
  #
185
- # === Notes
169
+ # @note Caution: Calling this method without parameters will instruct your PACS
170
+ # to return info on ALL series in the database!
171
+ # @param [Hash] query_params the query parameters to use
172
+ # @option query_params [String] 'GGGG,EEEE' a tag and value pair to be used in the query
186
173
  #
187
- # * Caution: Calling this method without parameters will instruct your PACS to return info on ALL series in the database!
188
- # * In addition to the above listed attributes, a number of "optional" attributes may be specified.
189
- # * For a general list of optional series level attributes, refer to the DICOM standard, PS3.4 C.6.1.1.4, Table C.6-3.
190
- #
191
- # === Examples
192
- #
193
- # node.find_series("0020,000D" => "1.2.840.1145.342")
174
+ # @example Find all series belonging to the given study
175
+ # node.find_series('0020,000D' => '1.2.840.1145.342')
194
176
  #
195
177
  def find_series(query_params={})
196
178
  # Study Root Query/Retrieve Information Model - FIND:
@@ -215,29 +197,27 @@ module DICOM
215
197
 
216
198
  # Queries a service class provider for studies that match the specified criteria.
217
199
  #
218
- # === Parameters
219
- #
220
- # * <tt>query_params</tt> -- A hash of query parameters.
221
- #
222
- # === Options
223
- #
224
- # * <tt>"0008,0020"</tt> -- Study Date
225
- # * <tt>"0008,0030"</tt> -- Study Time
226
- # * <tt>"0008,0050"</tt> -- Accession Number
227
- # * <tt>"0010,0010"</tt> -- Patient's Name
228
- # * <tt>"0010,0020"</tt> -- Patient ID
229
- # * <tt>"0020,000D"</tt> -- Study Instance UID
230
- # * <tt>"0020,0010"</tt> -- Study ID
200
+ # === Instance level attributes for this query:
231
201
  #
232
- # === Notes
202
+ # * '0008,0020' (Study Date)
203
+ # * '0008,0030' (Study Time)
204
+ # * '0008,0050' (Accession Number)
205
+ # * '0010,0010' (Patient's Name)
206
+ # * '0010,0020' (Patient ID)
207
+ # * '0020,000D' (Study Instance UID)
208
+ # * '0020,0010' (Study ID)
233
209
  #
234
- # * Caution: Calling this method without parameters will instruct your PACS to return info on ALL studies in the database!
235
- # * In addition to the above listed attributes, a number of "optional" attributes may be specified.
236
- # * For a general list of optional study level attributes, refer to the DICOM standard, PS3.4 C.6.2.1.2, Table C.6-5.
210
+ # In addition to the above listed attributes, a number of "optional" attributes
211
+ # may be specified. For a general list of optional object instance level attributes,
212
+ # please refer to the DICOM standard, PS3.4 C.6.2.1.2, Table C.6-5.
237
213
  #
238
- # === Examples
214
+ # @note Caution: Calling this method without parameters will instruct your PACS
215
+ # to return info on ALL studies in the database!
216
+ # @param [Hash] query_params the query parameters to use
217
+ # @option query_params [String] 'GGGG,EEEE' a tag and value pair to be used in the query
239
218
  #
240
- # node.find_studies("0008,0020" => "20090604-", "0010,000D" => "123456789")
219
+ # @example Find all studies matching the given study date and patient's id
220
+ # node.find_studies('0008,0020' => '20090604-', '0010,0020' => '123456789')
241
221
  #
242
222
  def find_studies(query_params={})
243
223
  # Study Root Query/Retrieve Information Model - FIND:
@@ -266,25 +246,22 @@ module DICOM
266
246
 
267
247
  # Retrieves a DICOM file from a service class provider (SCP/PACS).
268
248
  #
269
- # === Restrictions
270
- #
271
- # * This method has never actually been tested, and as such, it is probably not working! Feedback is welcome.
249
+ # === Instance level attributes for this procedure:
272
250
  #
273
- # === Parameters
251
+ # * '0008,0018' (SOP Instance UID)
252
+ # * '0008,0052' (Query/Retrieve Level)
253
+ # * '0020,000D' (Study Instance UID)
254
+ # * '0020,000E' (Series Instance UID)
274
255
  #
275
- # * <tt>path</tt> -- The path where incoming files will be saved.
276
- # * <tt>options</tt> -- A hash of parameters.
256
+ # @note This method has never actually been tested, and as such,
257
+ # it is probably not working! Feedback is welcome.
277
258
  #
278
- # === Options
259
+ # @param [String] path the directory where incoming files will be saved
260
+ # @param [Hash] options the options to use for retrieving the DICOM object
261
+ # @option options [String] 'GGGG,EEEE' a tag and value pair to be used for the procedure
279
262
  #
280
- # * <tt>"0008,0018"</tt> -- SOP Instance UID
281
- # * <tt>"0008,0052"</tt> -- Query/Retrieve Level
282
- # * <tt>"0020,000D"</tt> -- Study Instance UID
283
- # * <tt>"0020,000E"</tt> -- Series Instance UID
284
- #
285
- # === Examples
286
- #
287
- # node.get_image("c:/dicom/", "0008,0018" => sop_uid, "0020,000D" => study_uid, "0020,000E" => series_uid)
263
+ # @example Retrieve a file as specified by its UIDs
264
+ # node.get_image('c:/dicom/', '0008,0018' => sop_uid, '0020,000D' => study_uid, '0020,000E' => series_uid)
288
265
  #
289
266
  def get_image(path, options={})
290
267
  # Study Root Query/Retrieve Information Model - GET:
@@ -299,25 +276,22 @@ module DICOM
299
276
 
300
277
  # Moves a single image to a DICOM server.
301
278
  #
302
- # === Notes
303
- #
304
- # * This DICOM node must be a third party (not yourself).
305
- #
306
- # === Parameters
307
- #
308
- # * <tt>destination</tt> -- String. The name (AE title) of the DICOM server which will receive the file.
309
- # * <tt>options</tt> -- A hash of parameters.
279
+ # This DICOM node must be a third party (i.e. not the client instance you
280
+ # are requesting the move with!).
310
281
  #
311
- # === Options
282
+ # === Instance level attributes for this procedure:
312
283
  #
313
- # * <tt>"0008,0018"</tt> -- SOP Instance UID
314
- # * <tt>"0008,0052"</tt> -- Query/Retrieve Level
315
- # * <tt>"0020,000D"</tt> -- Study Instance UID
316
- # * <tt>"0020,000E"</tt> -- Series Instance UID
284
+ # * '0008,0018' (SOP Instance UID)
285
+ # * '0008,0052' (Query/Retrieve Level)
286
+ # * '0020,000D' (Study Instance UID)
287
+ # * '0020,000E' (Series Instance UID)
317
288
  #
318
- # === Examples
289
+ # @param [String] destination the AE title of the DICOM server which will receive the file
290
+ # @param [Hash] options the options to use for moving the DICOM object
291
+ # @option options [String] 'GGGG,EEEE' a tag and value pair to be used for the procedure
319
292
  #
320
- # node.move_image("SOME_SERVER", "0008,0018" => sop_uid, "0020,000D" => study_uid, "0020,000E" => series_uid)
293
+ # @example Move an image from e.q. a PACS to another SCP on the network
294
+ # node.move_image('SOME_SERVER', '0008,0018' => sop_uid, '0020,000D' => study_uid, '0020,000E' => series_uid)
321
295
  #
322
296
  def move_image(destination, options={})
323
297
  # Study Root Query/Retrieve Information Model - MOVE:
@@ -332,24 +306,21 @@ module DICOM
332
306
 
333
307
  # Move an entire study to a DICOM server.
334
308
  #
335
- # === Notes
336
- #
337
- # * This DICOM node must be a third party (not yourself).
309
+ # This DICOM node must be a third party (i.e. not the client instance you
310
+ # are requesting the move with!).
338
311
  #
339
- # === Parameters
312
+ # === Instance level attributes for this procedure:
340
313
  #
341
- # * <tt>destination</tt> -- String. The name (AE title) of the DICOM server which will receive the files.
342
- # * <tt>options</tt> -- A hash of parameters.
314
+ # * '0008,0052' (Query/Retrieve Level)
315
+ # * '0010,0020' (Patient ID)
316
+ # * '0020,000D' (Study Instance UID)
343
317
  #
344
- # === Options
318
+ # @param [String] destination the AE title of the DICOM server which will receive the files
319
+ # @param [Hash] options the options to use for moving the DICOM objects
320
+ # @option options [String] 'GGGG,EEEE' a tag and value pair to be used for the procedure
345
321
  #
346
- # * <tt>"0008,0052"</tt> -- Query/Retrieve Level
347
- # * <tt>"0010,0020"</tt> -- Patient ID
348
- # * <tt>"0020,000D"</tt> -- Study Instance UID
349
- #
350
- # === Examples
351
- #
352
- # node.move_study("SOME_SERVER", "0010,0020" => pat_id, "0020,000D" => study_uid)
322
+ # @example Move an entire study from e.q. a PACS to another SCP on the network
323
+ # node.move_study('SOME_SERVER', '0010,0020' => pat_id, '0020,000D' => study_uid)
353
324
  #
354
325
  def move_study(destination, options={})
355
326
  # Study Root Query/Retrieve Information Model - MOVE:
@@ -364,13 +335,9 @@ module DICOM
364
335
 
365
336
  # Sends one or more DICOM files to a service class provider (SCP/PACS).
366
337
  #
367
- # === Parameters
368
- #
369
- # * <tt>files</tt> -- A single file path or an array of paths, or a DObject or an array of DObject instances.
370
- #
371
- # === Examples
372
- #
373
- # node.send("my_file.dcm")
338
+ # @param [Array<String, DObject>, String, DObject] files a single file path or an array of paths, alternatively a DObject or an array of DObject instances
339
+ # @example Send a DICOM file to a storage server
340
+ # node.send('my_file.dcm')
374
341
  #
375
342
  def send(files)
376
343
  # Prepare the DICOM object(s):
@@ -393,7 +360,8 @@ module DICOM
393
360
  end
394
361
  end
395
362
 
396
- # Tests the connection to the server in a very simple way by negotiating an association and then releasing it.
363
+ # Tests the connection to the server in a very simple way by negotiating
364
+ # an association and then releasing it.
397
365
  #
398
366
  def test
399
367
  logger.info("TESTING CONNECTION...")
@@ -418,7 +386,6 @@ module DICOM
418
386
  end
419
387
 
420
388
 
421
- # Following methods are private:
422
389
  private
423
390
 
424
391
 
@@ -748,18 +715,15 @@ module DICOM
748
715
  else
749
716
  # We still consider the request 'approved' if at least one context were accepted:
750
717
  @request_approved = true if @approved_syntaxes.length > 0
751
-
752
718
  logger.error("One or more of your presentation contexts were denied by host #{@host_ae}!")
753
-
754
719
  @approved_syntaxes.each_pair do |key, value|
755
- sntx_k = LIBRARY.get_syntax_description(key)
756
- sntx_v = LIBRARY.get_syntax_description(value[1])
720
+ sntx_k = (LIBRARY.uid(key) ? LIBRARY.uid(key).name : 'Unknown UID!')
721
+ sntx_v = (LIBRARY.uid(value[1]) ? LIBRARY.uid(value[1]).name : 'Unknown UID!')
757
722
  logger.info("APPROVED: #{sntx_k} (#{sntx_v})")
758
723
  end
759
-
760
724
  rejected.each_pair do |key, value|
761
- sntx_k = LIBRARY.get_syntax_description(key)
762
- sntx_v = LIBRARY.get_syntax_description(value[1])
725
+ sntx_k = (LIBRARY.uid(key) ? LIBRARY.uid(key).name : 'Unknown UID!')
726
+ sntx_v = (LIBRARY.uid(value[1]) ? LIBRARY.uid(value[1]).name : 'Unknown UID!')
763
727
  logger.error("REJECTED: #{sntx_k} (#{sntx_v})")
764
728
  end
765
729
  end
@@ -918,23 +882,31 @@ module DICOM
918
882
  def set_user_information_array
919
883
  @user_information = [
920
884
  [ITEM_MAX_LENGTH, "UL", @max_package_size],
921
- [ITEM_IMPLEMENTATION_UID, "STR", UID],
885
+ [ITEM_IMPLEMENTATION_UID, "STR", UID_ROOT],
922
886
  [ITEM_IMPLEMENTATION_VERSION, "STR", NAME]
923
887
  ]
924
888
  end
925
889
 
890
+ # Checks if an association has been established.
891
+ #
926
892
  def association_established?
927
893
  @association == true
928
894
  end
929
895
 
896
+ # Checks if a request has been approved.
897
+ #
930
898
  def request_approved?
931
899
  @request_approved == true
932
900
  end
933
901
 
902
+ # Extracts the presentation context id from the approved syntax.
903
+ #
934
904
  def presentation_context_id
935
905
  @approved_syntaxes.to_a.first[1][0] # ID of first (and only) syntax in this Hash.
936
906
  end
937
907
 
908
+ # Sets the data_elements instance array with the given options.
909
+ #
938
910
  def set_data_elements(options)
939
911
  @data_elements = []
940
912
  options.keys.sort.each do |tag|
@@ -1,288 +1,220 @@
1
- module DICOM
2
-
3
- # This class contains methods that interact with Ruby DICOM's dictionary.
4
- #
5
- class DLibrary
6
-
7
- # A hash with element name strings as key and method name symbols as value.
8
- attr_reader :methods_from_names
9
- # A hash with element method name symbols as key and name strings as value.
10
- attr_reader :names_from_methods
11
- # A hash containing tags as key and an array as value, where the array contains data element vr and name.
12
- attr_reader :tags
13
- # A hash containing UIDs as key and an array as value, where the array contains name and type.
14
- attr_reader :uid
15
-
16
- # Creates a DLibrary instance.
17
- #
18
- def initialize
19
- # Load the data elements hash, where the keys are tag strings, and values
20
- # are two-element arrays [vr, name] (where vr itself is an array of 1-3 elements):
21
- @tags = Dictionary.load_data_elements
22
- # Load UID hash (DICOM unique identifiers), where the keys are UID strings,
23
- # and values are two-element arrays [description, type]:
24
- @uid = Dictionary.load_uid
25
- create_method_conversion_tables
26
- end
27
-
28
- # Returns the method (symbol) corresponding to the specified string value (which may represent a element tag, name or method).
29
- # Returns nil if no match is found.
30
- #
31
- def as_method(value)
32
- case true
33
- when value.tag?
34
- name, vr = get_name_vr(value)
35
- @methods_from_names[name]
36
- when value.dicom_name?
37
- @methods_from_names[value]
38
- when value.dicom_method?
39
- @names_from_methods.has_key?(value.to_sym) ? value.to_sym : nil
40
- else
41
- nil
42
- end
43
- end
44
-
45
- # Returns the name (string) corresponding to the specified string value (which may represent a element tag, name or method).
46
- # Returns nil if no match is found.
47
- #
48
- def as_name(value)
49
- case true
50
- when value.tag?
51
- name, vr = get_name_vr(value)
52
- name
53
- when value.dicom_name?
54
- @methods_from_names.has_key?(value) ? value.to_s : nil
55
- when value.dicom_method?
56
- @names_from_methods[value.to_sym]
57
- else
58
- nil
59
- end
60
- end
61
-
62
- # Returns the tag (string) corresponding to the specified string value (which may represent a element tag, name or method).
63
- # Returns nil if no match is found.
64
- #
65
- def as_tag(value)
66
- case true
67
- when value.tag?
68
- name, vr = get_name_vr(value)
69
- name.nil? ? nil : value
70
- when value.dicom_name?
71
- get_tag(value)
72
- when value.dicom_method?
73
- get_tag(@names_from_methods[value.to_sym])
74
- else
75
- nil
76
- end
77
- end
78
-
79
- # Checks whether a given string is a valid transfer syntax or not.
80
- # Returns true if valid, false if not.
81
- #
82
- # === Parameters
83
- #
84
- # * <tt>uid</tt> -- String. A DICOM UID value which will be matched against known transfer syntaxes.
85
- #
86
- def check_ts_validity(uid)
87
- result = false
88
- value = @uid[uid]
89
- if value
90
- result = true if value[1] == "Transfer Syntax"
91
- end
92
- return result
93
- end
94
-
95
- # Extracts, and returns, all transfer syntaxes and SOP Classes from the dictionary,
96
- # in the form of a transfer syntax hash and a sop class hash.
97
- #
98
- # Both hashes have UIDs as keys and their descriptions as values.
99
- #
100
- def extract_transfer_syntaxes_and_sop_classes
101
- transfer_syntaxes = Hash.new
102
- sop_classes = Hash.new
103
- @uid.each_pair do |key, value|
104
- if value[1] == "Transfer Syntax"
105
- transfer_syntaxes[key] = value[0]
106
- elsif value[1] == "SOP Class"
107
- sop_classes[key] = value[0]
108
- end
109
- end
110
- return transfer_syntaxes, sop_classes
111
- end
112
-
113
- # Checks if the specified transfer syntax implies the presence of pixel compression.
114
- # Returns true if pixel compression is implied, false if not.
115
- #
116
- # === Parameters
117
- #
118
- # * <tt>uid</tt> -- String. A DICOM UID value.
119
- #
120
- def get_compression(uid)
121
- raise ArgumentError, "Expected String, got #{uid.class}" unless uid.is_a?(String)
122
- result = false
123
- value = @uid[uid]
124
- if value
125
- first_word = value[0].split(" ").first
126
- result = true if value[1] == "Transfer Syntax" and not ["Implicit", "Explicit"].include?(first_word)
127
- end
128
- return result
129
- end
130
-
131
- # Determines, and returns, the name and vr of the data element which the specified tag belongs to.
132
- # Values are retrieved from the Ruby DICOM dictionary if a match is found.
133
- #
134
- # === Notes
135
- #
136
- # * Private tags will have their names listed as "Private".
137
- # * Non-private tags that are not found in the dictionary will be listed as "Unknown".
138
- #
139
- # === Parameters
140
- #
141
- # * <tt>tag</tt> -- String. A data element tag.
142
- #
143
- def get_name_vr(tag)
144
- if tag.private? and tag.element != GROUP_LENGTH
145
- name = "Private"
146
- vr = "UN"
147
- else
148
- # Check the dictionary:
149
- values = @tags[tag]
150
- if values
151
- name = values[1]
152
- vr = values[0][0]
153
- else
154
- # For the tags that are not recognised, we need to do some additional testing to see if it is one of the special cases:
155
- if tag.element == GROUP_LENGTH
156
- # Group length:
157
- name = "Group Length"
158
- vr = "UL"
159
- elsif tag[0..6] == "0020,31"
160
- # Source Image ID's (Retired):
161
- values = @tags["0020,31xx"]
162
- name = values[1]
163
- vr = values[0][0]
164
- elsif tag.group == "1000" and tag.element =~ /\A\h{3}[0-5]\z/
165
- # Group 1000,xxx[0-5] (Retired):
166
- new_tag = tag.group + "xx" + tag.element[3..3]
167
- values = @tags[new_tag]
168
- elsif tag.group == "1010"
169
- # Group 1010,xxxx (Retired):
170
- new_tag = tag.group + "xxxx"
171
- values = @tags[new_tag]
172
- elsif tag[0..1] == "50" or tag[0..1] == "60"
173
- # Group 50xx (Retired) and 60xx:
174
- new_tag = tag[0..1]+"xx"+tag[4..8]
175
- values = @tags[new_tag]
176
- if values
177
- name = values[1]
178
- vr = values[0][0]
179
- end
180
- elsif tag[0..1] == "7F" and tag[5..6] == "00"
181
- # Group 7Fxx,00[10,11,20,30,40] (Retired):
182
- new_tag = tag[0..1]+"xx"+tag[4..8]
183
- values = @tags[new_tag]
184
- if values
185
- name = values[1]
186
- vr = values[0][0]
187
- end
188
- end
189
- # If none of the above checks yielded a result, the tag is unknown:
190
- unless name
191
- name = "Unknown"
192
- vr = "UN"
193
- end
194
- end
195
- end
196
- return name, vr
197
- end
198
-
199
- # Returns the tag that matches the supplied data element name, by searching the Ruby DICOM dictionary.
200
- # Returns nil if no match is found.
201
- #
202
- # === Parameters
203
- #
204
- # * <tt>name</tt> -- String. A data element name.
205
- #
206
- def get_tag(name)
207
- tag = nil
208
- name = name.to_s.downcase
209
- @tag_name_pairs_cache ||= Hash.new
210
- return @tag_name_pairs_cache[name] unless @tag_name_pairs_cache[name].nil?
211
- @tags.each_pair do |key, value|
212
- next unless value[1].downcase == name
213
- tag = key
214
- break
215
- end
216
- @tag_name_pairs_cache[name]=tag
217
- return tag
218
- end
219
-
220
- # Returns the description/name of a specified UID (i.e. a transfer syntax or SOP class).
221
- # Returns nil if no match is found
222
- #
223
- # === Parameters
224
- #
225
- # * <tt>uid</tt> -- String. A DICOM UID value.
226
- #
227
- def get_syntax_description(uid)
228
- name = nil
229
- value = @uid[uid]
230
- name = value[0] if value
231
- return name
232
- end
233
-
234
- # Checks the validity of the specified transfer syntax UID and determines the
235
- # encoding settings (explicitness & endianness) associated with this value.
236
- # The results are returned as 3 booleans: validity, explicitness & endianness.
237
- #
238
- # === Parameters
239
- #
240
- # * <tt>uid</tt> -- String. A DICOM UID value.
241
- #
242
- def process_transfer_syntax(uid)
243
- valid = check_ts_validity(uid)
244
- case uid
245
- # Some variations with uncompressed pixel data:
246
- when IMPLICIT_LITTLE_ENDIAN
247
- explicit = false
248
- endian = false
249
- when EXPLICIT_LITTLE_ENDIAN
250
- explicit = true
251
- endian = false
252
- when "1.2.840.10008.1.2.1.99" # Deflated Explicit VR, Little Endian
253
- # Note: Has this transfer syntax been tested yet?
254
- explicit = true
255
- endian = false
256
- when EXPLICIT_BIG_ENDIAN
257
- explicit = true
258
- endian = true
259
- else
260
- # For everything else, assume compressed pixel data, with Explicit VR, Little Endian:
261
- explicit = true
262
- endian = false
263
- end
264
- return valid, explicit, endian
265
- end
266
-
267
-
268
- private
269
-
270
-
271
- # Creates the instance hashes that are used for name to/from method conversion.
272
- #
273
- def create_method_conversion_tables
274
- if @methods_from_names.nil?
275
- @methods_from_names = Hash.new
276
- @names_from_methods = Hash.new
277
- # Fill the hashes:
278
- @tags.each_pair do |key, value|
279
- name = value[1]
280
- method_name = name.dicom_methodize
281
- @methods_from_names[name] = method_name.to_sym
282
- @names_from_methods[method_name.to_sym] = name
283
- end
284
- end
285
- end
286
-
287
- end
1
+ module DICOM
2
+
3
+ # The DLibrary class contains methods for interacting with ruby-dicom's dictionary data.
4
+ #
5
+ # In practice, the library is for internal use and not accessed by the user. However, a
6
+ # a library instance is available through the DICOM::LIBRARY constant.
7
+ #
8
+ # @example Get a dictionary element corresponding to the given tag
9
+ # element = DICOM::LIBRARY.element('0010,0010')
10
+ #
11
+ class DLibrary
12
+
13
+ # A hash with element name strings as key and method name symbols as value.
14
+ attr_reader :methods_from_names
15
+ # A hash with element method name symbols as key and name strings as value.
16
+ attr_reader :names_from_methods
17
+
18
+ # Creates a DLibrary instance.
19
+ #
20
+ def initialize
21
+ # Create instance hashes used for dictionary data and method conversion:
22
+ @elements = Hash.new
23
+ @uids = Hash.new
24
+ @methods_from_names = Hash.new
25
+ @names_from_methods = Hash.new
26
+ # Load the elements dictionary:
27
+ File.open("#{ROOT_DIR}/dictionary/elements.txt").each do |record|
28
+ load_element(record)
29
+ end
30
+ # Load the unique identifiers dictionary:
31
+ File.open("#{ROOT_DIR}/dictionary/uids.txt").each do |record|
32
+ fields = record.split("\t")
33
+ # Store the uids in a hash with uid-value as key and the uid instance as value:
34
+ @uids[fields[0]] = UID.new(fields[0], fields[1], fields[2].rstrip, fields[3].rstrip)
35
+ end
36
+ end
37
+
38
+ # Adds a custom dictionary file to the ruby-dicom element dictionary.
39
+ #
40
+ # @note The format of the dictionary is a tab-separated text file with 5 columns:
41
+ # * Tag, Name, VR, VM & Retired status
42
+ # * For samples check out ruby-dicom's element dictionaries in the git repository
43
+ # @param [String] file The path to the dictionary file to be added
44
+ #
45
+ def add_element_dictionary(file)
46
+ File.open(file).each do |record|
47
+ load_element(record)
48
+ end
49
+ end
50
+
51
+
52
+ # Gives the method (symbol) corresponding to the specified element string value.
53
+ #
54
+ # @param [String] value an element tag, element name or an element's method name
55
+ # @return [Symbol, NilClass] the matched element method, or nil if no match is made
56
+ #
57
+ def as_method(value)
58
+ case true
59
+ when value.tag?
60
+ @methods_from_names[element(value).name]
61
+ when value.dicom_name?
62
+ @methods_from_names[value]
63
+ when value.dicom_method?
64
+ @names_from_methods.has_key?(value.to_sym) ? value.to_sym : nil
65
+ else
66
+ nil
67
+ end
68
+ end
69
+
70
+ # Gives the name corresponding to the specified element string value.
71
+ #
72
+ # @param [String] value an element tag, element name or an element's method name
73
+ # @return [String, NilClass] the matched element name, or nil if no match is made
74
+ #
75
+ def as_name(value)
76
+ case true
77
+ when value.tag?
78
+ element(value).name
79
+ when value.dicom_name?
80
+ @methods_from_names.has_key?(value) ? value.to_s : nil
81
+ when value.dicom_method?
82
+ @names_from_methods[value.to_sym]
83
+ else
84
+ nil
85
+ end
86
+ end
87
+
88
+ # Gives the tag corresponding to the specified element string value.
89
+ #
90
+ # @param [String] value an element tag, element name or an element's method name
91
+ # @return [String, NilClass] the matched element tag, or nil if no match is made
92
+ #
93
+ def as_tag(value)
94
+ case true
95
+ when value.tag?
96
+ element(value) ? value : nil
97
+ when value.dicom_name?
98
+ get_tag(value)
99
+ when value.dicom_method?
100
+ get_tag(@names_from_methods[value.to_sym])
101
+ else
102
+ nil
103
+ end
104
+ end
105
+
106
+ # Identifies the DictionaryElement that corresponds to the given tag.
107
+ #
108
+ # @note If a given tag doesn't return a dictionary match, a new DictionaryElement is created.
109
+ # * For private tags, a name 'Private' and VR 'UN' is assigned
110
+ # * For unknown tags, a name 'Unknown' and VR 'UN' is assigned
111
+ # @param [String] tag the tag of the element
112
+ # @return [DictionaryElement] a corresponding DictionaryElement
113
+ #
114
+ def element(tag)
115
+ element = @elements[tag]
116
+ unless element
117
+ if tag.group_length?
118
+ element = DictionaryElement.new(tag, 'Group Length', ['UL'], '1', '')
119
+ else
120
+ if tag.private?
121
+ element = DictionaryElement.new(tag, 'Private', ['UN'], '1', '')
122
+ else
123
+ if !(de = @elements["#{tag[0..3]},xxx#{tag[8]}"]).nil? # 1000,xxxh
124
+ element = DictionaryElement.new(tag, de.name, de.vrs, de.vm, de.retired)
125
+ elsif !(de = @elements["#{tag[0..3]},xxxx"]).nil? # 1010,xxxx
126
+ element = DictionaryElement.new(tag, de.name, de.vrs, de.vm, de.retired)
127
+ elsif !(de = @elements["#{tag[0..1]}xx,#{tag[5..8]}"]).nil? # hhxx,hhhh
128
+ element = DictionaryElement.new(tag, de.name, de.vrs, de.vm, de.retired)
129
+ elsif !(de = @elements["#{tag[0..6]}x#{tag[8]}"]).nil? # 0028,hhxh
130
+ element = DictionaryElement.new(tag, de.name, de.vrs, de.vm, de.retired)
131
+ else
132
+ # We are facing an unknown (but not private) tag:
133
+ element = DictionaryElement.new(tag, 'Unknown', ['UN'], '1', '')
134
+ end
135
+ end
136
+ end
137
+ end
138
+ return element
139
+ end
140
+
141
+ # Extracts, and returns, all transfer syntaxes and SOP Classes from the dictionary.
142
+ #
143
+ # @return [Array<Hash, Hash>] transfer syntax and sop class hashes, each with uid as key and name as value
144
+ #
145
+ def extract_transfer_syntaxes_and_sop_classes
146
+ transfer_syntaxes = Hash.new
147
+ sop_classes = Hash.new
148
+ @uids.each_value do |uid|
149
+ if uid.transfer_syntax?
150
+ transfer_syntaxes[uid.value] = uid.name
151
+ elsif uid.sop_class?
152
+ sop_classes[uid.value] = uid.name
153
+ end
154
+ end
155
+ return transfer_syntaxes, sop_classes
156
+ end
157
+
158
+ # Gives the tag that matches the supplied data element name, by searching the element dictionary.
159
+ #
160
+ # @param [String] name a data element name
161
+ # @return [String, NilClass] the corresponding element tag, or nil if no match is made
162
+ #
163
+ def get_tag(name)
164
+ tag = nil
165
+ name = name.to_s.downcase
166
+ @tag_name_pairs_cache ||= Hash.new
167
+ return @tag_name_pairs_cache[name] unless @tag_name_pairs_cache[name].nil?
168
+ @elements.each_value do |element|
169
+ next unless element.name.downcase == name
170
+ tag = element.tag
171
+ break
172
+ end
173
+ @tag_name_pairs_cache[name]=tag
174
+ return tag
175
+ end
176
+
177
+ # Determines the name and vr of the element which the specified tag belongs to,
178
+ # based on a lookup in the element data dictionary.
179
+ #
180
+ # @note If a given tag doesn't return a dictionary match, the following values are assigned:
181
+ # * For private tags: name 'Private' and VR 'UN'
182
+ # * For unknown (non-private) tags: name 'Unknown' and VR 'UN'
183
+ # @param [String] tag an element's tag
184
+ # @return [Array<String, String>] the name and value representation corresponding to the given tag
185
+ #
186
+ def name_and_vr(tag)
187
+ de = element(tag)
188
+ return de.name, de.vr
189
+ end
190
+
191
+ # Identifies the UID that corresponds to the given value.
192
+ #
193
+ # @param [String] value the unique identifier value
194
+ # @return [UID, NilClass] a corresponding UID instance, or nil (if no match is made)
195
+ #
196
+ def uid(value)
197
+ @uids[value]
198
+ end
199
+
200
+
201
+ private
202
+
203
+
204
+ # Loads an element to the dictionary from an element string record.
205
+ #
206
+ # @param [String] record a tab-separated string line as extracted from a dictionary file
207
+ #
208
+ def load_element(record)
209
+ fields = record.split("\t")
210
+ # Store the elements in a hash with tag as key and the element instance as value:
211
+ element = DictionaryElement.new(fields[0], fields[1], fields[2].split(","), fields[3].rstrip, fields[4].rstrip)
212
+ @elements[fields[0]] = element
213
+ # Populate the method conversion hashes with element data:
214
+ method = element.name.to_element_method
215
+ @methods_from_names[element.name] = method
216
+ @names_from_methods[method] = element.name
217
+ end
218
+
219
+ end
288
220
  end