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/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
|