dicom 0.6.1 → 0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- # Copyright 2009 Christoffer Lervag
1
+ # Copyright 2009-2010 Christoffer Lervag
2
2
 
3
3
  # This file contains the Stream class, which handles all encoding to and
4
4
  # decoding from binary strings. It is used by the other components of
@@ -0,0 +1,47 @@
1
+ # This file contains extensions to the Ruby library which are used by Ruby DICOM.
2
+
3
+ class Array
4
+
5
+ # Searching all indices, or a subset of indices, in an array, and returning all indices
6
+ # where the array's value equals the queried value.
7
+ def all_indices(array, value)
8
+ result = []
9
+ self.each do |pos|
10
+ result << pos if array[pos] == value
11
+ end
12
+ return result
13
+ end
14
+
15
+ # Similar to method above, but this one returns the position of all strings that
16
+ # contain the query string (exact match not required).
17
+ def all_indices_partial_match(array, value)
18
+ result = []
19
+ self.each do |pos|
20
+ result << pos if array[pos].include?(value)
21
+ end
22
+ return result
23
+ end
24
+
25
+ end
26
+
27
+ class String
28
+
29
+ # Check if a given string appears to be a valid tag (GGGG,EEEE) by regexp matching.
30
+ # The method tests that the string is exactly composed of 4 HEX characters, followed by
31
+ # a comma, then 4 new HEX characters, which constitutes the tag format used by Ruby DICOM.
32
+ def is_a_tag?
33
+ result = false
34
+ #result = true if self =~ /\A\h{4},\h{4}\z/ # (turns out the hex reference '\h' isnt compatible with ruby 1.8)
35
+ result = true if self =~ /\A[a-fA-F\d]{4},[a-fA-F\d]{4}\z/
36
+ return result
37
+ end
38
+
39
+ # Check if a given tag string indicates a private tag (Odd group number) by doing a regexp matching.
40
+ def private?
41
+ result = false
42
+ #result = true if self.upcase =~ /\A\h{3}[1,3,5,7,9,B,D,F],\h{4}\z/ # (incompatible with ruby 1.8)
43
+ result = true if self.upcase =~ /\A[a-fA-F\d]{3}[1,3,5,7,9,B,D,F],[a-fA-F\d]{4}\z/
44
+ return result
45
+ end
46
+
47
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dicom
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: "0.7"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christoffer Lervag
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-23 00:00:00 +02:00
12
+ date: 2010-02-28 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -22,20 +22,21 @@ extensions: []
22
22
  extra_rdoc_files: []
23
23
 
24
24
  files:
25
- - lib/DObject.rb
26
- - lib/DRead.rb
27
- - lib/DWrite.rb
28
25
  - lib/dicom.rb
29
- - lib/DServer.rb
30
- - lib/DClient.rb
31
- - lib/Stream.rb
32
- - lib/Anonymizer.rb
33
- - lib/Link.rb
34
- - lib/DLibrary.rb
35
- - lib/ruby_extensions.rb
36
- - lib/Dictionary.rb
37
- - DOCUMENTATION
26
+ - lib/dicom/DObject.rb
27
+ - lib/dicom/Link.rb
28
+ - lib/dicom/DLibrary.rb
29
+ - lib/dicom/DWrite.rb
30
+ - lib/dicom/Dictionary.rb
31
+ - lib/dicom/FileHandler.rb
32
+ - lib/dicom/DServer.rb
33
+ - lib/dicom/DClient.rb
34
+ - lib/dicom/Anonymizer.rb
35
+ - lib/dicom/Stream.rb
36
+ - lib/dicom/ruby_extensions.rb
37
+ - lib/dicom/DRead.rb
38
38
  - README
39
+ - DOCUMENTATION
39
40
  - COPYING
40
41
  - CHANGELOG
41
42
  has_rdoc: true
@@ -62,7 +63,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
63
  requirements: []
63
64
 
64
65
  rubyforge_project: dicom
65
- rubygems_version: 1.3.3
66
+ rubygems_version: 1.3.5
66
67
  signing_key:
67
68
  specification_version: 3
68
69
  summary: Library for handling DICOM files and DICOM network communication.
