dicom 0.9.5 → 0.9.6
Sign up to get free protection for your applications and to get access to all the features.
- 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/lib/dicom/audit_trail.rb
CHANGED
data/lib/dicom/d_client.rb
CHANGED
@@ -624,7 +624,7 @@ module DICOM
|
|
624
624
|
@link.build_data_fragment(@data_elements, presentation_context_id)
|
625
625
|
@link.transmit
|
626
626
|
# Receive confirmation response:
|
627
|
-
segments = @link.
|
627
|
+
segments = @link.receive_multiple_transmissions
|
628
628
|
process_returned_data(segments)
|
629
629
|
end
|
630
630
|
# Close the DICOM link:
|
data/lib/dicom/d_library.rb
CHANGED
@@ -24,9 +24,9 @@ module DICOM
|
|
24
24
|
@methods_from_names = Hash.new
|
25
25
|
@names_from_methods = Hash.new
|
26
26
|
# Load the elements dictionary:
|
27
|
-
add_element_dictionary("#{ROOT_DIR}/dictionary/elements.
|
27
|
+
add_element_dictionary("#{ROOT_DIR}/dictionary/elements.tsv")
|
28
28
|
# Load the unique identifiers dictionary:
|
29
|
-
add_uid_dictionary("#{ROOT_DIR}/dictionary/uids.
|
29
|
+
add_uid_dictionary("#{ROOT_DIR}/dictionary/uids.tsv")
|
30
30
|
end
|
31
31
|
|
32
32
|
# Adds a custom DictionaryElement to the ruby-dicom element dictionary.
|
@@ -153,22 +153,11 @@ module DICOM
|
|
153
153
|
if tag.private?
|
154
154
|
element = DictionaryElement.new(tag, 'Private', ['UN'], '1', '')
|
155
155
|
else
|
156
|
-
|
157
|
-
element = DictionaryElement.new(tag, de.name, de.vrs, de.vm, de.retired)
|
158
|
-
elsif !(de = @elements["#{tag[0..3]},xxxx"]).nil? # 1010,xxxx
|
159
|
-
element = DictionaryElement.new(tag, de.name, de.vrs, de.vm, de.retired)
|
160
|
-
elsif !(de = @elements["#{tag[0..1]}xx,#{tag[5..8]}"]).nil? # hhxx,hhhh
|
161
|
-
element = DictionaryElement.new(tag, de.name, de.vrs, de.vm, de.retired)
|
162
|
-
elsif !(de = @elements["#{tag[0..6]}x#{tag[8]}"]).nil? # 0028,hhxh
|
163
|
-
element = DictionaryElement.new(tag, de.name, de.vrs, de.vm, de.retired)
|
164
|
-
else
|
165
|
-
# We are facing an unknown (but not private) tag:
|
166
|
-
element = DictionaryElement.new(tag, 'Unknown', ['UN'], '1', '')
|
167
|
-
end
|
156
|
+
element = unknown_or_range_element(tag)
|
168
157
|
end
|
169
158
|
end
|
170
159
|
end
|
171
|
-
|
160
|
+
element
|
172
161
|
end
|
173
162
|
|
174
163
|
# Extracts, and returns, all transfer syntaxes and SOP Classes from the dictionary.
|
@@ -230,6 +219,47 @@ module DICOM
|
|
230
219
|
@uids[value]
|
231
220
|
end
|
232
221
|
|
222
|
+
|
223
|
+
private
|
224
|
+
|
225
|
+
|
226
|
+
# Creates a list of possible 'range' tag candidates based on the given tag.
|
227
|
+
# Usually tags are uniquely defined in the DICOM dictionary, and the given
|
228
|
+
# tag can be matched directly. However, for a small set of known tags, the
|
229
|
+
# dictionary allows a range of tags to be associated with a specific
|
230
|
+
# entry. This method creates an array of candidate tags which are processed
|
231
|
+
# in order to match against these ranges.
|
232
|
+
#
|
233
|
+
# @param [String] tag the element tag
|
234
|
+
# @return [Array<String>] processed candidate tags
|
235
|
+
#
|
236
|
+
def range_candidates(tag)
|
237
|
+
[
|
238
|
+
"#{tag[0..3]},xxx#{tag[8]}", # 1000,xxxh
|
239
|
+
"#{tag[0..3]},xxxx", # 1010,xxxx
|
240
|
+
"#{tag[0..1]}xx,#{tag[5..8]}", # hhxx,hhhh
|
241
|
+
"#{tag[0..6]}x#{tag[8]}" # 0028,hhxh
|
242
|
+
]
|
243
|
+
end
|
244
|
+
|
245
|
+
# Matches a tag against the possible range tag candidates, and if no match
|
246
|
+
# is found, returns a dictionary element representing an unknown tag.
|
247
|
+
#
|
248
|
+
# @param [String] tag the element tag
|
249
|
+
# @return [DictionaryElement] a matched range element or an unknown element
|
250
|
+
#
|
251
|
+
def unknown_or_range_element(tag)
|
252
|
+
element = nil
|
253
|
+
range_candidates(tag).each do |range_candidate_tag|
|
254
|
+
if de = @elements[range_candidate_tag]
|
255
|
+
element = DictionaryElement.new(tag, de.name, de.vrs, de.vm, de.retired)
|
256
|
+
break
|
257
|
+
end
|
258
|
+
end
|
259
|
+
# If nothing was matched, we are facing an unknown (but not private) tag:
|
260
|
+
element ||= DictionaryElement.new(tag, 'Unknown', ['UN'], '1', '')
|
261
|
+
end
|
262
|
+
|
233
263
|
end
|
234
264
|
|
235
265
|
end
|
data/lib/dicom/d_object.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright 2008-
|
1
|
+
# Copyright 2008-2014 Christoffer Lervag
|
2
2
|
#
|
3
3
|
# This program is free software: you can redistribute it and/or modify
|
4
4
|
# it under the terms of the GNU General Public License as published by
|
@@ -49,7 +49,7 @@ module DICOM
|
|
49
49
|
# @note Designed for the HTTP protocol only.
|
50
50
|
# @note Whether this method should be included or removed from ruby-dicom is up for debate.
|
51
51
|
#
|
52
|
-
# @param [String] link a hyperlink string which specifies remote location of the DICOM file to be loaded
|
52
|
+
# @param [String] link a hyperlink string which specifies remote location of the DICOM file to be loaded
|
53
53
|
# @return [DObject] the created DObject instance
|
54
54
|
#
|
55
55
|
def self.get(link)
|
@@ -410,25 +410,25 @@ module DICOM
|
|
410
410
|
# to ensure that a valid DICOM object is encoded.
|
411
411
|
#
|
412
412
|
def insert_missing_meta
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
413
|
+
{
|
414
|
+
'0002,0001' => [0,1], # File Meta Information Version
|
415
|
+
'0002,0002' => value('0008,0016'), # Media Storage SOP Class UID
|
416
|
+
'0002,0003' => value('0008,0018'), # Media Storage SOP Instance UID
|
417
|
+
'0002,0010' => transfer_syntax, # Transfer Syntax UID
|
418
|
+
'0002,0016' => DICOM.source_app_title, # Source Application Entity Title
|
419
|
+
}.each_pair do |tag, value|
|
420
|
+
add_element(tag, value) unless exists?(tag)
|
421
|
+
end
|
422
|
+
if !exists?("0002,0012") && !exists?("0002,0013")
|
422
423
|
# Implementation Class UID:
|
423
|
-
|
424
|
+
add_element("0002,0012", UID_ROOT)
|
424
425
|
# Implementation Version Name:
|
425
|
-
|
426
|
+
add_element("0002,0013", NAME)
|
426
427
|
end
|
427
|
-
#
|
428
|
-
|
429
|
-
# Group Length: Delete the old one (if it exists) before creating a new one.
|
428
|
+
# Delete the old group length first (if it exists) to avoid a miscount
|
429
|
+
# in the coming group length determination.
|
430
430
|
delete("0002,0000")
|
431
|
-
|
431
|
+
add_element("0002,0000", meta_group_length)
|
432
432
|
end
|
433
433
|
|
434
434
|
# Determines the length of the meta group in the DObject instance.
|
@@ -449,7 +449,7 @@ module DICOM
|
|
449
449
|
end
|
450
450
|
group_length += tag + vr + length + element.bin.length
|
451
451
|
end
|
452
|
-
|
452
|
+
group_length
|
453
453
|
end
|
454
454
|
|
455
455
|
# Collects the attributes of this instance.
|
data/lib/dicom/d_read.rb
CHANGED
@@ -2,9 +2,35 @@ module DICOM
|
|
2
2
|
|
3
3
|
class Parent
|
4
4
|
|
5
|
+
# Loads data from an encoded DICOM string and creates
|
6
|
+
# items and elements which are linked to this instance.
|
7
|
+
#
|
8
|
+
# @param [String] bin an encoded binary string containing DICOM information
|
9
|
+
# @param [String] syntax the transfer syntax to use when decoding the DICOM string
|
10
|
+
#
|
11
|
+
def parse(bin, syntax)
|
12
|
+
raise ArgumentError, "Invalid argument 'bin'. Expected String, got #{bin.class}." unless bin.is_a?(String)
|
13
|
+
raise ArgumentError, "Invalid argument 'syntax'. Expected String, got #{syntax.class}." unless syntax.is_a?(String)
|
14
|
+
read(bin, signature=false, :syntax => syntax)
|
15
|
+
end
|
16
|
+
|
17
|
+
|
5
18
|
private
|
6
19
|
|
7
20
|
|
21
|
+
# Checks whether the given tag is a duplicate of an existing tag with this parent.
|
22
|
+
#
|
23
|
+
# @param [String] tag the tag of the candidate duplicate elemental
|
24
|
+
# @param [String] elemental the duplicate elemental type (e.g. Sequence, Element)
|
25
|
+
#
|
26
|
+
def check_duplicate(tag, elemental)
|
27
|
+
if @current_parent[tag]
|
28
|
+
gp = @current_parent.parent ? "#{@current_parent.parent.representation} => " : ''
|
29
|
+
p = @current_parent.representation
|
30
|
+
logger.warn("Duplicate #{elemental} (#{tag}) detected at level: #{gp}#{p}")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
8
34
|
# Checks for the official DICOM header signature.
|
9
35
|
#
|
10
36
|
# @return [Boolean] true if the proper signature is present, false if not, and nil if the string was shorter then the length of the DICOM signature
|
@@ -88,8 +114,7 @@ module DICOM
|
|
88
114
|
# Create an Element from the gathered data:
|
89
115
|
if level_vr == "SQ" or tag == ITEM_TAG
|
90
116
|
if level_vr == "SQ"
|
91
|
-
|
92
|
-
logger.warn("Duplicate Sequence (#{tag}) detected at level #{@current_parent.parent.is_a?(DObject) ? 'DObject' : @current_parent.parent.tag + ' => ' if @current_parent.parent}#{@current_parent.is_a?(DObject) ? 'DObject' : @current_parent.tag}") if @current_parent[tag]
|
117
|
+
check_duplicate(tag, 'Sequence')
|
93
118
|
unless @current_parent[tag] and !@overwrite
|
94
119
|
@current_element = Sequence.new(tag, :length => length, :name => name, :parent => @current_parent, :vr => vr)
|
95
120
|
else
|
@@ -127,8 +152,7 @@ module DICOM
|
|
127
152
|
# The occurance of such a tag indicates that a sequence or item has ended, and the parent must be changed:
|
128
153
|
@current_parent = @current_parent.parent
|
129
154
|
else
|
130
|
-
|
131
|
-
logger.warn("Duplicate Element (#{tag}) detected at level #{@current_parent.parent.is_a?(DObject) ? 'DObject' : @current_parent.parent.tag + ' => ' if @current_parent.parent}#{@current_parent.is_a?(DObject) ? 'DObject' : @current_parent.tag}") if @current_parent[tag]
|
155
|
+
check_duplicate(tag, 'Element')
|
132
156
|
unless @current_parent[tag] and !@overwrite
|
133
157
|
@current_element = Element.new(tag, value, :bin => bin, :name => name, :parent => @current_parent, :vr => vr)
|
134
158
|
# Check that the data stream didn't end abruptly:
|
data/lib/dicom/d_write.rb
CHANGED
@@ -17,36 +17,31 @@ module DICOM
|
|
17
17
|
unless @segments
|
18
18
|
@stream.add_last(string)
|
19
19
|
else
|
20
|
-
|
21
|
-
if (string.length + @stream.length) > @max_size
|
22
|
-
# Duplicate the string as not to ruin the binary of the data element with our slicing:
|
23
|
-
segment = string.dup
|
24
|
-
append = segment.slice!(0, @max_size-@stream.length)
|
25
|
-
# Join these strings together and add them to the segments:
|
26
|
-
@segments << @stream.export + append
|
27
|
-
if (30 + segment.length) > @max_size
|
28
|
-
# The remaining part of the string is bigger than the max limit, fill up more segments:
|
29
|
-
# How many full segments will this string fill?
|
30
|
-
number = (segment.length/@max_size.to_f).floor
|
31
|
-
number.times {@segments << segment.slice!(0, @max_size)}
|
32
|
-
# The remaining part is added to the stream:
|
33
|
-
@stream.add_last(segment)
|
34
|
-
else
|
35
|
-
# The rest of the string is small enough that it can be added to the stream:
|
36
|
-
@stream.add_last(segment)
|
37
|
-
end
|
38
|
-
elsif (30 + @stream.length) > @max_size
|
39
|
-
# End the current segment, and start on a new segment for this string.
|
40
|
-
@segments << @stream.export
|
41
|
-
@stream.add_last(string)
|
42
|
-
else
|
43
|
-
# We are nowhere near the limit, simply add the string:
|
44
|
-
@stream.add_last(string)
|
45
|
-
end
|
20
|
+
add_with_segmentation(string)
|
46
21
|
end
|
47
22
|
end
|
48
23
|
end
|
49
24
|
|
25
|
+
# Adds an encoded string to the output stream, while keeping track of the
|
26
|
+
# accumulated size of the output stream, splitting it up as necessary, and
|
27
|
+
# transferring the encoded string fragments to an array.
|
28
|
+
#
|
29
|
+
# @param [String] string a pre-encoded string
|
30
|
+
#
|
31
|
+
def add_with_segmentation(string)
|
32
|
+
# As the encoded DICOM string will be cut in multiple, smaller pieces, we need to monitor the length of our encoded strings:
|
33
|
+
if (string.length + @stream.length) > @max_size
|
34
|
+
split_and_add(string)
|
35
|
+
elsif (30 + @stream.length) > @max_size
|
36
|
+
# End the current segment, and start on a new segment for this string.
|
37
|
+
@segments << @stream.export
|
38
|
+
@stream.add_last(string)
|
39
|
+
else
|
40
|
+
# We are nowhere near the limit, simply add the string:
|
41
|
+
@stream.add_last(string)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
50
45
|
# Toggles the status for enclosed pixel data.
|
51
46
|
#
|
52
47
|
# @param [Element, Item, Sequence] element a data element
|
@@ -124,6 +119,34 @@ module DICOM
|
|
124
119
|
end
|
125
120
|
end
|
126
121
|
|
122
|
+
# Splits a pre-encoded string in parts and adds it to the segments instance
|
123
|
+
# array.
|
124
|
+
#
|
125
|
+
# @param [String] string a pre-encoded string
|
126
|
+
#
|
127
|
+
def split_and_add(string)
|
128
|
+
# Duplicate the string as not to ruin the binary of the data element with our slicing:
|
129
|
+
segment = string.dup
|
130
|
+
append = segment.slice!(0, @max_size-@stream.length)
|
131
|
+
# Clear out the stream along with a small part of the string:
|
132
|
+
@segments << @stream.export + append
|
133
|
+
if (30 + segment.length) > @max_size
|
134
|
+
# The remaining part of the string is bigger than the max limit, fill up more segments:
|
135
|
+
# How many full segments will this string fill?
|
136
|
+
number = (segment.length/@max_size.to_f).floor
|
137
|
+
start_index = 0
|
138
|
+
number.times {
|
139
|
+
@segments << segment.slice(start_index, @max_size)
|
140
|
+
start_index += @max_size
|
141
|
+
}
|
142
|
+
# The remaining part is added to the stream:
|
143
|
+
@stream.add_last(segment.slice(start_index, segment.length - start_index))
|
144
|
+
else
|
145
|
+
# The rest of the string is small enough that it can be added to the stream:
|
146
|
+
@stream.add_last(segment)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
127
150
|
# Encodes and writes a single data element.
|
128
151
|
#
|
129
152
|
# @param [Element, Item, Sequence] element a data element
|
File without changes
|
File without changes
|
data/lib/dicom/element.rb
CHANGED
@@ -35,7 +35,7 @@ module DICOM
|
|
35
35
|
raise ArgumentError, "The supplied tag (#{tag}) is not valid. The tag must be a string of the form 'GGGG,EEEE'." unless tag.is_a?(String) && tag.tag?
|
36
36
|
# Set instance variables:
|
37
37
|
@tag = tag.upcase
|
38
|
-
# We may
|
38
|
+
# We may need to retrieve name and vr from the library:
|
39
39
|
if options[:name] and options[:vr]
|
40
40
|
@name = options[:name]
|
41
41
|
@vr = options[:vr].upcase
|
@@ -212,9 +212,9 @@ module DICOM
|
|
212
212
|
# In most cases the original encoding is IS0-8859-1 (ISO_IR 100), but if
|
213
213
|
# it is not specified in the DICOM object, or if the specified string
|
214
214
|
# is not recognized, ASCII-8BIT is assumed.
|
215
|
-
@value.encode('UTF-8', ENCODING_NAME[character_set]
|
215
|
+
@value.encode('UTF-8', ENCODING_NAME[character_set])
|
216
216
|
# If unpleasant encoding exceptions occur, the below version may be considered:
|
217
|
-
#@value.encode('UTF-8', ENCODING_NAME[character_set]
|
217
|
+
#@value.encode('UTF-8', ENCODING_NAME[character_set], :invalid => :replace, :undef => :replace)
|
218
218
|
else
|
219
219
|
@value
|
220
220
|
end
|
@@ -229,8 +229,7 @@ module DICOM
|
|
229
229
|
# @param [String, Integer, Float, Array] new_value a formatted value that is assigned to the element
|
230
230
|
#
|
231
231
|
def value=(new_value)
|
232
|
-
|
233
|
-
if conversion == :to_s
|
232
|
+
if VALUE_CONVERSION[@vr] == :to_s
|
234
233
|
# Unless this is actually the Character Set data element,
|
235
234
|
# get the character set (note that it may not be available):
|
236
235
|
character_set = (@tag != '0008,0005' && top_parent.is_a?(DObject)) ? top_parent.value('0008,0005') : nil
|
@@ -238,7 +237,7 @@ module DICOM
|
|
238
237
|
# In most cases the DObject encoding is IS0-8859-1 (ISO_IR 100), but if
|
239
238
|
# it is not specified in the DICOM object, or if the specified string
|
240
239
|
# is not recognized, ASCII-8BIT is assumed.
|
241
|
-
@value = new_value.to_s.encode(ENCODING_NAME[character_set]
|
240
|
+
@value = new_value.to_s.encode(ENCODING_NAME[character_set], new_value.to_s.encoding.name)
|
242
241
|
@bin = encode(@value)
|
243
242
|
else
|
244
243
|
# We may have an array (of numbers) which needs to be passed directly to
|
@@ -247,7 +246,7 @@ module DICOM
|
|
247
246
|
@value = new_value
|
248
247
|
@bin = encode(@value)
|
249
248
|
else
|
250
|
-
@value = new_value.send(
|
249
|
+
@value = new_value.send(VALUE_CONVERSION[@vr])
|
251
250
|
@bin = encode(@value)
|
252
251
|
end
|
253
252
|
end
|
data/lib/dicom/elemental.rb
CHANGED
@@ -0,0 +1,64 @@
|
|
1
|
+
module DICOM
|
2
|
+
|
3
|
+
# The ElementalParent mix-in module contains methods that are common among
|
4
|
+
# the two elemental parent classes: Item & Sequence
|
5
|
+
#
|
6
|
+
module ElementalParent
|
7
|
+
|
8
|
+
# Adds a child item to a Sequence (or Item in some cases where pixel data is encapsulated).
|
9
|
+
#
|
10
|
+
# If no existing Item is given, a new item will be created and added.
|
11
|
+
#
|
12
|
+
# @note Items are specified by index (starting at 0) instead of a tag string!
|
13
|
+
#
|
14
|
+
# @param [Item] item the Item instance to be added
|
15
|
+
# @param [Hash] options the options used for adding the item
|
16
|
+
# option options [Integer] :if specified, forces the item to be inserted at that specific index (Item number)
|
17
|
+
# option options [Boolean] :no_follow when true, the method does not update the parent attribute of the child that is added
|
18
|
+
# * <tt>options</tt> -- A hash of parameters.
|
19
|
+
# @example Add an empty Item to a specific Sequence
|
20
|
+
# dcm["3006,0020"].add_item
|
21
|
+
# @example Add an existing Item at the 2nd item position/index in the specific Sequence
|
22
|
+
# dcm["3006,0020"].add_item(my_item, :index => 1)
|
23
|
+
#
|
24
|
+
def add_item(item=nil, options={})
|
25
|
+
if item
|
26
|
+
if item.is_a?(Item)
|
27
|
+
if index = options[:index]
|
28
|
+
# This Item will take a specific index, and all existing Items with index higher or equal to this number will have their index increased by one.
|
29
|
+
# Check if index is valid (must be an existing index):
|
30
|
+
if index >= 0
|
31
|
+
# If the index value is larger than the max index present, we dont need to modify the existing items.
|
32
|
+
if index < @tags.length
|
33
|
+
@tags = @tags.create_key_gap_at(index)
|
34
|
+
else
|
35
|
+
# Set the index value one higher than the already existing max value:
|
36
|
+
index = @tags.length
|
37
|
+
end
|
38
|
+
#,Add the new Item and set its index:
|
39
|
+
@tags[index] = item
|
40
|
+
item.index = index
|
41
|
+
else
|
42
|
+
raise ArgumentError, "The specified index (#{index}) is out of range (must be a positive integer)."
|
43
|
+
end
|
44
|
+
else
|
45
|
+
# Add the existing Item to this Sequence:
|
46
|
+
index = @tags.length
|
47
|
+
@tags[index] = item
|
48
|
+
# Let the Item know what index key it's got in it's parent's Hash:
|
49
|
+
item.index = index
|
50
|
+
end
|
51
|
+
# Set ourself as this item's new parent:
|
52
|
+
item.set_parent(self) unless options[:no_follow]
|
53
|
+
else
|
54
|
+
raise ArgumentError, "Expected Item, got #{item.class}"
|
55
|
+
end
|
56
|
+
else
|
57
|
+
# Create an empty item with self as parent:
|
58
|
+
item = Item.new(:parent => self)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Extensions to the Array class.
|
2
|
+
# These mainly deal with encoding integer arrays as well as conversion between
|
3
|
+
# signed and unsigned integers.
|
4
|
+
#
|
5
|
+
class Array
|
6
|
+
|
7
|
+
# Packs an array of (unsigned) integers to a binary string (blob).
|
8
|
+
#
|
9
|
+
# @param [Integer] depth the bit depth to be used when encoding the unsigned integers
|
10
|
+
# @return [String] an encoded binary string
|
11
|
+
#
|
12
|
+
def to_blob(depth)
|
13
|
+
raise ArgumentError, "Expected Integer, got #{depth.class}" unless depth.is_a?(Integer)
|
14
|
+
raise ArgumentError, "Unsupported bit depth #{depth}." unless [8,16].include?(depth)
|
15
|
+
case depth
|
16
|
+
when 8
|
17
|
+
return self.pack('C*') # Unsigned char
|
18
|
+
when 16
|
19
|
+
return self.pack('S<*') # Unsigned short, little endian byte order
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Shifts the integer values of the array to make a signed data set.
|
24
|
+
# The size of the shift is determined by the given bit depth.
|
25
|
+
#
|
26
|
+
# @param [Integer] depth the bit depth of the integers
|
27
|
+
# @return [Array<Integer>] an array of signed integers
|
28
|
+
#
|
29
|
+
def to_signed(depth)
|
30
|
+
case depth
|
31
|
+
when 8
|
32
|
+
self.collect {|i| i - 128}
|
33
|
+
when 16
|
34
|
+
self.collect {|i| i - 32768}
|
35
|
+
else
|
36
|
+
raise ArgumentError, "Unknown or unsupported bit depth: #{depth}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Shifts the integer values of the array to make an unsigned data set.
|
41
|
+
# The size of the shift is determined by the given bit depth.
|
42
|
+
#
|
43
|
+
# @param [Integer] depth the bit depth of the integers
|
44
|
+
# @return [Array<Integer>] an array of unsigned integers
|
45
|
+
#
|
46
|
+
def to_unsigned(depth)
|
47
|
+
case depth
|
48
|
+
when 8
|
49
|
+
self.collect {|i| i + 128}
|
50
|
+
when 16
|
51
|
+
self.collect {|i| i + 32768}
|
52
|
+
else
|
53
|
+
raise ArgumentError, "Unknown or unsupported bit depth: #{depth}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|