dicom 0.9.5 → 0.9.6
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.
- checksums.yaml +13 -5
- data/{CHANGELOG.rdoc → CHANGELOG.md} +50 -30
- data/{CONTRIBUTING.rdoc → CONTRIBUTING.md} +16 -16
- data/Gemfile.lock +47 -0
- data/README.md +152 -0
- data/dicom.gemspec +11 -10
- data/lib/dicom.rb +30 -11
- data/lib/dicom/anonymizer.rb +654 -649
- data/lib/dicom/audit_trail.rb +0 -2
- data/lib/dicom/d_client.rb +1 -1
- data/lib/dicom/d_library.rb +45 -15
- data/lib/dicom/d_object.rb +18 -18
- data/lib/dicom/d_read.rb +28 -4
- data/lib/dicom/d_write.rb +49 -26
- data/lib/dicom/dictionary/{elements.txt → elements.tsv} +0 -0
- data/lib/dicom/dictionary/{uids.txt → uids.tsv} +0 -0
- data/lib/dicom/element.rb +6 -7
- data/lib/dicom/elemental.rb +1 -0
- data/lib/dicom/elemental_parent.rb +64 -0
- data/lib/dicom/extensions/array.rb +57 -0
- data/lib/dicom/extensions/hash.rb +31 -0
- data/lib/dicom/extensions/string.rb +126 -0
- data/lib/dicom/{constants.rb → general/constants.rb} +29 -38
- data/lib/dicom/{deprecated.rb → general/deprecated.rb} +0 -0
- data/lib/dicom/{logging.rb → general/logging.rb} +0 -0
- data/lib/dicom/{variables.rb → general/methods.rb} +0 -22
- data/lib/dicom/general/variables.rb +29 -0
- data/lib/dicom/{version.rb → general/version.rb} +1 -1
- data/lib/dicom/image_item.rb +0 -2
- data/lib/dicom/image_processor.rb +2 -0
- data/lib/dicom/item.rb +1 -13
- data/lib/dicom/link.rb +2 -1
- data/lib/dicom/parent.rb +34 -86
- data/lib/dicom/sequence.rb +1 -13
- data/lib/dicom/stream.rb +94 -114
- data/rakefile.rb +1 -1
- metadata +73 -36
- data/README.rdoc +0 -149
- data/lib/dicom/ruby_extensions.rb +0 -249
data/README.rdoc
DELETED
@@ -1,149 +0,0 @@
|
|
1
|
-
= RUBY DICOM
|
2
|
-
|
3
|
-
Ruby DICOM is a small and simple library for handling DICOM in Ruby. DICOM (Digital Imaging
|
4
|
-
and Communications in Medicine) is a standard for handling, storing, printing,
|
5
|
-
and transmitting information in medical imaging. It includes a file format definition
|
6
|
-
and a network communications protocol. Ruby DICOM supports reading from, editing
|
7
|
-
and writing to this file format. It also features basic support for select network
|
8
|
-
communication modalities like querying, moving, sending and receiving files.
|
9
|
-
|
10
|
-
|
11
|
-
== INSTALLATION
|
12
|
-
|
13
|
-
gem install dicom
|
14
|
-
|
15
|
-
|
16
|
-
== REQUIREMENTS
|
17
|
-
|
18
|
-
* Ruby 1.9.2 (if you are still on Ruby 1.8, gems up to version 0.9.1 can be used)
|
19
|
-
|
20
|
-
|
21
|
-
== BASIC USAGE
|
22
|
-
|
23
|
-
=== Load & Include
|
24
|
-
|
25
|
-
require 'dicom'
|
26
|
-
include DICOM
|
27
|
-
|
28
|
-
=== Read, modify and write
|
29
|
-
|
30
|
-
# Read file:
|
31
|
-
dcm = DObject.read("some_file.dcm")
|
32
|
-
# Extract the Patient's Name value:
|
33
|
-
dcm.patients_name.value
|
34
|
-
# Add or modify the Patient's Name element:
|
35
|
-
dcm.patients_name = "Anonymous"
|
36
|
-
# Remove a data element from the DICOM object:
|
37
|
-
dcm.pixel_data = nil
|
38
|
-
# Write to file:
|
39
|
-
dcm.write("new_file.dcm")
|
40
|
-
|
41
|
-
=== Modify using tag strings instead of dictionary method names
|
42
|
-
|
43
|
-
# Extract the Patient's Name value:
|
44
|
-
dcm.value("0010,0010")
|
45
|
-
# Modify the Patient's Name element:
|
46
|
-
dcm["0010,0010"].value = "Anonymous"
|
47
|
-
# Delete a data element from the DICOM object:
|
48
|
-
dcm.delete("7FE0,0010")
|
49
|
-
|
50
|
-
=== Extracting information about the DICOM object
|
51
|
-
|
52
|
-
# Display a short summary of the file's properties:
|
53
|
-
dcm.summary
|
54
|
-
# Print all data elements to screen:
|
55
|
-
dcm.print
|
56
|
-
# Convert the data element hierarchy to a nested hash:
|
57
|
-
dcm.to_hash
|
58
|
-
|
59
|
-
=== Handle pixel data
|
60
|
-
|
61
|
-
# Retrieve the pixel data in a Ruby Array:
|
62
|
-
dcm.pixels
|
63
|
-
# Load the pixel data to an numerical array (NArray):
|
64
|
-
dcm.narray
|
65
|
-
# Load the pixel data to an RMagick image object and display it on the screen:
|
66
|
-
dcm.image.display
|
67
|
-
|
68
|
-
=== Transmit a DICOM file
|
69
|
-
|
70
|
-
# Send a local file to a server (PACS) over the network:
|
71
|
-
node = DClient.new("10.1.25.200", 104)
|
72
|
-
node.send("some_file.dcm")
|
73
|
-
|
74
|
-
=== Start a DICOM server
|
75
|
-
|
76
|
-
# Initiate a simple storage provider which can receive DICOM files of all modalities:
|
77
|
-
s = DServer.new(104, :host_ae => "MY_DICOM_SERVER")
|
78
|
-
s.start_scp("C:/temp/")
|
79
|
-
|
80
|
-
=== Log settings
|
81
|
-
|
82
|
-
# Change the log level so that only error messages are displayed:
|
83
|
-
DICOM.logger.level = Logger::ERROR
|
84
|
-
# Setting up a simple file log:
|
85
|
-
l = Logger.new('my_logfile.log')
|
86
|
-
DICOM.logger = l
|
87
|
-
# Create a logger which ages logfile daily/monthly:
|
88
|
-
DICOM.logger = Logger.new('foo.log', 'daily')
|
89
|
-
DICOM.logger = Logger.new('foo.log', 'monthly')
|
90
|
-
|
91
|
-
|
92
|
-
=== IRB Tip
|
93
|
-
|
94
|
-
When working with Ruby DICOM in irb, you may be annoyed with all the information
|
95
|
-
that is printed to screen, regardless if you have set verbose as false. This is because
|
96
|
-
in irb every variable loaded in the program is automatically printed to the screen.
|
97
|
-
A useful hack to avoid this effect is to append ";0" after a command.
|
98
|
-
Example:
|
99
|
-
dcm = DObject.read("some_file.dcm") ;0
|
100
|
-
|
101
|
-
|
102
|
-
== RESOURCES
|
103
|
-
|
104
|
-
* {Official home page}[http://dicom.rubyforge.org/]
|
105
|
-
* {Discussion forum}[http://groups.google.com/group/ruby-dicom]
|
106
|
-
* {Documentation}[http://rubydoc.info/gems/dicom/frames]
|
107
|
-
* {Tutorials}[http://dicom.rubyforge.org/tutorials.html]
|
108
|
-
* {Source code repository}[https://github.com/dicom/ruby-dicom]
|
109
|
-
|
110
|
-
|
111
|
-
== COPYRIGHT
|
112
|
-
|
113
|
-
Copyright 2008-2013 Christoffer Lervåg
|
114
|
-
|
115
|
-
This program is free software: you can redistribute it and/or modify
|
116
|
-
it under the terms of the GNU General Public License as published by
|
117
|
-
the Free Software Foundation, either version 3 of the License, or
|
118
|
-
(at your option) any later version.
|
119
|
-
|
120
|
-
This program is distributed in the hope that it will be useful,
|
121
|
-
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
122
|
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
123
|
-
GNU General Public License for more details.
|
124
|
-
|
125
|
-
You should have received a copy of the GNU General Public License
|
126
|
-
along with this program. If not, see http://www.gnu.org/licenses/ .
|
127
|
-
|
128
|
-
|
129
|
-
== ABOUT THE AUTHOR
|
130
|
-
|
131
|
-
* Name: Christoffer Lervåg
|
132
|
-
* Location: Norway
|
133
|
-
* Email: chris.lervag [@nospam.com] @gmail.com
|
134
|
-
|
135
|
-
Please don't hesitate to email me if you have any feedback related to this project!
|
136
|
-
|
137
|
-
|
138
|
-
== CONTRIBUTORS
|
139
|
-
|
140
|
-
* {Christoffer Lervåg}[https://github.com/dicom]
|
141
|
-
* {John Axel Eriksson}[https://github.com/johnae]
|
142
|
-
* {Kamil Bujniewicz}[https://github.com/icdark]
|
143
|
-
* {Jeff Miller}[https://github.com/jeffmax]
|
144
|
-
* {Donnie Millar}[https://github.com/dmillar]
|
145
|
-
* {Björn Albers}[https://github.com/bjoernalbers]
|
146
|
-
* {Felix Petriconi}[https://github.com/FelixPetriconi]
|
147
|
-
* {Steven Bedrick}[https://github.com/stevenbedrick]
|
148
|
-
* {Lars Benner}[https://github.com/Maturin]
|
149
|
-
* {Brett Goulder}[https://github.com/brettgoulder]
|
@@ -1,249 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
# This file contains extensions to the Ruby library which are used by Ruby DICOM.
|
4
|
-
|
5
|
-
# Extension to the String class. These mainly facilitate the processing and analysis of element tags.
|
6
|
-
# A tag string (as used by the ruby-dicom library) is 9 characters long and of the form 'GGGG,EEEE'
|
7
|
-
# (where G represents a group hexadecimal, and E represents an element hexadecimal).
|
8
|
-
#
|
9
|
-
class String
|
10
|
-
|
11
|
-
# Renames the original unpack method.
|
12
|
-
#
|
13
|
-
alias __original_unpack__ unpack
|
14
|
-
|
15
|
-
# Checks if a string value LOOKS like a DICOM name - it may still not be valid one.
|
16
|
-
#
|
17
|
-
# @return [Boolean] true if a string looks like a DICOM name, and false if not
|
18
|
-
#
|
19
|
-
def dicom_name?
|
20
|
-
self == self.dicom_titleize
|
21
|
-
end
|
22
|
-
|
23
|
-
# Checks if a string value LOOKS like a DICOM method name - it may still not be valid one.
|
24
|
-
#
|
25
|
-
# @return [Boolean] true if a string looks like a DICOM method name, and false if not
|
26
|
-
#
|
27
|
-
def dicom_method?
|
28
|
-
self == self.dicom_underscore
|
29
|
-
end
|
30
|
-
|
31
|
-
# Capitalizes all the words in the string and replaces some characters to make a nicer looking title.
|
32
|
-
#
|
33
|
-
# @return [String] a formatted, capitalized string
|
34
|
-
#
|
35
|
-
def dicom_titleize
|
36
|
-
self.dicom_underscore.gsub(/_/, ' ').gsub(/\b('?[a-z])/) { $1.capitalize }
|
37
|
-
end
|
38
|
-
|
39
|
-
# Makes an underscored, lowercased version of the string.
|
40
|
-
#
|
41
|
-
# @return [String] an underscored, lower case string
|
42
|
-
#
|
43
|
-
def dicom_underscore
|
44
|
-
word = self.dup
|
45
|
-
word.tr!('-', '_')
|
46
|
-
word.downcase!
|
47
|
-
word
|
48
|
-
end
|
49
|
-
|
50
|
-
# Divides a string into a number of sub-strings of exactly equal length.
|
51
|
-
#
|
52
|
-
# @note The length of self must be a multiple of parts, or an exception will be raised.
|
53
|
-
# @param [Integer] parts the number of sub-strings to create
|
54
|
-
# @return [Array<String>] the divided sub-strings
|
55
|
-
#
|
56
|
-
def divide(parts)
|
57
|
-
raise ArgumentError, "Expected an integer (Fixnum). Got #{parts.class}." unless parts.is_a?(Fixnum)
|
58
|
-
raise ArgumentError, "Argument must be in the range <1 - self.length (#{self.length})>. Got #{parts}." if parts < 1 or parts > self.length
|
59
|
-
raise ArgumentError, "Length of self (#{self.length}) must be a multiple of parts (#{parts})." unless (self.length/parts).to_f == self.length/parts.to_f
|
60
|
-
if parts > 1
|
61
|
-
sub_strings = Array.new
|
62
|
-
sub_length = self.length/parts
|
63
|
-
parts.times { sub_strings << self.slice!(0..(sub_length-1)) }
|
64
|
-
return sub_strings
|
65
|
-
else
|
66
|
-
return [self]
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
# Extracts the element part of the tag string: The last 4 characters.
|
71
|
-
#
|
72
|
-
# @return [String] the element part of the tag
|
73
|
-
#
|
74
|
-
def element
|
75
|
-
return self[5..8]
|
76
|
-
end
|
77
|
-
|
78
|
-
# Returns the group part of the tag string: The first 4 characters.
|
79
|
-
#
|
80
|
-
# @return [String] the group part of the tag
|
81
|
-
#
|
82
|
-
def group
|
83
|
-
return self[0..3]
|
84
|
-
end
|
85
|
-
|
86
|
-
# Returns the "Group Length" ('GGGG,0000') tag which corresponds to the original tag/group string.
|
87
|
-
# The original string may either be a 4 character group string, or a 9 character custom tag.
|
88
|
-
#
|
89
|
-
# @return [String] a group length tag
|
90
|
-
#
|
91
|
-
def group_length
|
92
|
-
if self.length == 4
|
93
|
-
return self + ',0000'
|
94
|
-
else
|
95
|
-
return self.group + ',0000'
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# Checks if the string is a "Group Length" tag (its element part is '0000').
|
100
|
-
#
|
101
|
-
# @return [Boolean] true if it is a group length tag, and false if not
|
102
|
-
#
|
103
|
-
def group_length?
|
104
|
-
return (self.element == '0000' ? true : false)
|
105
|
-
end
|
106
|
-
|
107
|
-
# Checks if the string is a private tag (has an odd group number).
|
108
|
-
#
|
109
|
-
# @return [Boolean] true if it is a private tag, and false if not
|
110
|
-
#
|
111
|
-
def private?
|
112
|
-
return ((self.upcase =~ /\A\h{3}[1,3,5,7,9,B,D,F],\h{4}\z/) == nil ? false : true)
|
113
|
-
end
|
114
|
-
|
115
|
-
# Checks if the string is a valid tag (as defined by ruby-dicom: 'GGGG,EEEE').
|
116
|
-
#
|
117
|
-
# @return [Boolean] true if it is a valid tag, and false if not
|
118
|
-
#
|
119
|
-
def tag?
|
120
|
-
# Test that the string is composed of exactly 4 HEX characters, followed by a comma, then 4 more HEX characters:
|
121
|
-
return ((self.upcase =~ /\A\h{4},\h{4}\z/) == nil ? false : true)
|
122
|
-
end
|
123
|
-
|
124
|
-
# Converts the string to a proper DICOM element method name symbol.
|
125
|
-
#
|
126
|
-
# @return [Symbol] a DICOM element method name
|
127
|
-
#
|
128
|
-
def to_element_method
|
129
|
-
self.gsub(/^3/,'three_').gsub(/[#*?!]/,' ').gsub(', ',' ').gsub('&','and').gsub(' - ','_').gsub(' / ','_').gsub(/[\s\-\.\,\/\\]/,'_').gsub(/[\(\)\']/,'').gsub(/\_+/, '_').downcase.to_sym
|
130
|
-
end
|
131
|
-
|
132
|
-
# Redefines the core library unpack method, adding
|
133
|
-
# the ability to decode signed integers in big endian.
|
134
|
-
#
|
135
|
-
# @param [String] format a format string which decides the decoding scheme to use
|
136
|
-
# @return [Array<String, Integer, Float>] the decoded values
|
137
|
-
#
|
138
|
-
def unpack(format)
|
139
|
-
# Check for some custom unpack strings that we've invented:
|
140
|
-
case format
|
141
|
-
when "k*" # SS
|
142
|
-
# Unpack BE US, repack LE US, then finally unpack LE SS:
|
143
|
-
wrongly_unpacked = self.__original_unpack__('n*')
|
144
|
-
repacked = wrongly_unpacked.__original_pack__('S*')
|
145
|
-
correct = repacked.__original_unpack__('s*')
|
146
|
-
when 'r*' # SL
|
147
|
-
# Unpack BE UL, repack LE UL, then finally unpack LE SL:
|
148
|
-
wrongly_unpacked = self.__original_unpack__('N*')
|
149
|
-
repacked = wrongly_unpacked.__original_pack__('I*')
|
150
|
-
correct = repacked.__original_unpack__('l*')
|
151
|
-
else
|
152
|
-
# Call the original method for all other (normal) cases:
|
153
|
-
self.__original_unpack__(format)
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
end
|
158
|
-
|
159
|
-
|
160
|
-
# Extensions to the Array class.
|
161
|
-
# These mainly deal with encoding integer arrays as well as conversion between
|
162
|
-
# signed and unsigned integers.
|
163
|
-
#
|
164
|
-
class Array
|
165
|
-
|
166
|
-
# Renames the original pack method.
|
167
|
-
#
|
168
|
-
alias __original_pack__ pack
|
169
|
-
|
170
|
-
# Redefines the old pack method, adding the ability to encode signed integers in big endian
|
171
|
-
# (which surprisingly has not been supported out of the box in Ruby until version 1.9.3).
|
172
|
-
#
|
173
|
-
# @param [String] format a format string which decides the encoding scheme to use
|
174
|
-
# @return [String] the encoded binary string
|
175
|
-
#
|
176
|
-
def pack(format)
|
177
|
-
# FIXME: At some time in the future, when Ruby 1.9.3 can be set as required ruby version,
|
178
|
-
# this custom pack (as well as unpack) method can be discarded, and the desired endian
|
179
|
-
# encodings can probably be achieved with the new template strings introduced in 1.9.3.
|
180
|
-
#
|
181
|
-
# Check for some custom pack strings that we've invented:
|
182
|
-
case format
|
183
|
-
when 'k*' # SS
|
184
|
-
# Pack LE SS, re-unpack as LE US, then finally pack BE US:
|
185
|
-
wrongly_packed = self.__original_pack__('s*')
|
186
|
-
reunpacked = wrongly_packed.__original_unpack__('S*')
|
187
|
-
correct = reunpacked.__original_pack__('n*')
|
188
|
-
when 'r*' # SL
|
189
|
-
# Pack LE SL, re-unpack as LE UL, then finally pack BE UL:
|
190
|
-
wrongly_packed = self.__original_pack__('l*')
|
191
|
-
reunpacked = wrongly_packed.__original_unpack__('I*')
|
192
|
-
correct = reunpacked.__original_pack__('N*')
|
193
|
-
else
|
194
|
-
# Call the original method for all other (normal) cases:
|
195
|
-
self.__original_pack__(format)
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
# Packs an array of (unsigned) integers to a binary string (blob).
|
200
|
-
#
|
201
|
-
# @param [Integer] depth the bit depth to be used when encoding the unsigned integers
|
202
|
-
# @return [String] an encoded binary string
|
203
|
-
#
|
204
|
-
def to_blob(depth)
|
205
|
-
raise ArgumentError, "Expected Integer, got #{depth.class}" unless depth.is_a?(Integer)
|
206
|
-
raise ArgumentError, "Unsupported bit depth #{depth}." unless [8,16].include?(depth)
|
207
|
-
case depth
|
208
|
-
when 8
|
209
|
-
return self.pack('C*') # Unsigned char
|
210
|
-
when 16
|
211
|
-
return self.pack('S*') # Unsigned short, native byte order
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
# Shifts the integer values of the array to make a signed data set.
|
216
|
-
# The size of the shift is determined by the given bit depth.
|
217
|
-
#
|
218
|
-
# @param [Integer] depth the bit depth of the integers
|
219
|
-
# @return [Array<Integer>] an array of signed integers
|
220
|
-
#
|
221
|
-
def to_signed(depth)
|
222
|
-
raise ArgumentError, "Expected Integer, got #{depth.class}" unless depth.is_a?(Integer)
|
223
|
-
raise ArgumentError, "Unsupported bit depth #{depth}." unless [8,16].include?(depth)
|
224
|
-
case depth
|
225
|
-
when 8
|
226
|
-
return self.collect {|i| i - 128}
|
227
|
-
when 16
|
228
|
-
return self.collect {|i| i - 32768}
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
# Shifts the integer values of the array to make an unsigned data set.
|
233
|
-
# The size of the shift is determined by the given bit depth.
|
234
|
-
#
|
235
|
-
# @param [Integer] depth the bit depth of the integers
|
236
|
-
# @return [Array<Integer>] an array of unsigned integers
|
237
|
-
#
|
238
|
-
def to_unsigned(depth)
|
239
|
-
raise ArgumentError, "Expected Integer, got #{depth.class}" unless depth.is_a?(Integer)
|
240
|
-
raise ArgumentError, "Unsupported bit depth #{depth}." unless [8,16].include?(depth)
|
241
|
-
case depth
|
242
|
-
when 8
|
243
|
-
return self.collect {|i| i + 128}
|
244
|
-
when 16
|
245
|
-
return self.collect {|i| i + 32768}
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
end
|