dicom 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +312 -290
- data/COPYING +674 -674
- data/Gemfile +3 -0
- data/dicom.gemspec +31 -0
- data/lib/dicom.rb +53 -51
- data/lib/dicom/anonymizer.rb +98 -123
- data/lib/dicom/audit_trail.rb +104 -116
- data/lib/dicom/constants.rb +219 -170
- data/lib/dicom/d_client.rb +122 -150
- data/lib/dicom/d_library.rb +219 -287
- data/lib/dicom/d_object.rb +451 -539
- data/lib/dicom/d_read.rb +151 -245
- data/lib/dicom/d_server.rb +329 -359
- data/lib/dicom/d_write.rb +327 -395
- data/lib/dicom/deprecated.rb +1 -72
- data/lib/dicom/dictionary/elements.txt +3646 -0
- data/lib/dicom/dictionary/uids.txt +334 -0
- data/lib/dicom/dictionary_element.rb +61 -0
- data/lib/dicom/element.rb +278 -218
- data/lib/dicom/elemental.rb +21 -27
- data/lib/dicom/file_handler.rb +121 -121
- data/lib/dicom/image_item.rb +819 -861
- data/lib/dicom/image_processor.rb +24 -15
- data/lib/dicom/image_processor_mini_magick.rb +21 -23
- data/lib/dicom/image_processor_r_magick.rb +39 -34
- data/lib/dicom/item.rb +133 -120
- data/lib/dicom/link.rb +1531 -1532
- data/lib/dicom/logging.rb +155 -158
- data/lib/dicom/parent.rb +782 -847
- data/lib/dicom/ruby_extensions.rb +248 -229
- data/lib/dicom/sequence.rb +109 -92
- data/lib/dicom/stream.rb +480 -511
- data/lib/dicom/uid.rb +82 -0
- data/lib/dicom/variables.rb +9 -9
- data/lib/dicom/version.rb +5 -5
- data/rakefile.rb +29 -0
- metadata +130 -76
- data/lib/dicom/dictionary.rb +0 -3280
data/lib/dicom/d_client.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
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
|
-
#
|
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
|
-
# ===
|
92
|
-
#
|
93
|
-
# * <tt>query_params</tt> -- A hash of query parameters.
|
79
|
+
# === Instance level attributes for this query:
|
94
80
|
#
|
95
|
-
#
|
81
|
+
# * '0008,0018' (SOP Instance UID)
|
82
|
+
# * '0020,0013' (Instance Number)
|
96
83
|
#
|
97
|
-
#
|
98
|
-
#
|
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
|
-
#
|
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
|
-
#
|
103
|
-
#
|
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
|
-
# ===
|
117
|
+
# === Instance level attributes for this query:
|
132
118
|
#
|
133
|
-
# *
|
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
|
-
#
|
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
|
-
#
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
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
|
-
#
|
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
|
-
# ===
|
176
|
-
#
|
177
|
-
# * <tt>query_params</tt> -- A hash of query parameters.
|
159
|
+
# === Instance level attributes for this query:
|
178
160
|
#
|
179
|
-
#
|
161
|
+
# * '0008,0060' (Modality)
|
162
|
+
# * '0020,000E' (Series Instance UID)
|
163
|
+
# * '0020,0011' (Series Number)
|
180
164
|
#
|
181
|
-
#
|
182
|
-
#
|
183
|
-
#
|
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
|
-
#
|
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
|
-
#
|
188
|
-
#
|
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
|
-
# ===
|
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
|
-
#
|
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
|
-
#
|
235
|
-
#
|
236
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
# ===
|
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
|
-
#
|
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
|
-
#
|
276
|
-
#
|
256
|
+
# @note This method has never actually been tested, and as such,
|
257
|
+
# it is probably not working! Feedback is welcome.
|
277
258
|
#
|
278
|
-
#
|
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
|
-
#
|
281
|
-
#
|
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
|
-
#
|
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
|
-
# ===
|
282
|
+
# === Instance level attributes for this procedure:
|
312
283
|
#
|
313
|
-
# *
|
314
|
-
# *
|
315
|
-
# *
|
316
|
-
# *
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
# ===
|
312
|
+
# === Instance level attributes for this procedure:
|
340
313
|
#
|
341
|
-
# *
|
342
|
-
# *
|
314
|
+
# * '0008,0052' (Query/Retrieve Level)
|
315
|
+
# * '0010,0020' (Patient ID)
|
316
|
+
# * '0020,000D' (Study Instance UID)
|
343
317
|
#
|
344
|
-
#
|
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
|
-
#
|
347
|
-
#
|
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
|
-
#
|
368
|
-
#
|
369
|
-
#
|
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
|
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.
|
756
|
-
sntx_v = LIBRARY.
|
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.
|
762
|
-
sntx_v = LIBRARY.
|
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",
|
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|
|
data/lib/dicom/d_library.rb
CHANGED
@@ -1,288 +1,220 @@
|
|
1
|
-
module DICOM
|
2
|
-
|
3
|
-
#
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# A hash
|
14
|
-
attr_reader :
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
@
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
#
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
#
|
142
|
-
#
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
#
|
205
|
-
#
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
@
|
217
|
-
|
218
|
-
|
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
|