dicom 0.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +15 -2
- data/DOCUMENTATION +32 -9
- data/README +5 -2
- data/lib/DLibrary.rb +15 -0
- data/lib/DObject.rb +256 -82
- data/lib/DRead.rb +152 -28
- data/lib/Dictionary.rb +38 -0
- metadata +41 -34
data/CHANGELOG
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
= 0.3
|
2
|
+
|
3
|
+
=== 12th October, 2008
|
4
|
+
|
5
|
+
* The DRead class is now able to keep track of the position of the tags inside the hierarchy of sequences and items.
|
6
|
+
* DObject class has seen a number of improvements to allow taking advantage of this hierarchy awareness:
|
7
|
+
* Method get_pos() now can take an array of positions as an argument when searching for tags,
|
8
|
+
meaning it will only search for hits amongst the positions provided.
|
9
|
+
* Method print() has been improved with new options to visualize the tree structure of the DICOM file.
|
10
|
+
* Method print() is able to print to file as well. The text file will be put in the same folder as the DICOM file.
|
11
|
+
* New method below(), which lets you specify a sequence or item, and this method will return the position
|
12
|
+
of all tags contained in this sequence/item.
|
13
|
+
|
1
14
|
= 0.2
|
2
15
|
|
3
16
|
=== 10th August, 2008
|
@@ -6,8 +19,8 @@
|
|
6
19
|
* New DLibrary class which handles all interaction with the dictionary.
|
7
20
|
* Dictionary can be loaded before reading files, which will considerably speed up the process if reading multiple files.
|
8
21
|
* Reading compressed pixel data into an RMagick object is supported in principle, although it lacks proper testing at this point.
|
9
|
-
* DRead class is more resistant to breaking if it is handed a faulty file to read.
|
10
|
-
* Added option to load DICOM object verbose or silent.
|
22
|
+
* DRead class is more resistant to breaking if it is handed a faulty file to read, and will return an error message instead of halting execution.
|
23
|
+
* Added option to load DICOM object in verbose or silent mode.
|
11
24
|
|
12
25
|
= 0.1
|
13
26
|
|
data/DOCUMENTATION
CHANGED
@@ -15,7 +15,9 @@ PUBLIC CLASS METHODS
|
|
15
15
|
new()
|
16
16
|
|
17
17
|
Initialize a new Library (dictionary) object.
|
18
|
-
Useful if you want to make a script that reads hundreds or thousands of DICOM files,
|
18
|
+
Useful if you want to make a script that reads hundreds or thousands of DICOM files,
|
19
|
+
because you can save time by loading the library one time at startup instead of
|
20
|
+
having the library being loaded for each DICOM file being read.
|
19
21
|
Example:
|
20
22
|
lib = DLibrary.new()
|
21
23
|
|
@@ -35,11 +37,21 @@ PUBLIC CLASS METHODS
|
|
35
37
|
|
36
38
|
PUBLIC INSTANCE METHODS
|
37
39
|
|
40
|
+
below(id, *options)
|
41
|
+
Returns the positions of all tags inside the hierarchy of a sequence or an item.
|
42
|
+
This is useful if you later want to get the position(s) of a certain tag,
|
43
|
+
restricted to the positions inside the given sequence or item.
|
44
|
+
Example 1: (Return all tag positions that is contained in the following sequence)
|
45
|
+
pos = obj.below("3006,0082")
|
46
|
+
Example 2: (Return all tag positions that is contained only directly beneath the following sequence)
|
47
|
+
pos = obj.below("3006,0082", next_only=true)
|
48
|
+
|
38
49
|
get_frames()
|
39
50
|
Returns the number of frames present in the image data in the DICOM file.
|
40
51
|
|
41
52
|
get_image_magick()
|
42
|
-
Returns an array of RMagick image objects, where the size of the array corresponds
|
53
|
+
Returns an array of RMagick image objects, where the size of the array corresponds
|
54
|
+
with the number of frames in the image data.
|
43
55
|
To call this method the user needs to have performed " require 'RMagick' " in advance.
|
44
56
|
Example (retrieve object and display first frame):
|
45
57
|
require 'RMagick'
|
@@ -58,8 +70,12 @@ PUBLIC INSTANCE METHODS
|
|
58
70
|
get_image_pos()
|
59
71
|
Returns the index(es) of the tag(s) that contain image data.
|
60
72
|
|
61
|
-
get_pos(
|
62
|
-
Returns the index(es) of the tag(s) in the DICOM file that match the supplied tag
|
73
|
+
get_pos(id, *options)
|
74
|
+
Returns the index(es) of the tag(s) in the DICOM file that match the supplied tag ID.
|
75
|
+
Example 1: (Find all occurences of the specified tag in the object)
|
76
|
+
pos = obj.get_pos("3006,0080")
|
77
|
+
Example 2: (Find all occurences of the specified tag inside the specified sequence)
|
78
|
+
pos = obj.get_pos("3006,0080", obj.below("3006,0082"))
|
63
79
|
|
64
80
|
get_raw(id)
|
65
81
|
Returns the raw data of the DICOM tag that matches the supplied tag ID.
|
@@ -69,12 +85,19 @@ PUBLIC INSTANCE METHODS
|
|
69
85
|
Returns the value (processed raw data) of the DICOM tag that matches the supplied tag ID.
|
70
86
|
The ID may be a tag index, tag name or tag label.
|
71
87
|
|
72
|
-
print(id)
|
73
|
-
Prints the information of
|
74
|
-
|
88
|
+
print(id, *options)
|
89
|
+
Prints the information of one or many tag(s):
|
90
|
+
(index, [hierarchy level,] label, name, type, length, value)
|
91
|
+
The method can print to both screen or to a text file. If print to file is chosen,
|
92
|
+
the text file will be put in the folder of the original DICOM file with a '.txt' extension.
|
93
|
+
The ID may be a tag name, label or position, or it might be an array of positions.
|
94
|
+
Example 1: (Print all tags to file, with both tree visualization and level numbers)
|
95
|
+
obj.print(true, levels=true, tree=true, file=true)
|
96
|
+
Example 2: (Print an array of tags to screen, no level or tree visualization)
|
97
|
+
obj.print([4,5,6])
|
75
98
|
|
76
99
|
print_all()
|
77
|
-
Prints information of all tags stored in the DICOM object.
|
100
|
+
Prints information of all tags stored in the DICOM object to the screen.
|
78
101
|
|
79
102
|
print_properties()
|
80
|
-
Prints the key structural properties of the DICOM file.
|
103
|
+
Prints the key structural properties of the DICOM file to the screen.
|
data/README
CHANGED
@@ -11,8 +11,10 @@ BASIC USAGE
|
|
11
11
|
|
12
12
|
require 'dicom'
|
13
13
|
# Read file:
|
14
|
-
dcm = DICOM::DObject.new("myFile.dcm")
|
15
|
-
#
|
14
|
+
dcm = DICOM::DObject.new("myFile.dcm")
|
15
|
+
# Display some key information about the file:
|
16
|
+
dcm.print_properties()
|
17
|
+
# Print all tags to screen:
|
16
18
|
dcm.print_all()
|
17
19
|
# Retrieve a tag value:
|
18
20
|
name = dcm.get_value("0010.0010")
|
@@ -56,3 +58,4 @@ Oslo, Norway
|
|
56
58
|
Email:
|
57
59
|
chris.lervag @nospam @gmail.com
|
58
60
|
Please don't hesitate to email me if have any thoughts on this project!
|
61
|
+
|
data/lib/DLibrary.rb
CHANGED
@@ -21,6 +21,7 @@ module DICOM
|
|
21
21
|
|
22
22
|
# Load the dictionary:
|
23
23
|
dict = Dictionary.new()
|
24
|
+
|
24
25
|
# Data elements:
|
25
26
|
de = dict.load_tags()
|
26
27
|
@de_label = de[0]
|
@@ -91,6 +92,20 @@ module DICOM
|
|
91
92
|
end
|
92
93
|
|
93
94
|
|
95
|
+
# Returns the name corresponding to a given UID.
|
96
|
+
def get_uid(label)
|
97
|
+
# Find the position of the specified label in the array:
|
98
|
+
pos = @uid_value.index(label)
|
99
|
+
# Fetch the name of this UID:
|
100
|
+
if pos != nil
|
101
|
+
name = @uid_name[pos]
|
102
|
+
else
|
103
|
+
name = "Unknown UID!"
|
104
|
+
end
|
105
|
+
return name
|
106
|
+
end
|
107
|
+
|
108
|
+
|
94
109
|
# Following methods are private.
|
95
110
|
private
|
96
111
|
|
data/lib/DObject.rb
CHANGED
@@ -30,7 +30,7 @@ module DICOM
|
|
30
30
|
# Class for handling the DICOM contents:
|
31
31
|
class DObject
|
32
32
|
|
33
|
-
attr_reader :read_success
|
33
|
+
attr_reader :read_success, :modality
|
34
34
|
|
35
35
|
# Initialize the DObject instance.
|
36
36
|
def initialize(file_name=nil, verbose=false, lib=nil)
|
@@ -41,6 +41,7 @@ module DICOM
|
|
41
41
|
@lengths = Array.new()
|
42
42
|
@values = Array.new()
|
43
43
|
@raw = Array.new()
|
44
|
+
@levels = Array.new()
|
44
45
|
# Array that will holde any messages generated while reading the DICOM file:
|
45
46
|
@msg = Array.new()
|
46
47
|
# Array to keep track of sequences/structure of the dicom tags:
|
@@ -52,6 +53,8 @@ module DICOM
|
|
52
53
|
@color = false
|
53
54
|
@explicit = true
|
54
55
|
@file_endian = false
|
56
|
+
# Information about the DICOM object:
|
57
|
+
@modality = nil
|
55
58
|
# Handling variables:
|
56
59
|
@verbose = verbose
|
57
60
|
# Control variables:
|
@@ -65,6 +68,7 @@ module DICOM
|
|
65
68
|
end
|
66
69
|
# If a (valid) file name string is supplied, launch the method to read DICOM file:
|
67
70
|
if file_name != nil and file_name != ""
|
71
|
+
@file = file_name
|
68
72
|
read_file(file_name)
|
69
73
|
end
|
70
74
|
end
|
@@ -90,16 +94,19 @@ module DICOM
|
|
90
94
|
@lengths = data[3]
|
91
95
|
@values = data[4]
|
92
96
|
@raw = data[5]
|
97
|
+
@levels = data[6]
|
93
98
|
# Other information:
|
94
|
-
@compression = data[
|
95
|
-
@color = data[
|
96
|
-
@explicit = data[
|
97
|
-
@file_endian = data[
|
99
|
+
@compression = data[7]
|
100
|
+
@color = data[8]
|
101
|
+
@explicit = data[9]
|
102
|
+
@file_endian = data[10]
|
98
103
|
# Index of last element in tag arrays:
|
99
104
|
@last_index=@names.length-1
|
105
|
+
# Set the modality of the DICOM object:
|
106
|
+
set_modality()
|
100
107
|
end
|
101
108
|
# The messages must be stored regardless of success or failure:
|
102
|
-
messages = data[
|
109
|
+
messages = data[11]
|
103
110
|
# If any messages has been recorded, send these to the message handling method:
|
104
111
|
if messages.size != 0
|
105
112
|
add_msg(messages)
|
@@ -306,15 +313,37 @@ module DICOM
|
|
306
313
|
end
|
307
314
|
|
308
315
|
|
309
|
-
# Returns the index(es) of the tag(s) in the DICOM file that match the supplied tag label.
|
310
|
-
|
311
|
-
|
316
|
+
# Returns an array of the index(es) of the tag(s) in the DICOM file that match the supplied tag label, name or position.
|
317
|
+
# If no match is found, the method will return false.
|
318
|
+
# Additional options:
|
319
|
+
# If an array of positions is specified as options[0], the method will search for hits in this
|
320
|
+
# array of positions instead of searching for hits in the entire object.
|
321
|
+
# If options[0]=false, then this method should also return false.
|
322
|
+
def get_pos(label, *options)
|
323
|
+
if options[0] == false
|
324
|
+
return false
|
325
|
+
end
|
326
|
+
# There may be a more elegant/efficient method to do this, but I leave that for later optimization.
|
327
|
+
# Array that will contain the positions where the supplied label gives a match:
|
312
328
|
indexes = Array.new()
|
313
|
-
|
329
|
+
# Either use the supplied array, or we will create an array that contain the indices of the entire DICOM object:
|
330
|
+
if options[0].is_a?(Array)
|
331
|
+
search_array=options[0]
|
332
|
+
else
|
333
|
+
search_array = Array.new(@names.size) {|i| i}
|
334
|
+
end
|
335
|
+
# Do the search:
|
336
|
+
#(0..@last_index).each do |i|
|
337
|
+
search_array.each do |i|
|
314
338
|
if @labels[i] == label
|
315
339
|
indexes += [i]
|
340
|
+
elsif @names[i] == label
|
341
|
+
indexes += [i]
|
342
|
+
elsif label == i
|
343
|
+
indexes += [i]
|
316
344
|
end
|
317
345
|
end
|
346
|
+
# Return false if no hits are found, else return the array of indices:
|
318
347
|
if indexes.size == 0
|
319
348
|
#puts "Notice: The requested position of label "+label+" was not identified."
|
320
349
|
return false
|
@@ -324,43 +353,171 @@ module DICOM
|
|
324
353
|
end
|
325
354
|
|
326
355
|
|
327
|
-
#
|
328
|
-
#
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
356
|
+
# Returns the positions of all tags inside the hierarchy of a sequence or an item.
|
357
|
+
# Options:
|
358
|
+
# If options[0] = true, then this method will only search immediately below the specified
|
359
|
+
# item or sequence (that is, in the level of parent 1).
|
360
|
+
def below(tag, *options)
|
361
|
+
restriction = options[0]
|
362
|
+
# Retrieve position of parent tag which from which we will search:
|
363
|
+
pos = get_pos(tag)
|
364
|
+
if pos == false
|
365
|
+
return false
|
366
|
+
end
|
367
|
+
if pos.size > 1
|
368
|
+
add_msg("Warning: The supplied parent tag gives multiple hits. Search will be applied to all hits. To avoid this behaviour, specify position instead of label.")
|
369
|
+
end
|
370
|
+
# First we need to establish in which positions to perform the search:
|
371
|
+
below_pos = Array.new()
|
372
|
+
pos.each do |p|
|
373
|
+
parent_level = @levels[p]
|
374
|
+
remain_array = @levels[p+1..@levels.size-1]
|
375
|
+
extract = true
|
376
|
+
remain_array.each_index do |i|
|
377
|
+
if (remain_array[i] > parent_level) and (extract == true)
|
378
|
+
# If search is targetted at any specific level, we can just add this position:
|
379
|
+
if not restriction == true
|
380
|
+
below_pos += [p+1+i]
|
381
|
+
else
|
382
|
+
# As search is restricted to parent level + 1, do a test for this:
|
383
|
+
if remain_array[i] == parent_level + 1
|
384
|
+
below_pos += [p+1+i]
|
385
|
+
end
|
386
|
+
end
|
387
|
+
else
|
388
|
+
# If we encounter a position who's level is not deeper than the original level, we can not extract any more values:
|
389
|
+
extract = false
|
390
|
+
end
|
342
391
|
end
|
343
392
|
end
|
393
|
+
# Positions to search in have been established, now we can perform the actual search:
|
394
|
+
if below_pos.size == 0
|
395
|
+
return false
|
396
|
+
else
|
397
|
+
return below_pos
|
398
|
+
end
|
344
399
|
end
|
345
400
|
|
346
401
|
|
347
|
-
# Prints
|
348
|
-
|
349
|
-
#
|
350
|
-
def
|
351
|
-
|
352
|
-
|
402
|
+
# Prints information of all tags stored in the DICOM object.
|
403
|
+
# This method is kept for backwards compatibility.
|
404
|
+
# Instead of calling print_all() you may use print(true) for the same functionality.
|
405
|
+
def print_all()
|
406
|
+
print(true)
|
407
|
+
end
|
408
|
+
|
409
|
+
|
410
|
+
# Prints the tag information of the specified tags (index, [hierarchy level, tree visualisation,] label, name, type, length, value)
|
411
|
+
# The supplied variable may be a single position, an array of positions, or true - which will make the method print all tags in object.
|
412
|
+
# Tag(s) may be specified by position, label or name.
|
413
|
+
# Options:
|
414
|
+
# options[0] = true will make the method print the level numbers for each tag.
|
415
|
+
# options[1] = true will make the method print a tree structure for the tags.
|
416
|
+
# options[2] = true will make the method print to file instead of printing to screen.
|
417
|
+
def print(pos, *options)
|
418
|
+
# Convert to array if number:
|
419
|
+
if not pos.is_a?(Array) and pos != true
|
420
|
+
pos_valid = get_pos(pos)
|
421
|
+
elsif pos == true
|
422
|
+
# Create an array of positions which a
|
423
|
+
pos_valid = Array.new(@names.length)
|
424
|
+
# Fill in indices:
|
425
|
+
pos_valid.each_index do |i|
|
426
|
+
pos_valid[i]=i
|
427
|
+
end
|
353
428
|
else
|
354
|
-
#
|
355
|
-
|
356
|
-
|
357
|
-
|
429
|
+
# Check that the supplied array contains valid positions:
|
430
|
+
pos_valid = Array.new()
|
431
|
+
pos.each_index do |i|
|
432
|
+
if pos[i] >= 0 and pos[i] <= @names.length
|
433
|
+
pos_valid += [pos[i]]
|
434
|
+
end
|
358
435
|
end
|
359
|
-
|
360
|
-
|
436
|
+
end
|
437
|
+
# Continue only if we have valid positions:
|
438
|
+
if pos_valid == false
|
439
|
+
return
|
440
|
+
elsif pos_valid.size == 0
|
441
|
+
return
|
442
|
+
end
|
443
|
+
# We have valid positions and are ready to start process the tags:
|
444
|
+
# Extract the information to be printed from the object arrays:
|
445
|
+
indices = Array.new()
|
446
|
+
levels = Array.new()
|
447
|
+
labels = Array.new()
|
448
|
+
names = Array.new()
|
449
|
+
types = Array.new()
|
450
|
+
lengths = Array.new()
|
451
|
+
values = Array.new()
|
452
|
+
# There may be a more elegant way to do this.
|
453
|
+
pos_valid.each_index do |i|
|
454
|
+
labels += [@labels[pos_valid[i]]]
|
455
|
+
levels += [@levels[pos_valid[i]]]
|
456
|
+
names += [@names[pos_valid[i]]]
|
457
|
+
types += [@types[pos_valid[i]]]
|
458
|
+
lengths += [@lengths[pos_valid[i]]]
|
459
|
+
values += [@values[pos_valid[i]]]
|
460
|
+
end
|
461
|
+
# We have collected the data that is to be printed, now we need to do some string manipulation if hierarchy is to be displayed:
|
462
|
+
if options[1] == true
|
463
|
+
# Tree structure requested.
|
464
|
+
front_symbol = "| "
|
465
|
+
tree_symbol = "|_"
|
466
|
+
labels.each_index do |i|
|
467
|
+
if levels[i] != 0
|
468
|
+
labels[i] = front_symbol*(levels[i]-1) + tree_symbol + labels[i]
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
# Extract the string lengths which are needed to make the formatting nice:
|
473
|
+
label_lengths = Array.new()
|
474
|
+
name_lengths = Array.new()
|
475
|
+
type_lengths = Array.new()
|
476
|
+
length_lengths = Array.new()
|
477
|
+
names.each_index do |i|
|
478
|
+
label_lengths[i] = labels[i].length
|
479
|
+
name_lengths[i] = names[i].length
|
480
|
+
type_lengths[i] = types[i].length
|
481
|
+
length_lengths[i] = lengths[i].to_s.length
|
482
|
+
end
|
483
|
+
# To give the printed output a nice format we need to check the string lengths of some of these arrays:
|
484
|
+
index_maxL = pos_valid.max.to_s.length
|
485
|
+
label_maxL = label_lengths.max
|
486
|
+
name_maxL = name_lengths.max
|
487
|
+
type_maxL = type_lengths.max
|
488
|
+
length_maxL = length_lengths.max
|
489
|
+
# Construct the strings, one for each line of output, where each line contain the information of one tag:
|
490
|
+
tags = Array.new()
|
491
|
+
labels.each_index do |i|
|
492
|
+
# Configure empty spaces:
|
493
|
+
s = " "
|
494
|
+
f0 = " "*(index_maxL-pos_valid[i].to_s.length)
|
495
|
+
f2 = " "*(label_maxL-labels[i].length+1)
|
496
|
+
f3 = " "*(name_maxL-names[i].length+1)
|
497
|
+
f4 = " "*(type_maxL-types[i].length+1)
|
498
|
+
f5 = " "*(length_maxL-lengths[i].to_s.length)
|
499
|
+
# Display levels?
|
500
|
+
if options[0] == true
|
501
|
+
lev = levels[i].to_s + s
|
502
|
+
else
|
503
|
+
lev = ""
|
504
|
+
end
|
505
|
+
# Restrict length of value string:
|
506
|
+
if values[i].to_s.length > 28
|
507
|
+
value = (values[i].to_s)[0..27]+" ..."
|
361
508
|
else
|
362
|
-
|
509
|
+
value = (values[i].to_s)
|
363
510
|
end
|
511
|
+
if types[i] == "OB" or types[i] == "OW" or types[i] == "UN"
|
512
|
+
value = "(Binary data)"
|
513
|
+
end
|
514
|
+
tags += [f0 + pos_valid[i].to_s + s + lev + s + labels[i] + f2 + names[i] + f3 + types[i] + f4 + f5 + lengths[i].to_s + s + s + value]
|
515
|
+
end
|
516
|
+
# Print to either screen or file, depending on what the user requested:
|
517
|
+
if options[2] == true
|
518
|
+
print_file(tags)
|
519
|
+
else
|
520
|
+
print_screen(tags)
|
364
521
|
end
|
365
522
|
end
|
366
523
|
|
@@ -433,70 +590,87 @@ module DICOM
|
|
433
590
|
|
434
591
|
# Prints the key structural properties of the DICOM file.
|
435
592
|
def print_properties()
|
593
|
+
# Explicitness:
|
436
594
|
if @explicit
|
437
|
-
|
595
|
+
explicit = "Explicit"
|
438
596
|
else
|
439
|
-
|
597
|
+
explicit = "Implicit"
|
440
598
|
end
|
599
|
+
# Endianness:
|
441
600
|
if @file_endian
|
442
|
-
|
601
|
+
endian = "Big Endian"
|
443
602
|
else
|
444
|
-
|
603
|
+
endian = "Little Endian"
|
445
604
|
end
|
446
|
-
|
447
|
-
|
605
|
+
# Pixel data:
|
606
|
+
if @compression == nil
|
607
|
+
pixels = "No"
|
448
608
|
else
|
449
|
-
|
609
|
+
pixels = "Yes"
|
450
610
|
end
|
611
|
+
# Colors:
|
451
612
|
if @color
|
452
|
-
|
613
|
+
image = "Colors"
|
453
614
|
else
|
454
|
-
|
615
|
+
image = "Greyscale"
|
455
616
|
end
|
456
|
-
|
457
|
-
|
458
|
-
|
617
|
+
# Compression:
|
618
|
+
if @compression == true
|
619
|
+
compression = @lib.get_uid(get_value("0002,0010").rstrip)
|
620
|
+
else
|
621
|
+
compression = "No"
|
459
622
|
end
|
460
|
-
|
623
|
+
# Bits per pixel (allocated):
|
624
|
+
bits = get_value("0028,0100").to_s
|
625
|
+
# Print the file properties:
|
626
|
+
puts "Key properties of DICOM object:"
|
627
|
+
puts "-------------------------------"
|
628
|
+
puts "File: " + @file
|
629
|
+
puts "Modality: " + @modality
|
630
|
+
puts "Value repr.: " + explicit
|
631
|
+
puts "Byte order: " + endian
|
632
|
+
puts "Pixel data: " + pixels
|
633
|
+
if pixels == "Yes"
|
634
|
+
puts "Image: " + image
|
635
|
+
puts "Compression: " + compression
|
636
|
+
puts "Bits per pixel: " + bits
|
637
|
+
end
|
638
|
+
puts "-------------------------------"
|
461
639
|
end
|
462
640
|
|
463
641
|
|
464
|
-
# Following methods are private
|
642
|
+
# ***** PS: Following methods are private! *****
|
465
643
|
private
|
466
644
|
|
467
645
|
|
468
|
-
#
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
add_msg("The specified index "+pos.to_s+" is outside the bounds of the tags array.")
|
646
|
+
# Sets the modality variable of the current DICOM object, by querying the library with the object's SOP Class UID.
|
647
|
+
def set_modality()
|
648
|
+
value = get_value("0008,0016")
|
649
|
+
if value == false
|
650
|
+
@modality = "Not specified"
|
474
651
|
else
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
# We dont want to print tag contents if it is binary data:
|
489
|
-
if @types[pos] == "OB" or @types[pos] == "OW" or @types[pos] == "UN"
|
490
|
-
p6 = "(binary data)"
|
491
|
-
else
|
492
|
-
if @values[pos].to_s.length > 28
|
493
|
-
p6 = (@values[pos].to_s)[0..27]+" ..."
|
494
|
-
else
|
495
|
-
p6 = (@values[pos].to_s)
|
496
|
-
end
|
652
|
+
modality = @lib.get_uid(value.rstrip)
|
653
|
+
@modality = modality
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
|
658
|
+
# Prints the selected tags to an ascii text file.
|
659
|
+
# The text file will be saved in the folder of the original DICOM file,
|
660
|
+
# with the original file name plus a .txt extension.
|
661
|
+
def print_file(tags)
|
662
|
+
File.open( @file + '.txt', 'w' ) do |output|
|
663
|
+
tags.each do | line |
|
664
|
+
output.print line + "\n"
|
497
665
|
end
|
498
|
-
|
499
|
-
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
|
670
|
+
# Prints the selected tags to screen.
|
671
|
+
def print_screen(tags)
|
672
|
+
tags.each do | tag |
|
673
|
+
puts tag
|
500
674
|
end
|
501
675
|
end
|
502
676
|
|
data/lib/DRead.rb
CHANGED
@@ -6,6 +6,8 @@ module DICOM
|
|
6
6
|
|
7
7
|
# Initialize the DRead instance.
|
8
8
|
def initialize(file_name=nil, lib=nil)
|
9
|
+
@a=0
|
10
|
+
@b=0
|
9
11
|
# Variables that hold data that will be returned to the person/procedure using this class:
|
10
12
|
# Arrays that will hold information from the DICOM file:
|
11
13
|
@names = Array.new()
|
@@ -14,6 +16,14 @@ module DICOM
|
|
14
16
|
@lengths = Array.new()
|
15
17
|
@values = Array.new()
|
16
18
|
@raw = Array.new()
|
19
|
+
@levels = Array.new()
|
20
|
+
# Keeping track of how many bytes have been read from the file up to and including each tag:
|
21
|
+
# This is necessary for tracking the hiearchy in some DICOM files.
|
22
|
+
@integrated_lengths = Array.new()
|
23
|
+
@header_length = 0
|
24
|
+
# Keep track of the hierarchy of tags (this will be used to determine when a sequence or item is finished):
|
25
|
+
@hierarchy = Array.new()
|
26
|
+
@hierarchy_error = false
|
17
27
|
# Array that will holde any messages generated while reading the DICOM file:
|
18
28
|
@msg = Array.new()
|
19
29
|
# Explicitness (explicit (true) by default):
|
@@ -32,8 +42,6 @@ module DICOM
|
|
32
42
|
# Variables used internally when reading the dicom file:
|
33
43
|
# If tag does not exist in the library it is unknown:
|
34
44
|
@unknown = false
|
35
|
-
# Does the particular tag contain any information?
|
36
|
-
@content = true
|
37
45
|
# Check endianness of the system (false if little endian):
|
38
46
|
@sys_endian=check_sys_endian()
|
39
47
|
# Endianness of the remaining groups after the first group:
|
@@ -48,6 +56,8 @@ module DICOM
|
|
48
56
|
@data_length = 0
|
49
57
|
# Variable used to tell whether file was read succesfully or not:
|
50
58
|
@success = false
|
59
|
+
# Keeping track of the tag level while reading through the file:
|
60
|
+
@current_level = 0
|
51
61
|
|
52
62
|
# Open file for binary reading:
|
53
63
|
begin
|
@@ -70,6 +80,11 @@ module DICOM
|
|
70
80
|
if header == false
|
71
81
|
@file.close()
|
72
82
|
@file = File.new(file_name, "rb")
|
83
|
+
@header_length = 0
|
84
|
+
elsif header == nil
|
85
|
+
# Reading the file did not succeed, and we need to abort.
|
86
|
+
@msg += ["Error! Could not read: "+ file_name + " It might be a directory. Returning."]
|
87
|
+
return
|
73
88
|
end
|
74
89
|
|
75
90
|
# Initiate the process to read tags:
|
@@ -78,7 +93,7 @@ module DICOM
|
|
78
93
|
while tag != false and temp_check== true do
|
79
94
|
tag=process_tag()
|
80
95
|
# Store the tag information in arrays:
|
81
|
-
if tag != false
|
96
|
+
if tag != false
|
82
97
|
@names+=[tag[0]]
|
83
98
|
@labels+=[tag[1]]
|
84
99
|
@types+=[tag[2]]
|
@@ -105,7 +120,7 @@ module DICOM
|
|
105
120
|
|
106
121
|
# Returns the relevant information gathered from the read dicom procedure.
|
107
122
|
def return_data()
|
108
|
-
return [@names,@labels,@types,@lengths,@values,@raw,@compression,@color,@explicit, @file_endian, @msg]
|
123
|
+
return [@names,@labels,@types,@lengths,@values,@raw,@levels,@compression,@color,@explicit, @file_endian, @msg]
|
109
124
|
end
|
110
125
|
|
111
126
|
|
@@ -115,10 +130,17 @@ module DICOM
|
|
115
130
|
# consequtive zero bytes followed by 4 bytes that spell the string 'DICM'.
|
116
131
|
# Apparently, some providers seems to skip this in their DICOM files.
|
117
132
|
# First 128 bytes should be zeroes:
|
118
|
-
|
133
|
+
begin
|
134
|
+
bin1=@file.read(128)
|
135
|
+
@header_length += 128
|
136
|
+
rescue
|
137
|
+
# The file could not be read. Most likely because the file name variable supplied to this instance was in fact a directory.
|
138
|
+
return nil
|
139
|
+
end
|
119
140
|
str_header1=bin1.unpack('a' * 128).to_s
|
120
141
|
# Next 4 bytes should spell 'DICM':
|
121
142
|
bin2=@file.read(4)
|
143
|
+
@header_length += 4
|
122
144
|
str_header2=bin2.unpack('a' * 4).to_s
|
123
145
|
# If we dont have this expected header, we will still try to read it is a DICOM file.
|
124
146
|
if str_header2 != 'DICM' then
|
@@ -173,17 +195,15 @@ module DICOM
|
|
173
195
|
|
174
196
|
|
175
197
|
# Governs the process of reading tags in the DICOM file.
|
198
|
+
# (This method needs to be cleaned up a bit, it just isnt that easy to see whats
|
199
|
+
#going on here in all cases. Perhaps some day I will get the courage to have a go at it again.)
|
176
200
|
def process_tag()
|
177
201
|
#STEP 1: ------------------------------------------------------
|
178
|
-
# Read the tag label, but
|
179
|
-
@content = true
|
202
|
+
# Read the tag label, but do not continue if the method signals that we have reached end of file:
|
180
203
|
label=read_label()
|
181
204
|
if label == false
|
182
205
|
return false
|
183
206
|
end
|
184
|
-
if @content == false # PS: @content switch is not active atm it seems!
|
185
|
-
return
|
186
|
-
end
|
187
207
|
# Retrieve the tag name and type based on the label we have read from file:
|
188
208
|
lib_data = @lib.get_name_vr(label)
|
189
209
|
name = lib_data[0]
|
@@ -210,14 +230,27 @@ module DICOM
|
|
210
230
|
if length == "UNDEFINED"
|
211
231
|
if label == "7FE0,0010"
|
212
232
|
data = "(Encapsulated pixel data)"
|
213
|
-
name = "Encapsulated image"
|
233
|
+
name = "Encapsulated image(s)"
|
234
|
+
type = "SQ"
|
235
|
+
elsif type == "SQ" or type == "()"
|
236
|
+
# Do not change name of tag.
|
237
|
+
data = "(Encapsulated tags)"
|
214
238
|
else
|
215
239
|
data = "(Encapsulated data)"
|
216
240
|
name = "Encapsulated information"
|
217
241
|
end
|
242
|
+
# Set hiearchy level:
|
243
|
+
set_level(type, length, label)
|
218
244
|
return [name,label,type,length,data]
|
219
245
|
end
|
220
|
-
#
|
246
|
+
# Add the length of the content of the tag to the last element in the integrated_lengths array:
|
247
|
+
# (but not if it is a sequence or item, as in this case the length of the tag is its sub-tags)
|
248
|
+
if length.to_i != 0 and type != "SQ" and type != "()"
|
249
|
+
@integrated_lengths[@integrated_lengths.size-1] += length
|
250
|
+
end
|
251
|
+
# Set hiearchy level:
|
252
|
+
set_level(type, length, label)
|
253
|
+
# Some special handling for item related tags, which may result in returning without reading data:
|
221
254
|
if type == "()"
|
222
255
|
# If length is zero, just return:
|
223
256
|
if length == 0
|
@@ -233,26 +266,29 @@ module DICOM
|
|
233
266
|
if @sq_length != true
|
234
267
|
# Treat the item as containing image data:
|
235
268
|
type = "OW" # A more general approach should be implemented here.
|
269
|
+
# For this special case, where item contains the data itself, instead of in sub-tags,
|
270
|
+
# we declare that there is to be no sub-level after all.
|
271
|
+
# This handling is not particularly obvious or elegant, and perhaps in the future I will
|
272
|
+
# be able to rewrite this whole process_tag method to something more sane.
|
273
|
+
@current_level = @current_level - 1
|
236
274
|
end
|
237
275
|
end
|
238
276
|
end
|
239
277
|
# STEP 3: ----------------------------------------
|
240
278
|
# Finally read the tag data.
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
@data_length = raw.length
|
253
|
-
end
|
254
|
-
return [name,label,type,length,value,raw]
|
279
|
+
tag_data = read_data(type,length)
|
280
|
+
value = tag_data[0]
|
281
|
+
raw = tag_data[1]
|
282
|
+
# Check for the Transfer Syntax UID tag, and process it:
|
283
|
+
if label == "0002,0010"
|
284
|
+
process_syntax(value)
|
285
|
+
end
|
286
|
+
if type == "SQ" or type == "()"
|
287
|
+
@data_length = length # To avoid false errors. In time perhaps a better way of handling this will be found.
|
288
|
+
else
|
289
|
+
@data_length = raw.length
|
255
290
|
end
|
291
|
+
return [name,label,type,length,value,raw]
|
256
292
|
end
|
257
293
|
# END READ TAG
|
258
294
|
|
@@ -262,9 +298,18 @@ module DICOM
|
|
262
298
|
bin1=@file.read(2)
|
263
299
|
bin2=@file.read(2)
|
264
300
|
# Check if we have reached end of file before proceeding:
|
265
|
-
if bin1 == nil
|
301
|
+
if bin1 == nil or bin2 == nil
|
266
302
|
return false
|
267
303
|
end
|
304
|
+
# Add the length of the tag label. If this was the first label read from file, we need to add the header length too:
|
305
|
+
if @integrated_lengths.length == 0
|
306
|
+
# Increase the array with the length of the header + the 4 bytes:
|
307
|
+
@integrated_lengths += [@header_length + 4]
|
308
|
+
else
|
309
|
+
# For the remaining tags, increase the array with the integrated length of the previous tags + the 4 bytes:
|
310
|
+
@integrated_lengths += [@integrated_lengths[@integrated_lengths.length-1] + 4]
|
311
|
+
end
|
312
|
+
# Unpack the blobs:
|
268
313
|
label1=bin1.unpack('h*').to_s.reverse.upcase
|
269
314
|
label2=bin2.unpack('h*').to_s.reverse.upcase
|
270
315
|
# Special treatment of tags that are of the first "0002" group:
|
@@ -310,10 +355,12 @@ module DICOM
|
|
310
355
|
# It seems we need to have a special case for item labels in the explicit scenario:
|
311
356
|
if label == "FFFE,E000" or label == "FFFE,E00D" or label == "FFFE,E0DD"
|
312
357
|
bin=@file.read(4)
|
358
|
+
@integrated_lengths[@integrated_lengths.length-1] += 4
|
313
359
|
length = get_SL(bin)
|
314
360
|
else
|
315
361
|
# Read tag type field (2 bytes - since we are not dealing with an item related tag):
|
316
362
|
bin=@file.read(2)
|
363
|
+
@integrated_lengths[@integrated_lengths.length-1] += 2
|
317
364
|
type=bin.unpack('a*').to_s
|
318
365
|
end
|
319
366
|
# Two (three) possible structures for value length here, dependent on tag type:
|
@@ -321,20 +368,24 @@ module DICOM
|
|
321
368
|
when "OB","OW","SQ","UN"
|
322
369
|
# Two empty bytes should occur here, according to the standard:
|
323
370
|
bin=@file.read(2)
|
371
|
+
@integrated_lengths[@integrated_lengths.length-1] += 2
|
324
372
|
# Read value length (4 bytes):
|
325
373
|
bin=@file.read(4)
|
374
|
+
@integrated_lengths[@integrated_lengths.length-1] += 4
|
326
375
|
length=get_SL(bin)
|
327
376
|
when "()"
|
328
377
|
#An empty entry for the item related tags (As it has already been processed).
|
329
378
|
else
|
330
379
|
# For all the other tag types: Read value length (2 bytes):
|
331
380
|
bin=@file.read(2)
|
381
|
+
@integrated_lengths[@integrated_lengths.length-1] += 2
|
332
382
|
length=get_US(bin)
|
333
383
|
end
|
334
384
|
else
|
335
385
|
#IMPLICIT:
|
336
386
|
# Read value length (4 bytes):
|
337
387
|
bin=@file.read(4)
|
388
|
+
@integrated_lengths[@integrated_lengths.length-1] += 4
|
338
389
|
length = get_SL(bin)
|
339
390
|
end
|
340
391
|
# For encapsulated data, the tag length will not be defined. To convey this,
|
@@ -471,7 +522,7 @@ module DICOM
|
|
471
522
|
end
|
472
523
|
|
473
524
|
# For everything else, assume string type information:
|
474
|
-
when 'AE','AS','CS','DA','DS','IS','LO','LT','PN','SH','ST','TM','UI','VR'
|
525
|
+
when 'AE','AS','CS','DA','DS','DT','IS','LO','LT','PN','SH','ST','TM','UI','UT' #,'VR'
|
475
526
|
bin=@file.read(length)
|
476
527
|
data=bin.unpack('a*').to_s
|
477
528
|
else
|
@@ -486,6 +537,79 @@ module DICOM
|
|
486
537
|
# END TAG DATA
|
487
538
|
|
488
539
|
|
540
|
+
# Sets the level of the current tag in the hiearchy.
|
541
|
+
# The default (top) level is zero.
|
542
|
+
def set_level(type, length, label)
|
543
|
+
# Set the level of this tag:
|
544
|
+
@levels += [@current_level]
|
545
|
+
# Determine if there is a level change for the following tag:
|
546
|
+
# If tag is a sequence, the level of the following tags will be increased by one.
|
547
|
+
# If tag is an item, the level of the following tags will be increased by one.
|
548
|
+
# Note the following exception:
|
549
|
+
# If label is "Item", and it contains data (image fragment) directly, which is to say,
|
550
|
+
# not in its sub-tags, we should not increase the level. (This is fixed in the process_tag method.)
|
551
|
+
if type == "SQ"
|
552
|
+
increase = true
|
553
|
+
elsif label =="FFFE,E000"
|
554
|
+
increase = true
|
555
|
+
else
|
556
|
+
increase = false
|
557
|
+
end
|
558
|
+
if increase == true
|
559
|
+
@current_level = @current_level + 1
|
560
|
+
# If length of sequence/item is specified, we must note this length + the current tag position in the arrays:
|
561
|
+
if length.to_i != 0
|
562
|
+
@hierarchy += [[length,@integrated_lengths.last]]
|
563
|
+
else
|
564
|
+
@hierarchy += [type]
|
565
|
+
end
|
566
|
+
end
|
567
|
+
# Need to check whether a previous sequence or item has ended, if so the level must be decreased by one:
|
568
|
+
# In the case of tag specification:
|
569
|
+
if (label == "FFFE,E00D") or (label == "FFFE,E0DD")
|
570
|
+
@current_level = @current_level - 1
|
571
|
+
end
|
572
|
+
# In the case of sequence and item length specification:
|
573
|
+
# Check the last position in the hieararchy array.
|
574
|
+
# If it is an array (of length and position), then we need to check the integrated_lengths array
|
575
|
+
# to see if the current sub-level has expired.
|
576
|
+
if @hierarchy.size > 0
|
577
|
+
check_level_end()
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
|
582
|
+
# Checks how far we've read in the DICOM file to determine if we have reached a point
|
583
|
+
# where sub-levels are ending. This method is recursive, as multiple sequences/items might end at the same point.
|
584
|
+
def check_level_end()
|
585
|
+
# The test is only meaningful to perform if we are not expecting an 'end of sequence/item' tag to signal the level-change.
|
586
|
+
if (@hierarchy.last).is_a?(Array)
|
587
|
+
described_length = (@hierarchy.last)[0]
|
588
|
+
previous_length = (@hierarchy.last)[1]
|
589
|
+
current_length = @integrated_lengths.last
|
590
|
+
current_diff = current_length - previous_length
|
591
|
+
if current_diff == described_length
|
592
|
+
# Decrease level by one:
|
593
|
+
@current_level = @current_level - 1
|
594
|
+
# Also we need to delete the last entry of the @hierarchy array:
|
595
|
+
if (@hierarchy.size > 1)
|
596
|
+
@hierarchy = @hierarchy[0..(@hierarchy.size-2)]
|
597
|
+
# There might be numerous levels that ends at this particular point, so we need to do a recursive repeat to check.
|
598
|
+
check_level_end()
|
599
|
+
else
|
600
|
+
@hierarchy = Array.new()
|
601
|
+
end
|
602
|
+
elsif current_diff > described_length
|
603
|
+
# Only register this type of error one time per file to avoid a spamming effect:
|
604
|
+
if not @hierarchy_error
|
605
|
+
@msg += ["Unexpected hierarchy incident: Current length difference is greater than the expected value, which should not occur. This will not pose any problems unless you intend to query the object for tags in the hierarchy."]
|
606
|
+
@hierarchy_error = true
|
607
|
+
end
|
608
|
+
end
|
609
|
+
end
|
610
|
+
end
|
611
|
+
|
612
|
+
|
489
613
|
# Returns the (processed) value of a DICOM tag based on an input tag label, category name or array index.
|
490
614
|
def get_value(id)
|
491
615
|
# Assume we have been fed a tag label:
|
data/lib/Dictionary.rb
CHANGED
@@ -17,6 +17,44 @@ module DICOM
|
|
17
17
|
return [a,b]
|
18
18
|
end
|
19
19
|
|
20
|
+
|
21
|
+
# Loads the value representation library.
|
22
|
+
# Consists of VR name, meaning and data format.
|
23
|
+
def load_vr()
|
24
|
+
d = Array.new()
|
25
|
+
e = Array.new()
|
26
|
+
f = Array.new()
|
27
|
+
d+=["AE"] and e+=["Application entity"] and f+=["String"]
|
28
|
+
d+=["AS"] and e+=["Age string"] and f+=["String"]
|
29
|
+
d+=["AT"] and e+=["Attribute tag"] and f+=["Two 2-byte integers"]
|
30
|
+
d+=["CS"] and e+=["Code string"] and f+=["String"]
|
31
|
+
d+=["DA"] and e+=["Date"] and f+=["String"]
|
32
|
+
d+=["DS"] and e+=["Decimal string"] and f+=["String"]
|
33
|
+
d+=["DT"] and e+=["Date time"] and f+=["String"]
|
34
|
+
d+=["FL"] and e+=["Floating point single"] and f+=["4-byte floating point"]
|
35
|
+
d+=["FD"] and e+=["Floating point double"] and f+=["8-byte floating point"]
|
36
|
+
d+=["IS"] and e+=["Integer string"] and f+=["String"]
|
37
|
+
d+=["LO"] and e+=["Long string"] and f+=["String"]
|
38
|
+
d+=["LT"] and e+=["Long text"] and f+=["String"]
|
39
|
+
d+=["OB"] and e+=["Other byte string"] and f+=["1-byte integers"]
|
40
|
+
d+=["OF"] and e+=["Other float string"] and f+=["4-byte floating point numbers"]
|
41
|
+
d+=["OW"] and e+=["Other word string"] and f+=["2-byte integers"]
|
42
|
+
d+=["PN"] and e+=["Person name"] and f+=["String"]
|
43
|
+
d+=["SH"] and e+=["Short string"] and f+=["String"]
|
44
|
+
d+=["SL"] and e+=["Signed long"] and f+=["4-byte integer"]
|
45
|
+
d+=["SQ"] and e+=["Sequence of items"] and f+=["Unknown"]
|
46
|
+
d+=["SS"] and e+=["Signed short"] and f+=["2-byte integer"]
|
47
|
+
d+=["ST"] and e+=["Short text"] and f+=["String"]
|
48
|
+
d+=["TM"] and e+=["Time"] and f+=["String"]
|
49
|
+
d+=["UI"] and e+=["Unique identifier"] and f+=["String"]
|
50
|
+
d+=["UL"] and e+=["Unsigned long"] and f+=["4-byte integer"]
|
51
|
+
d+=["UN"] and e+=["Unknown"] and f+=["Unknown"]
|
52
|
+
d+=["US"] and e+=["Unsigned short"] and f+=["2-byte integer"]
|
53
|
+
d+=["UT"] and e+=["Unlimited text"] and f+=["String"]
|
54
|
+
return [d,e,f]
|
55
|
+
end
|
56
|
+
|
57
|
+
|
20
58
|
# Table A.1 UID Values (DICOM Part 6, Annex A: Registry of DICOM unique identifiers)
|
21
59
|
def load_uid()
|
22
60
|
r = Array.new()
|
metadata
CHANGED
@@ -1,33 +1,26 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.4
|
3
|
-
specification_version: 1
|
4
2
|
name: dicom
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
version: "0.
|
7
|
-
date: 2008-08-10 00:00:00 +02:00
|
8
|
-
summary: Library for reading DICOM files.
|
9
|
-
require_paths:
|
10
|
-
- lib
|
11
|
-
email: chris.lervag@gmail.com
|
12
|
-
homepage: http://rubyforge.org/projects/dicom/
|
13
|
-
rubyforge_project: dicom
|
14
|
-
description: DICOM is a standard widely used throughout the world to store and transfer medical image data. This project aims to make a library that is able to handle DICOM in the Ruby language, to the benefit of any student or professional who would like to use Ruby to process their DICOM files.
|
15
|
-
autorequire:
|
16
|
-
default_executable:
|
17
|
-
bindir: bin
|
18
|
-
has_rdoc: false
|
19
|
-
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">"
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 0.0.0
|
24
|
-
version:
|
4
|
+
version: "0.3"
|
25
5
|
platform: ruby
|
26
|
-
signing_key:
|
27
|
-
cert_chain:
|
28
|
-
post_install_message:
|
29
6
|
authors:
|
30
|
-
-
|
7
|
+
- Christoffer Lervag
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-10-12 00:00:00 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: DICOM is a standard widely used throughout the world to store and transfer medical image data. This project aims to make a library that is able to handle DICOM in the Ruby language, to the benefit of any student or professional who would like to use Ruby to process their DICOM files.
|
17
|
+
email: chris.lervag@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
31
24
|
files:
|
32
25
|
- lib/DObject.rb
|
33
26
|
- lib/DLibrary.rb
|
@@ -38,17 +31,31 @@ files:
|
|
38
31
|
- COPYING
|
39
32
|
- README
|
40
33
|
- DOCUMENTATION
|
41
|
-
|
42
|
-
|
34
|
+
has_rdoc: false
|
35
|
+
homepage: http://dicom.rubyforge.org/
|
36
|
+
post_install_message:
|
43
37
|
rdoc_options: []
|
44
38
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
version:
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
51
53
|
requirements: []
|
52
54
|
|
53
|
-
|
55
|
+
rubyforge_project: dicom
|
56
|
+
rubygems_version: 1.3.0
|
57
|
+
signing_key:
|
58
|
+
specification_version: 2
|
59
|
+
summary: Library for reading DICOM files.
|
60
|
+
test_files: []
|
54
61
|
|