@@ -1,1194 +0,0 @@
1
- # Copyright 2008-2009 Christoffer Lervag
2
- #
3
- # This program is free software: you can redistribute it and/or modify
4
- # it under the terms of the GNU General Public License as published by
5
- # the Free Software Foundation, either version 3 of the License, or
6
- # (at your option) any later version.
7
- #
8
- # This program is distributed in the hope that it will be useful,
9
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
- # GNU General Public License for more details.
12
- #
13
- # You should have received a copy of the GNU General Public License
14
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
- #
16
- #--------------------------------------------------------------------------------------------------
17
-
18
- # TODO:
19
- # -Improve the retrieve file network functionality
20
- # -Make the networking more intelligent in its handling of (unexpected) messages
21
- # -Support for writing complex (hierarchical) DICOM files (basic write support is featured).
22
- # -Full support for compressed image data.
23
- # -Read 12 bit image data correctly.
24
- # -Support for color image data to get_image_narray and get_image_magick.
25
- # -Complete support for Big endian (basic support is already featured).
26
- # -Complete support for multiple frame image data to NArray and RMagick objects (partial support already featured).
27
- # -Make the image handling more intelligent with respect to interpreting data elements that hold information on the image and its properties.
28
-
29
- module DICOM
30
-
31
- # Class for interacting with the DICOM object.
32
- class DObject
33
-
34
- attr_reader :read_success, :write_success, :modality, :errors, :segments,
35
- :names, :tags, :types, :lengths, :values, :raw, :levels
36
-
37
- # Initialize the DObject instance.
38
- def initialize(string=nil, options={})
39
- # Process option values, setting defaults for the ones that are not specified:
40
- @verbose = options[:verbose]
41
- @lib = options[:lib] || DLibrary.new
42
- segment_size = options[:segment_size]
43
- bin = options[:bin]
44
- syntax = options[:syntax]
45
- # Default verbosity is true:
46
- @verbose = true if @verbose == nil
47
-
48
- # Initialize variables that will be used for the DICOM object:
49
- @names = Array.new
50
- @tags = Array.new
51
- @types = Array.new
52
- @lengths = Array.new
53
- @values = Array.new
54
- @raw = Array.new
55
- @levels = Array.new
56
- # Array that will holde any messages generated while reading the DICOM file:
57
- @errors = Array.new
58
- # Array to keep track of sequences/structure of the dicom elements:
59
- @sequence = Array.new
60
- # Index of last element in data element arrays:
61
- @last_index=0
62
- # Structural information (default values):
63
- @compression = false
64
- @color = false
65
- @explicit = true
66
- @file_endian = false
67
- # Information about the DICOM object:
68
- @modality = nil
69
- # Control variables:
70
- @read_success = false
71
- # Initialize a Stream instance which is used for encoding/decoding:
72
- @stream = Stream.new(nil, @file_endian, @explicit)
73
-
74
- # If a (valid) file name string is supplied, call the method to read the DICOM file:
75
- if string.is_a?(String) and string != ""
76
- @file = string
77
- read(string, :bin => bin, :segment_size => segment_size, :syntax => syntax)
78
- end
79
- end # of initialize
80
-
81
-
82
- # Returns a DICOM object by reading the file specified.
83
- # This is accomplished by initliazing the DRead class, which loads DICOM information to arrays.
84
- # For the time being, this method is called automatically when initializing the DObject class,
85
- # but in the future, when write support is added, this method may have to be called manually.
86
- def read(string, options = {})
87
- r = DRead.new(string, :lib => @lib, :sys_endian => @sys_endian, :bin => options[:bin], :syntax => options[:syntax])
88
- # Store the data to the instance variables if the readout was a success:
89
- if r.success
90
- @read_success = true
91
- @names = r.names
92
- @tags = r.tags
93
- @types = r.types
94
- @lengths = r.lengths
95
- @values = r.values
96
- @raw = r.raw
97
- @levels = r.levels
98
- @explicit = r.explicit
99
- @file_endian = r.file_endian
100
- # Update Stream instance with settings from this DICOM file:
101
- @stream.set_endian(@file_endian)
102
- @stream.explicit = @explicit
103
- # Index of last data element in element arrays:
104
- @last_index=@names.length-1
105
- # Update status variables for this object:
106
- check_properties
107
- # Set the modality of the DICOM object:
108
- set_modality
109
- else
110
- @read_success = false
111
- end
112
- # Check if a partial extraction has been requested (used for network communication purposes)
113
- if options[:segment_size]
114
- @segments = r.extract_segments(options[:segment_size])
115
- end
116
- # If any messages has been recorded, send these to the message handling method:
117
- add_msg(r.msg) if r.msg.size != 0
118
- end
119
-
120
-
121
- # Transfers necessary information from the DObject to the DWrite class, which
122
- # will attempt to write this information to a valid DICOM file.
123
- def write(file_name, transfer_syntax = nil)
124
- w = set_write_object(file_name, transfer_syntax)
125
- w.write
126
- # Write process succesful?
127
- @write_success = w.success
128
- # If any messages has been recorded, send these to the message handling method:
129
- add_msg(w.msg) if w.msg.size != 0
130
- end
131
-
132
-
133
- # Encodes the DICOM object into a series of binary string segments with a specified maximum length.
134
- def encode_segments(size)
135
- w = set_write_object
136
- @segments = w.encode_segments(size)
137
- # Write process succesful?
138
- @write_success = w.success
139
- # If any messages has been recorded, send these to the message handling method:
140
- add_msg(w.msg) if w.msg.size != 0
141
- end
142
-
143
-
144
- #################################################
145
- # START OF METHODS FOR READING INFORMATION FROM DICOM OBJECT:
146
- #################################################
147
-
148
-
149
- # Checks the status of the pixel data that has been read from the DICOM file: whether it exists at all and if its greyscale or color.
150
- # Modifies instance variable @color if color image is detected and instance variable @compression if no pixel data is detected.
151
- def check_properties
152
- # Check if pixel data is present:
153
- if @tags.index("7FE0,0010") == nil
154
- # No pixel data in DICOM file:
155
- @compression = nil
156
- else
157
- @compression = @lib.get_compression(get_value("0002,0010", :silent => true))
158
- end
159
- # Set color variable as true if our object contain a color image:
160
- col_string = get_value("0028,0004", :silent => true)
161
- if col_string != false
162
- if (col_string.include? "RGB") or (col_string.include? "COLOR") or (col_string.include? "COLOUR")
163
- @color = true
164
- end
165
- end
166
- end
167
-
168
-
169
- # Returns image data from the provided element index, performing decompression of data if necessary.
170
- def read_image_magick(pos, columns, rows)
171
- if pos == false or columns == false or rows == false
172
- add_msg("Error: Method read_image_magick does not have enough data available to build an image object.")
173
- return false
174
- end
175
- unless @compression
176
- # Non-compressed, just return the array contained on the particular element:
177
- image_data = get_pixels(pos)
178
- image = Magick::Image.new(columns,rows)
179
- image.import_pixels(0, 0, columns, rows, "I", image_data)
180
- return image
181
- else
182
- # Image data is compressed, we will attempt to deflate it using RMagick (ImageMagick):
183
- begin
184
- image = Magick::Image.from_blob(@raw[pos])
185
- return image
186
- rescue
187
- add_msg("RMagick did not succeed in decoding the compressed image data. Returning false.")
188
- return false
189
- end
190
- end
191
- end
192
-
193
-
194
- # Returns a 3d NArray object where the array dimensions are related to [frames, columns, rows].
195
- # To call this method the user needs to have performed " require 'narray' " in advance.
196
- def get_image_narray
197
- # Does pixel data exist at all in the DICOM object?
198
- if @compression == nil
199
- add_msg("It seems pixel data is not present in this DICOM object: returning false.")
200
- return false
201
- end
202
- # No support yet for retrieving compressed data:
203
- if @compression == true
204
- add_msg("Reading compressed data to a NArray object not supported yet: returning false.")
205
- return false
206
- end
207
- # No support yet for retrieving color pixel data:
208
- if @color
209
- add_msg("Warning: Unpacking color pixel data is not supported yet for this method: returning false.")
210
- return false
211
- end
212
- # Gather information about the dimensions of the image data:
213
- rows = get_value("0028,0010")
214
- columns = get_value("0028,0011")
215
- frames = get_frames
216
- image_pos = get_image_pos
217
- # Creating a NArray object using int to make sure we have a big enough range for our numbers:
218
- image = NArray.int(frames,columns,rows)
219
- image_temp = NArray.int(columns,rows)
220
- # Handling of image data will depend on whether we have one or more frames,
221
- # and if it is located in one or more elements:
222
- if image_pos.size == 1
223
- # All of the image data is located in one element:
224
- image_data = get_pixels(image_pos[0])
225
- #image_data = get_image_data(image_pos[0])
226
- (0..frames-1).each do |i|
227
- (0..columns*rows-1).each do |j|
228
- image_temp[j] = image_data[j+i*columns*rows]
229
- end
230
- image[i,true,true] = image_temp
231
- end
232
- else
233
- # Image data is encapsulated in items:
234
- (0..frames-1).each do |i|
235
- image_data=get_value(image_pos[i])
236
- #image_data = get_image_data(image_pos[i])
237
- (0..columns*rows-1).each do |j|
238
- image_temp[j] = image_data[j+i*columns*rows]
239
- end
240
- image[i,true,true] = image_temp
241
- end
242
- end
243
- # Turn around the images to get the expected orientation when displaying on the screen:
244
- (0..frames-1).each do |i|
245
- temp_image=image[i,true,true]
246
- #Transpose the images:
247
- temp_image.transpose(1,0)
248
- #Need to mirror the y-axis:
249
- (0..temp_image.shape[0]-1).each do |j|
250
- temp_image[j,0..temp_image.shape[1]-1] = temp_image[j,temp_image.shape[1]-1..0]
251
- end
252
- # Put the reoriented image back in the image matrixx:
253
- image[i,true,true]=temp_image
254
- end
255
- return image
256
- end # of get_image_narray
257
-
258
-
259
- # Returns an array of RMagick image objects, where the size of the array corresponds to the number of frames in the image data.
260
- # To call this method the user needs to have loaded the ImageMagick library in advance (require 'RMagick').
261
- def get_image_magick
262
- # Does pixel data exist at all in the DICOM object?
263
- if @compression == nil
264
- add_msg("It seems pixel data is not present in this DICOM object: Returning false.")
265
- return false
266
- end
267
- # No support yet for color pixel data:
268
- if @color
269
- add_msg("Warning: Unpacking color pixel data is not supported yet for this method: Aborting.")
270
- return false
271
- end
272
- # Gather information about the dimensions of the image data:
273
- rows = get_value("0028,0010", :array => true)
274
- columns = get_value("0028,0011", :array => true)
275
- rows = [rows] unless rows.is_a?(Array)
276
- columns = [columns] unless columns.is_a?(Array)
277
- frames = get_frames
278
- image_pos = get_image_pos
279
- # Array that will hold the RMagick image objects, one image object for each frame:
280
- image_arr = Array.new(frames)
281
- # A hack for the special case (some MR files), where two images are stored (one is a smaller thumbnail image):
282
- if image_pos.length > 1 and columns.length > 1
283
- image_pos = [image_pos.last]
284
- columns = [columns[0]]
285
- rows = [rows[0]]
286
- end
287
- # Handling of image data will depend on whether we have one or more frames,
288
- if image_pos.size == 1
289
- # All of the image data is located in one data element:
290
- if frames > 1
291
- add_msg("Unfortunately, this method only supports reading the first image frame as of now.")
292
- end
293
- image = read_image_magick(image_pos[0], columns[0], rows[0])
294
- image_arr[0] = image
295
- else
296
- # Image data is encapsulated in items:
297
- (0..frames-1).each do |i|
298
- image = read_image_magick(image_pos[i], columns[0], rows[0])
299
- image_arr[i] = image
300
- end
301
- end
302
- return image_arr
303
- end # of get_image_magick
304
-
305
-
306
- # Returns the number of frames present in the image data in the DICOM file.
307
- def get_frames
308
- frames = get_value("0028,0008", :silent => true)
309
- # If the DICOM object does not specify the number of frames explicitly, assume 1 image frame:
310
- frames = 1 unless frames
311
- return frames.to_i
312
- end
313
-
314
-
315
- # Unpacks and returns pixel data from a specified data element array position:
316
- def get_pixels(pos)
317
- pixels = false
318
- # We need to know what kind of bith depth the pixel data is saved with:
319
- bit_depth = get_value("0028,0100", :array => true)
320
- unless bit_depth == false
321
- # Load the binary pixel data to the Stream instance:
322
- @stream.set_string(get_raw(pos))
323
- bit_depth = bit_depth.first if bit_depth.is_a?(Array)
324
- # Number of bytes used per pixel will determine how to unpack this:
325
- case bit_depth
326
- when 8
327
- pixels = @stream.decode_all("BY") # Byte/Character/Fixnum (1 byte)
328
- when 16
329
- pixels = @stream.decode_all("US") # Unsigned short (2 bytes)
330
- when 12
331
- # 12 BIT SIMPLY NOT WORKING YET!
332
- # This one is a bit more tricky to extract.
333
- # I havent really given this priority so far as 12 bit image data is rather rare.
334
- add_msg("Warning: Bit depth 12 is not working correctly at this time! Please contact the author.")
335
- else
336
- raise "Bit depth ["+bit_depth.to_s+"] has not received implementation in this procedure yet. Please contact the author."
337
- end # of case bit_depth
338
- else
339
- add_msg("Error: DICOM object does not contain the 'Bit Depth' data element (0028,0010).")
340
- end # of if bit_depth ..
341
- return pixels
342
- end
343
-
344
-
345
- # Returns the index(es) of the element(s) that contain image data.
346
- def get_image_pos
347
- image_element_pos = get_pos("7FE0,0010")
348
- item_pos = get_pos("FFFE,E000")
349
- # Proceed only if an image element actually exists:
350
- if image_element_pos == false
351
- return false
352
- else
353
- # Check if we have item elements:
354
- if item_pos == false
355
- return image_element_pos
356
- else
357
- # Extract item positions that occur after the image element position:
358
- late_item_pos = item_pos.select {|item| image_element_pos[0] < item}
359
- # Check if there are items appearing after the image element.
360
- if late_item_pos.size == 0
361
- # None occured after the image element position:
362
- return image_element_pos
363
- else
364
- # Determine which of these late item elements contain image data.
365
- # Usually, there are frames+1 late items, and all except
366
- # the first item contain an image frame:
367
- frames = get_frames
368
- if frames != false # note: function get_frames will never return false
369
- if late_item_pos.size == frames.to_i+1
370
- return late_item_pos[1..late_item_pos.size-1]
371
- else
372
- add_msg("Warning: Unexpected behaviour in DICOM file for method get_image_pos. Expected number of image data items not equal to number of frames+1, returning false.")
373
- return false
374
- end
375
- else
376
- add_msg("Warning: 'Number of Frames' data element not found. Method get_image_pos will return false.")
377
- return false
378
- end
379
- end
380
- end
381
- end
382
- end
383
-
384
-
385
- # Returns an array of the index(es) of the element(s) in the DICOM file that match the supplied element position, tag or name.
386
- # If no match is found, the method will return false.
387
- # Additional options:
388
- # :array => myArray - tells the method to search for matches in this specific array of positions instead of searching
389
- # through the entire DICOM object. If myArray equals false, the method will return false.
390
- # :partial => true - get_pos will not only search for exact matches, but will search the names and tags arrays for
391
- # strings that contain the given search string.
392
- def get_pos(query, options={})
393
- indexes = Array.new
394
- # For convenience, allow query to be a one-element array (its value will be extracted):
395
- if query.is_a?(Array)
396
- if query.length > 1 or query.length == 0
397
- add_msg("Invalid array length supplied to method get_pos.")
398
- return false
399
- else
400
- query = query[0]
401
- end
402
- end
403
- if options[:array] == false
404
- # If the supplied array option equals false, it signals that the user tries to search for an element
405
- # in an invalid position, and as such, this method will also return false:
406
- add_msg("Warning: Attempted to call get_pos with query #{query}, but since keyword :array is false I will return false.")
407
- indexes = false
408
- else
409
- # Check if query is a number (some methods want to have the ability to call get_pos with a number):
410
- if query.is_a?(Integer)
411
- # Return the position if it is valid:
412
- indexes = [query] if query >= 0 and query < @names.length
413
- elsif query.is_a?(String)
414
- # Either use the supplied array, or search the entire DICOM object:
415
- if options[:array].is_a?(Array)
416
- search_array = options[:array]
417
- else
418
- search_array = Array.new(@names.length) {|i| i}
419
- end
420
- # Perform search:
421
- if options[:partial] == true
422
- # Search for partial string matches:
423
- partial_indexes = search_array.all_indices_partial_match(@tags, query.upcase)
424
- if partial_indexes.length > 0
425
- indexes = partial_indexes
426
- else
427
- indexes = search_array.all_indices_partial_match(@names, query)
428
- end
429
- else
430
- # Search for identical matches:
431
- if query[4..4] == ","
432
- indexes = search_array.all_indices(@tags, query.upcase)
433
- else
434
- indexes = search_array.all_indices(@names, query)
435
- end
436
- end
437
- end
438
- # Policy: If no matches found, return false instead of an empty array:
439
- indexes = false if indexes.length == 0
440
- end
441
- return indexes
442
- end # of get_pos
443
-
444
-
445
- # Dumps the binary content of the Pixel Data element to file.
446
- def image_to_file(file)
447
- pos = get_image_pos
448
- if pos
449
- if pos.length == 1
450
- # Pixel data located in one element:
451
- pixel_data = get_raw(pos[0])
452
- f = File.new(file, "wb")
453
- f.write(pixel_data)
454
- f.close
455
- else
456
- # Pixel data located in several elements:
457
- pos.each_index do |i|
458
- pixel_data = get_raw(pos[i])
459
- f = File.new(file + i.to_s, "wb")
460
- f.write(pixel_data)
461
- f.close
462
- end
463
- end
464
- end
465
- end
466
-
467
-
468
- # Returns the positions of all data elements inside the hierarchy of a sequence or an item.
469
- # Options:
470
- # :next_only => true - The method will only search immediately below the specified
471
- # item or sequence (that is, in the level of parent + 1).
472
- def children(element, options={})
473
- # Process option values, setting defaults for the ones that are not specified:
474
- opt_next_only = options[:next_only] || false
475
- value = false
476
- # Retrieve array position:
477
- pos = get_pos(element)
478
- if pos == false
479
- add_msg("Warning: Invalid data element provided to method children. Returning false.")
480
- else
481
- if pos.size > 1
482
- add_msg("Warning: Method children does not allow a query which yields multiple array hits. Please use array position instead of tag/name. Returning false.")
483
- else
484
- # Proceed to find the value:
485
- # First we need to establish in which positions to perform the search:
486
- below_pos = Array.new
487
- pos.each do |p|
488
- parent_level = @levels[p]
489
- remain_array = @levels[p+1..@levels.size-1]
490
- extract = true
491
- remain_array.each_index do |i|
492
- if (remain_array[i] > parent_level) and (extract == true)
493
- # If search is targetted at any specific level, we can just add this position:
494
- if not opt_next_only == true
495
- below_pos << (p+1+i)
496
- else
497
- # As search is restricted to parent level + 1, do a test for this:
498
- if remain_array[i] == parent_level + 1
499
- below_pos << (p+1+i)
500
- end
501
- end
502
- else
503
- # If we encounter a position who's level is not deeper than the original level, we can not extract any more values:
504
- extract = false
505
- end
506
- end
507
- end # of pos.each do..
508
- value = below_pos if below_pos.size != 0
509
- end # of if pos.size..else..
510
- end
511
- return value
512
- end
513
-
514
-
515
- # Returns the value (processed raw data) of the requested DICOM data element.
516
- # Data element may be specified by array position, tag or name.
517
- # Options:
518
- # :array => true - Allows the query of the value of a tag that occurs more than one time in the
519
- # DICOM object. Values will be returned in an array with length equal to the number
520
- # of occurances of the tag. If keyword is not specified, the method returns false in this case.
521
- # :silent => true - As this method is also used internally, we want the possibility of warnings not being
522
- # raised even if verbose is set to true by the user, in order to avoid confusion.
523
- def get_value(element, options={})
524
- value = false
525
- # Retrieve array position:
526
- pos = get_pos(element)
527
- if pos == false
528
- add_msg("Warning: Invalid data element provided to method get_value. Returning false.") unless options[:silent]
529
- else
530
- if pos.size > 1
531
- if options[:array] == true
532
- # Retrieve all values into an array:
533
- value = Array.new
534
- pos.each do |i|
535
- value << @values[i]
536
- end
537
- else
538
- add_msg("Warning: Method get_value does not allow a query which yields multiple array hits. Please use array position instead of tag/name, or use keyword (:array => true). Returning false.") unless options[:silent]
539
- end
540
- else
541
- value = @values[pos[0]]
542
- end
543
- end
544
- return value
545
- end
546
-
547
-
548
- # Returns the raw data of the requested DICOM data element.
549
- # Data element may be specified by array position, tag or name.
550
- # Options:
551
- # :array => true - Allows the query of the value of a tag that occurs more than one time in the
552
- # DICOM object. Values will be returned in an array with length equal to the number
553
- # of occurances of the tag. If keyword is not specified, the method returns false in this case.
554
- def get_raw(element, options={})
555
- value = false
556
- # Retrieve array position:
557
- pos = get_pos(element)
558
- if pos == false
559
- add_msg("Warning: Invalid data element provided to method get_raw. Returning false.")
560
- else
561
- if pos.size > 1
562
- if options[:array] == true
563
- # Retrieve all values into an array:
564
- value = Array.new
565
- pos.each do |i|
566
- value << @raw[i]
567
- end
568
- else
569
- add_msg("Warning: Method get_raw does not allow a query which yields multiple array hits. Please use array position instead of tag/name, or use keyword (:array => true). Returning false.")
570
- end
571
- else
572
- value = @raw[pos[0]]
573
- end
574
- end
575
- return value
576
- end
577
-
578
-
579
- # Returns the position of (possible) parents of the specified data element in the hierarchy structure of the DICOM object.
580
- def parents(element)
581
- value = false
582
- # Retrieve array position:
583
- pos = get_pos(element)
584
- if pos == false
585
- add_msg("Warning: Invalid data element provided to method parents. Returning false.")
586
- else
587
- if pos.length > 1
588
- add_msg("Warning: Method parents does not allow a query which yields multiple array hits. Please use array position instead of tag/name. Returning false.")
589
- else
590
- # Proceed to find the value:
591
- # Get the level of our element:
592
- level = @levels[pos[0]]
593
- # Element can obviously only have parents if it is not a top level element:
594
- unless level == 0
595
- # Search backwards, and record the position every time we encounter an upwards change in the level number.
596
- parents = Array.new
597
- prev_level = level
598
- search_arr = @levels[0..pos[0]-1].reverse
599
- search_arr.each_index do |i|
600
- if search_arr[i] < prev_level
601
- parents << search_arr.length-i-1
602
- prev_level = search_arr[i]
603
- end
604
- end
605
- # When the element has several generations of parents, we want its top parent to be first in the returned array:
606
- parents = parents.reverse
607
- value = parents if parents.length > 0
608
- end
609
- end
610
- end
611
- return value
612
- end
613
-
614
-
615
- ##############################################
616
- ####### START OF METHODS FOR PRINTING INFORMATION:######
617
- ##############################################
618
-
619
-
620
- # Prints the information of all elements stored in the DICOM object.
621
- # This method is kept for backwards compatibility.
622
- # Instead of calling print_all you may use print(true) for the same functionality.
623
- def print_all
624
- print(true)
625
- end
626
-
627
-
628
- # Prints the information of the specified elements: Index, [hierarchy level, tree visualisation,] tag, name, type, length, value
629
- # The supplied variable may be a single position, an array of positions, or true - which will make the method print all elements.
630
- # Optional arguments:
631
- # :levels => true - method will print the level numbers for each element.
632
- # :tree => true - method will print a tree structure for the elements.
633
- # :file => true - method will print to file instead of printing to screen.
634
- def print(pos, options={})
635
- # Process option values, setting defaults for the ones that are not specified:
636
- opt_levels = options[:levels] || false
637
- opt_tree = options[:tree] || false
638
- opt_file = options[:file] || false
639
- # If pos is false, abort, and inform the user:
640
- if pos == false
641
- add_msg("Warning: Method print was supplied false instead of a valid position. Aborting print.")
642
- return
643
- end
644
- if not pos.is_a?(Array) and pos != true
645
- # Convert to array if number:
646
- pos_valid = [pos]
647
- elsif pos == true
648
- # Create a complete array of indices:
649
- pos_valid = Array.new(@names.length) {|i| i}
650
- else
651
- # Use the supplied array of numbers:
652
- pos_valid = pos
653
- end
654
- # Extract the information to be printed from the object arrays:
655
- indices = Array.new
656
- levels = Array.new
657
- tags = Array.new
658
- names = Array.new
659
- types = Array.new
660
- lengths = Array.new
661
- values = Array.new
662
- # There may be a more elegant way to do this.
663
- pos_valid.each do |pos|
664
- tags << @tags[pos]
665
- levels << @levels[pos]
666
- names << @names[pos]
667
- types << @types[pos]
668
- lengths << @lengths[pos].to_s
669
- values << @values[pos].to_s
670
- end
671
- # We have collected the data that is to be printed, now we need to do some string manipulation if hierarchy is to be displayed:
672
- if opt_tree
673
- # Tree structure requested.
674
- front_symbol = "| "
675
- tree_symbol = "|_"
676
- tags.each_index do |i|
677
- if levels[i] != 0
678
- tags[i] = front_symbol*(levels[i]-1) + tree_symbol + tags[i]
679
- end
680
- end
681
- end
682
- # Extract the string lengths which are needed to make the formatting nice:
683
- tag_lengths = Array.new
684
- name_lengths = Array.new
685
- type_lengths = Array.new
686
- length_lengths = Array.new
687
- names.each_index do |i|
688
- tag_lengths[i] = tags[i].length
689
- name_lengths[i] = names[i].length
690
- type_lengths[i] = types[i].length
691
- length_lengths[i] = lengths[i].to_s.length
692
- end
693
- # To give the printed output a nice format we need to check the string lengths of some of these arrays:
694
- index_maxL = pos_valid.max.to_s.length
695
- tag_maxL = tag_lengths.max
696
- name_maxL = name_lengths.max
697
- type_maxL = type_lengths.max
698
- length_maxL = length_lengths.max
699
- # Construct the strings, one for each line of output, where each line contain the information of one data element:
700
- elements = Array.new
701
- # Start of loop which formats the element data:
702
- # (This loop is what consumes most of the computing time of this method)
703
- tags.each_index do |i|
704
- # Configure empty spaces:
705
- s = " "
706
- f0 = " "*(index_maxL-pos_valid[i].to_s.length)
707
- f2 = " "*(tag_maxL-tags[i].length+1)
708
- f3 = " "*(name_maxL-names[i].length+1)
709
- f4 = " "*(type_maxL-types[i].length+1)
710
- f5 = " "*(length_maxL-lengths[i].to_s.length)
711
- # Display levels?
712
- if opt_levels
713
- lev = levels[i].to_s + s
714
- else
715
- lev = ""
716
- end
717
- # Restrict length of value string:
718
- if values[i].length > 28
719
- value = (values[i])[0..27]+" ..."
720
- else
721
- value = (values[i])
722
- end
723
- # Insert descriptive text for elements that hold binary data:
724
- case types[i]
725
- when "OW","OB","UN"
726
- value = "(Binary Data)"
727
- when "SQ","()"
728
- value = "(Encapsulated Elements)"
729
- end
730
- elements << (f0 + pos_valid[i].to_s + s + lev + s + tags[i] + f2 + names[i] + f3 + types[i] + f4 + f5 + lengths[i].to_s + s + s + value.rstrip)
731
- end
732
- # Print to either screen or file, depending on what the user requested:
733
- if opt_file
734
- print_file(elements)
735
- else
736
- print_screen(elements)
737
- end
738
- end # of print
739
-
740
-
741
- # Prints the key structural properties of the DICOM file.
742
- def print_properties
743
- # Explicitness:
744
- if @explicit
745
- explicit = "Explicit"
746
- else
747
- explicit = "Implicit"
748
- end
749
- # Endianness:
750
- if @file_endian
751
- endian = "Big Endian"
752
- else
753
- endian = "Little Endian"
754
- end
755
- # Pixel data:
756
- if @compression == nil
757
- pixels = "No"
758
- else
759
- pixels = "Yes"
760
- end
761
- # Colors:
762
- if @color
763
- image = "Colors"
764
- else
765
- image = "Greyscale"
766
- end
767
- # Compression:
768
- if @compression == true
769
- compression = @lib.get_uid(get_value("0002,0010").rstrip)
770
- else
771
- compression = "No"
772
- end
773
- # Bits per pixel (allocated):
774
- bits = get_value("0028,0100", :array => true)
775
- bits = bits[0].to_s if bits
776
- # Print the file properties:
777
- puts "Key properties of DICOM object:"
778
- puts "-------------------------------"
779
- puts "File: " + @file
780
- puts "Modality: " + @modality.to_s
781
- puts "Value repr.: " + explicit
782
- puts "Byte order: " + endian
783
- puts "Pixel data: " + pixels
784
- if pixels == "Yes"
785
- puts "Image: " + image
786
- puts "Compression: " + compression
787
- puts "Bits per pixel: " + bits
788
- end
789
- puts "-------------------------------"
790
- end # of print_properties
791
-
792
-
793
- ####################################################
794
- ### START OF METHODS FOR WRITING INFORMATION TO THE DICOM OBJECT:
795
- ####################################################
796
-
797
-
798
- # Reads binary information from file and inserts it in the pixel data element:
799
- def set_image_file(file)
800
- # Try to read file:
801
- begin
802
- f = File.new(file, "rb")
803
- bin = f.read(f.stat.size)
804
- rescue
805
- # Reading file was not successful. Register an error message.
806
- add_msg("Reading specified file was not successful for some reason. No data has been added.")
807
- return
808
- end
809
- if bin.length > 0
810
- pos = @tags.index("7FE0,0010")
811
- # Modify element:
812
- set_value(bin, "7FE0,0010", :create => true, :bin => true)
813
- else
814
- add_msg("Content of file is of zero length. Nothing to store.")
815
- end
816
- end
817
-
818
-
819
- # Transfers pixel data from a RMagick object to the pixel data element:
820
- def set_image_magick(magick_obj)
821
- # Export the RMagick object to a standard Ruby array of numbers:
822
- pixel_array = magick_obj.export_pixels(x=0, y=0, columns=magick_obj.columns, rows=magick_obj.rows, map="I")
823
- # Encode this array using the standard class method:
824
- set_value(pixel_array, "7FE0,0010", :create => true)
825
- end
826
-
827
-
828
- # Removes an element from the DICOM object:
829
- def remove(element)
830
- pos = get_pos(element)
831
- if pos != false
832
- if pos.length > 1
833
- add_msg("Warning: Method remove does not allow an element query which yields multiple array hits. Please use array position instead of tag/name. Value NOT removed.")
834
- else
835
- # Extract first array number:
836
- pos = pos[0]
837
- # Update group length:
838
- if @tags[pos][5..8] != "0000"
839
- change = @lengths[pos]
840
- vr = @types[pos]
841
- update_group_length(pos, vr, change, -1)
842
- end
843
- # Remove entry from arrays:
844
- @tags.delete_at(pos)
845
- @levels.delete_at(pos)
846
- @names.delete_at(pos)
847
- @types.delete_at(pos)
848
- @lengths.delete_at(pos)
849
- @values.delete_at(pos)
850
- @raw.delete_at(pos)
851
- end
852
- else
853
- add_msg("Warning: The data element #{element} could not be found in the DICOM object. Method remove has no data element to remove.")
854
- end
855
- end
856
-
857
-
858
- # Sets the value of a data element by modifying an existing element or creating a new one.
859
- # If the supplied value is not binary, it will attempt to encode the value to binary itself.
860
- def set_value(value, element, options={})
861
- # Options:
862
- create = options[:create] # =false means no element creation
863
- bin = options[:bin] # =true means value already encoded
864
- # Retrieve array position:
865
- pos = get_pos(element)
866
- # We do not support changing multiple data elements:
867
- if pos.is_a?(Array)
868
- if pos.length > 1
869
- add_msg("Warning: Method set_value does not allow an element query which yields multiple array hits. Please use array position instead of tag/name. Value NOT saved.")
870
- return
871
- end
872
- end
873
- if pos == false and create == false
874
- # Since user has requested an element shall only be updated, we can not do so as the element position is not valid:
875
- add_msg("Warning: Invalid data element provided to method set_value. Value NOT updated.")
876
- elsif create == false
877
- # Modify element:
878
- modify_element(value, pos[0], :bin => bin)
879
- else
880
- # User wants to create an element (or modify it if it is already present).
881
- unless pos == false
882
- # The data element already exist, so we modify instead of creating:
883
- modify_element(value, pos[0], :bin => bin)
884
- else
885
- # We need to create element:
886
- tag = @lib.get_tag(element)
887
- if tag == false
888
- add_msg("Warning: Method set_value could not create data element, either because data element name was not recognized in the library, or data element tag is invalid (Expected format of tags is 'GGGG,EEEE').")
889
- else
890
- # As we wish to create a new data element, we need to find out where to insert it in the element arrays:
891
- # We will do this by finding the array position of the last element that will (alphabetically/numerically) stay in front of this element.
892
- if @tags.size > 0
893
- # Search the array:
894
- index = -1
895
- quit = false
896
- while quit != true do
897
- if index+1 >= @tags.length # We have reached end of array.
898
- quit = true
899
- elsif tag < @tags[index+1] and @levels[index+1] == 0 # We are past the correct position (only match against top level tags).
900
- quit = true
901
- else # Increase index in anticipation of a 'hit'.
902
- index += 1
903
- end
904
- end # of while
905
- else
906
- # We are dealing with an empty DICOM object:
907
- index = nil
908
- end
909
- # The necessary information is gathered; create new data element:
910
- create_element(value, tag, index, :bin => bin)
911
- end
912
- end
913
- end
914
- end # of set_value
915
-
916
-
917
- ##################################################
918
- ############## START OF PRIVATE METHODS: ########
919
- ##################################################
920
- private
921
-
922
-
923
- # Adds a warning or error message to the instance array holding messages, and if verbose variable is true, prints the message as well.
924
- def add_msg(msg)
925
- puts msg if @verbose
926
- @errors << msg
927
- @errors.flatten
928
- end
929
-
930
-
931
- # Creates a new data element:
932
- def create_element(value, tag, last_pos, options={})
933
- bin_only = options[:bin]
934
- # Fetch the VR:
935
- info = @lib.get_name_vr(tag)
936
- vr = info[1]
937
- name = info[0]
938
- # Encode binary (if a binary is not provided):
939
- if bin_only == true
940
- # Data already encoded.
941
- bin = value
942
- value = nil
943
- else
944
- if vr != "UN"
945
- # Encode:
946
- bin = encode(value, vr)
947
- else
948
- add_msg("Error. Unable to encode data element value of unknown type (Value Representation)!")
949
- end
950
- end
951
- # Put the information of this data element into the arrays:
952
- if bin
953
- # 4 different scenarios: Array is empty, or: element is put in front, inside array, or at end of array:
954
- # NB! No support for hierarchy at this time! Defaulting to level = 0.
955
- if last_pos == nil
956
- # We have empty DICOM object:
957
- @tags = [tag]
958
- @levels = [0]
959
- @names = [name]
960
- @types = [vr]
961
- @lengths = [bin.length]
962
- @values = [value]
963
- @raw = [bin]
964
- elsif last_pos == -1
965
- # Insert in front of arrays:
966
- @tags = [tag] + @tags
967
- @levels = [0] + @levels
968
- @names = [name] + @names
969
- @types = [vr] + @types
970
- @lengths = [bin.length] + @lengths
971
- @values = [value] + @values
972
- @raw = [bin] + @raw
973
- elsif last_pos == @tags.length-1
974
- # Insert at end arrays:
975
- @tags = @tags + [tag]
976
- @levels = @levels + [0]
977
- @names = @names + [name]
978
- @types = @types + [vr]
979
- @lengths = @lengths + [bin.length]
980
- @values = @values + [value]
981
- @raw = @raw + [bin]
982
- else
983
- # Insert somewhere inside the array:
984
- @tags = @tags[0..last_pos] + [tag] + @tags[(last_pos+1)..(@tags.length-1)]
985
- @levels = @levels[0..last_pos] + [0] + @levels[(last_pos+1)..(@levels.length-1)]
986
- @names = @names[0..last_pos] + [name] + @names[(last_pos+1)..(@names.length-1)]
987
- @types = @types[0..last_pos] + [vr] + @types[(last_pos+1)..(@types.length-1)]
988
- @lengths = @lengths[0..last_pos] + [bin.length] + @lengths[(last_pos+1)..(@lengths.length-1)]
989
- @values = @values[0..last_pos] + [value] + @values[(last_pos+1)..(@values.length-1)]
990
- @raw = @raw[0..last_pos] + [bin] + @raw[(last_pos+1)..(@raw.length-1)]
991
- end
992
- # Update last index variable as we have added to our arrays:
993
- @last_index += 1
994
- # Update group length (as long as it was not a group length element that was created):
995
- pos = @tags.index(tag)
996
- if @tags[pos][5..8] != "0000"
997
- change = bin.length
998
- update_group_length(pos, vr, change, 1)
999
- end
1000
- else
1001
- add_msg("Binary is nil. Nothing to save.")
1002
- end
1003
- end # of create_element
1004
-
1005
-
1006
- # Encodes a value to binary (used for inserting values into a DICOM object).
1007
- def encode(value, vr)
1008
- # VR will decide how to encode this value:
1009
- case vr
1010
- when "AT" # (Data element tag: Assume it has the format "GGGG,EEEE"
1011
- if value.is_a_tag?
1012
- bin = @stream.encode_tag(value)
1013
- else
1014
- add_msg("Invalid tag format (#{value}). Expected format: 'GGGG,EEEE'")
1015
- end
1016
- # We have a number of VRs that are encoded as string:
1017
- when 'AE','AS','CS','DA','DS','DT','IS','LO','LT','PN','SH','ST','TM','UI','UT'
1018
- # In case we are dealing with a number string element, the supplied value might be a number
1019
- # instead of a string, and as such, we convert to string just to make sure this will work nicely:
1020
- value = value.to_s
1021
- bin = @stream.encode_value(value, "STR")
1022
- # Image related value representations:
1023
- when "OW"
1024
- # What bit depth to use when encoding the pixel data?
1025
- bit_depth = get_value("0028,0100")
1026
- if bit_depth == false
1027
- # Data element not specified:
1028
- add_msg("Attempted to encode pixel data, but 'Bit Depth' data element is missing (0028,0100).")
1029
- else
1030
- # 8,12 or 16 bits?
1031
- case bit_depth
1032
- when 8
1033
- bin = @stream.encode(value, "BY")
1034
- when 12
1035
- # 12 bit not supported yet!
1036
- add_msg("Encoding 12 bit pixel values not supported yet. Please change the bit depth to 8 or 16 bits.")
1037
- when 16
1038
- bin = @stream.encode(value, "US")
1039
- else
1040
- # Unknown bit depth:
1041
- add_msg("Unknown bit depth #{bit_depth}. No data encoded.")
1042
- end
1043
- end
1044
- # All other VR's:
1045
- else
1046
- # Just encode:
1047
- bin = @stream.encode(value, vr)
1048
- end # of case vr
1049
- return bin
1050
- end # of encode
1051
-
1052
-
1053
- # Modifies existing data element:
1054
- def modify_element(value, pos, options={})
1055
- bin_only = options[:bin]
1056
- # Fetch the VR and old length:
1057
- vr = @types[pos]
1058
- old_length = @lengths[pos]
1059
- # Encode binary (if a binary is not provided):
1060
- if bin_only == true
1061
- # Data already encoded.
1062
- bin = value
1063
- value = nil
1064
- else
1065
- if vr != "UN"
1066
- # Encode:
1067
- bin = encode(value, vr)
1068
- else
1069
- add_msg("Error. Unable to encode data element value of unknown type (Value Representation)!")
1070
- end
1071
- end
1072
- # Update the arrays with this new information:
1073
- if bin
1074
- # Replace array entries for this element:
1075
- #@types[pos] = vr # for the time being there is no logic for updating type.
1076
- @lengths[pos] = bin.length
1077
- @values[pos] = value
1078
- @raw[pos] = bin
1079
- # Update group length (as long as it was not the group length that was modified):
1080
- if @tags[pos][5..8] != "0000"
1081
- change = bin.length - old_length
1082
- update_group_length(pos, vr, change, 0)
1083
- end
1084
- else
1085
- add_msg("Binary is nil. Nothing to save.")
1086
- end
1087
- end
1088
-
1089
-
1090
- # Prints the selected elements to an ascii text file.
1091
- # The text file will be saved in the folder of the original DICOM file,
1092
- # with the original file name plus a .txt extension.
1093
- def print_file(elements)
1094
- File.open( @file + '.txt', 'w' ) do |output|
1095
- elements.each do | line |
1096
- output.print line + "\n"
1097
- end
1098
- end
1099
- end
1100
-
1101
-
1102
- # Prints the selected elements to screen.
1103
- def print_screen(elements)
1104
- elements.each do |element|
1105
- puts element
1106
- end
1107
- end
1108
-
1109
-
1110
- # Sets the modality variable of the current DICOM object, by querying the library with the object's SOP Class UID.
1111
- def set_modality
1112
- value = get_value("0008,0016", :silent => true)
1113
- if value == false
1114
- @modality = "Not specified"
1115
- else
1116
- modality = @lib.get_uid(value.rstrip)
1117
- @modality = modality
1118
- end
1119
- end
1120
-
1121
-
1122
- # Handles the creation of a DWrite object, and returns this object to the calling method.
1123
- def set_write_object(file_name = nil, transfer_syntax = nil)
1124
- unless transfer_syntax
1125
- transfer_syntax = get_value("0002,0010", :silent => true)
1126
- transfer_syntax = "1.2.840.10008.1.2" if not transfer_syntax # Default is implicit, little endian
1127
- end
1128
- w = DWrite.new(file_name, :lib => @lib, :sys_endian => @sys_endian, :transfer_syntax => transfer_syntax)
1129
- w.tags = @tags
1130
- w.types = @types
1131
- w.lengths = @lengths
1132
- w.raw = @raw
1133
- w.rest_endian = @file_endian
1134
- w.rest_explicit = @explicit
1135
- return w
1136
- end
1137
-
1138
-
1139
- # Updates the group length value when a data element has been updated, created or removed:
1140
- # The variable change holds the change in value length for the updated data element.
1141
- # (Change should be positive when a data element is removed - it will only be negative when editing an element to a shorter value)
1142
- # The variable existance is -1 if data element has been removed, +1 if element has been added and 0 if it has been updated.
1143
- # (Perhaps in the future this functionality might be moved to the DWrite class, it might give an easier implementation)
1144
- def update_group_length(pos, type, change, existance)
1145
- # Find position of relevant group length (if it exists):
1146
- gl_pos = @tags.index(@tags[pos][0..4] + "0000")
1147
- existance = 0 if existance == nil
1148
- # If it exists, calculate change:
1149
- if gl_pos
1150
- if existance == 0
1151
- # Element has only been updated, so we only need to think about value change:
1152
- value = @values[gl_pos] + change
1153
- else
1154
- # Element has either been created or removed. This means we need to calculate the length of its other parts.
1155
- if @explicit
1156
- # In the explicit scenario it is slightly complex to determine this value:
1157
- element_length = 0
1158
- # VR?:
1159
- unless @tags[pos] == "FFFE,E000" or @tags[pos] == "FFFE,E00D" or @tags[pos] == "FFFE,E0DD"
1160
- element_length += 2
1161
- end
1162
- # Length value:
1163
- case @types[pos]
1164
- when "OB","OW","SQ","UN"
1165
- if pos > @tags.index("7FE0,0010").to_i and @tags.index("7FE0,0010").to_i != 0
1166
- element_length += 4
1167
- else
1168
- element_length += 6
1169
- end
1170
- when "()"
1171
- element_length += 4
1172
- else
1173
- element_length += 2
1174
- end # of case
1175
- else
1176
- # In the implicit scenario it is easier:
1177
- element_length = 4
1178
- end
1179
- # Update group length for creation/deletion scenario:
1180
- change = (4 + element_length + change) * existance
1181
- value = @values[gl_pos] + change
1182
- end
1183
- # Write the new Group Length value:
1184
- # Encode the new value to binary:
1185
- bin = encode(value, "UL")
1186
- # Update arrays:
1187
- @values[gl_pos] = value
1188
- @raw[gl_pos] = bin
1189
- end
1190
- end # of update_group_length
1191
-
1192
-
1193
- end # of class
1194
- end # of module