dicom 0.9.3 → 0.9.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|